1. 程式人生 > >以太坊錢包開發

以太坊錢包開發

目前的公鏈專案,影響力最大的應該就數以太坊和比特幣了,其他的多數公鏈,基本上都是借鑑了以太坊和比特幣公鏈專案而設計開發的。瞭解區塊鏈的人都知道,比特幣和以太坊這兩個公鏈專案的差距還是挺大的,故而他們的錢包開發也是特別不一樣的。本章我們將詳細講解以太坊錢包原理和開發流程,涉及到的內容有以下這些:

  • 依託錢包節點方式開發錢包,但這種方式的缺點就keystore生成存放到區塊的節點上;

  • 非確定性以太坊錢包開發,實現本地儲存私鑰,但每個賬戶對應一個私鑰,私鑰的管理比較難;

  • 分層確定性以太坊錢包開發流程,實現本地儲存,實現多鏈多賬戶和私鑰關聯性錢包。

一.以太坊簡介

1.什麼是以太坊

以太坊是一個開放的區塊鏈平臺,任何人都可以使用區塊鏈技術構建和使用分散的應用程式。 像比特幣一樣,沒有人控制或擁有以太坊,它是由世界各地的許多人建立的開源專案。 但與比特幣協議不同,以太坊的設計具有適應性和靈活性。 在以太坊平臺上建立新應用程式很容易,任何人都可以安全地使用這些應用程式。基於以太坊的一套生態也越來越強大,目前在以太坊上發Token的公司已經有將近800家了,質量比較高的數字資產佔Token量的20%以上,基於以太的數字貨幣資產的發展直接導致了進入的資金越來越龐大。

2.以太坊產生的背景

區塊鏈技術是比特幣的技術基礎,首先由其神祕作者中本聰(Satoshi Nakamoto)在2008年出版的白皮書“比特幣:點對點電子現金系統”中描述。雖然區塊鏈用於更廣泛的用途已經是在原始論文中討論過,直到幾年後,區塊鏈技術才成為一個通用術語。區塊鏈是一種分散式計算架構,其中每個網路節點執行並記錄相同的事務,這些事務被分組為塊。一次只能新增一個塊,並且每個塊都包含一個數學證明,用於驗證它是否依次跟隨前一個塊。通過這種方式,區塊鏈的“分散式資料庫”在整個網路中保持一致。個人使用者與分類帳(交易)的互動由強密碼術保護。維護和驗證網路的節點受到編碼到協議中的數學強制經濟激勵的激勵。

在比特幣的情況下,分散式資料庫被設想為賬戶餘額表,分類賬,交易是比特幣代幣的轉移,以促進個人之間的無信任融資。但隨著比特幣開始引起開發人員和技術人員的更多關注,新的專案開始將比特幣網路用於除了價值代幣轉移之外的其他目的。其中許多采取“替代硬幣”的形式 - 單獨的區塊鏈與他們自己的加密貨幣,改進了原有的比特幣協議,以增加新的功能或功能。 2013年底,以太坊的發明者Vitalik Buterin提出,一個能夠重新程式設計以執行任意複雜計算的區塊鏈可以包含這些許多其他專案。

2014年,以太坊創始人Vitalik Buterin,Gavin Wood和Jeffrey Wilcke開始研究下一代區塊鏈,該區塊鏈有望實施一個完全無信任的通用智慧合約平臺。

3.以太坊虛擬機器

以太坊是一個可程式設計的區塊鏈。 Ethereum不是為使用者提供一組預定義的操作(例如比特幣交易),而是允許使用者建立他們希望的任何複雜度的自己的操作。通過這種方式,它可以作為許多不同型別的分散式區塊鏈應用程式的平臺,包括但不限於加密貨幣。

狹義上的以太坊是指為分散應用程式定義平臺的一套協議。其核心是以太坊虛擬機器(“EVM”),它可以執行任意演算法複雜度的程式碼。在計算機科學術語中,以太坊是“圖靈完整”。開發人員可以使用以JavaScript和Python等現有語言為模型的友好程式語言建立在EVM上執行的應用程式。

與任何區塊鏈一樣,以太坊也包括點對點網路協議。以太坊區塊鏈資料庫由連線到網路的許多節點維護和更新。網路的每個節點都執行EVM並執行相同的指令。出於這個原因,以太坊有時被描述為“世界計算機”。

在整個以太坊網路中進行大規模的計算並行化並不是為了提高計算效率。實際上,這個過程使得以太坊上的計算比傳統的“計算機”慢得多,也更昂貴。相反,每個以太坊節點都執行EVM,以便在區塊鏈中保持一致。分散的共識為以太坊提供了極端的容錯能力,確保零停機時間,並使儲存在區塊鏈上的資料永遠不變且具有審查能力。

以太坊平臺本身沒有特色或與價值無關。與程式語言類似,由企業家和開發人員決定應該使用什麼。但是,很明顯某些應用程式型別比以太坊的功能更有益。具體而言,以太坊適用於自動化同伴之間直接互動或促進跨網路協調的群組行動的應用程式。例如,用於協調點對點市場的應用程式,或複雜金融合同的自動化。比特幣允許個人交換現金而不涉及金融機構,銀行或政府等任何中間人。以太坊的影響可能更為深遠。從理論上講,任何複雜性的金融互動或交換都可以使用在以太坊上執行的程式碼自動可靠地執行。除了金融應用程式之外,任何信任,安全性和永續性都很重要的環境(例如,資產註冊,投票,治理和物聯網)都可能受到以太坊平臺的嚴重影響。

4.以太坊工作機制

以太坊融合了比特幣使用者所熟悉的許多功能和技術,同時還引入了許多自己的修改和創新。

雖然比特幣區塊鏈純粹是一個交易清單,但以太坊的基本單位是賬戶。以太坊區塊鏈跟蹤每個賬戶的狀態,以太坊區塊鏈上的所有狀態轉換都是賬戶之間價值和資訊的轉移。有兩種型別的帳戶:

  • 外部擁有帳戶(EOA),由私鑰控制
  • 合約賬戶,由合約程式碼控制,只能由EOA“啟用”

對於大多數使用者而言,這些使用者之間的基本區別在於人類使用者控制EOA,因為他們可以控制控制EOA的私鑰。另一方面,合同賬戶由其內部程式碼管理。如果它們被人類使用者“控制”,那是因為它們被程式設計為由具有特定地址的EOA控制,該地址又由持有控制該EOA的私鑰的任何人控制。流行的術語“智慧合約”是指合約賬戶中的程式碼 - 在將交易傳送到該賬戶時執行的程式。使用者可以通過將程式碼部署到區塊鏈來建立新合同。

合同賬戶僅在EOA指示時執行操作。因此,合同帳戶不可能執行本機操作(如隨機數生成或API呼叫),只有在EOA提示時才能執行這些操作。這是因為以太坊要求節點能夠就計算結果達成一致,這需要保證嚴格確定性的執行。

與比特幣一樣,使用者必須向網路支付小額交易費用。這可以保護以太坊區塊鏈免受無聊或惡意的計算任務,如DDoS攻擊或無限迴圈。事務的傳送者必須為他們啟用的“程式”的每一步付費,包括計算和記憶體儲存。這些費用以以太坊的本地價值標記,以太幣的金額支付。

這些交易費用由驗證網路的節點收集。這些“礦工”是以太坊網路中接收,傳播,驗證和執行交易的節點。然後礦工將交易分組,其中包括對以太坊區塊鏈中賬戶“狀態”的許多更新,進入所謂的“區塊”,然後礦工互相競爭,使他們的區塊成為下一個要新增的區塊。區塊鏈。對於他們開採的每個成功區塊,礦工都會獲得以太獎勵。這為人們將乙太網和電力專用於以太坊網路提供了經濟激勵。

正如在比特幣網路中一樣,礦工的任務是解決複雜的數學問題,以便成功“挖掘”一個區塊。這被稱為“工作證明”。任何計算問題都需要數量級更多的資源才能在演算法上解決,而不是驗證解決方案,這是一個很好的候選工作證明。為了阻止由於使用專用硬體(例如ASIC)而導致的集中化,如比特幣網路中所發生的那樣,以太坊選擇了儲存器硬計算問題。如果問題需要記憶體和CPU,理想的硬體實際上是通用計算機。這使得以太坊的工作證明具有ASIC抗性,允許比採用比特幣等專用硬體占主導地位的區塊鏈更安全地分散安全性。

二.以太坊錢包涉及到的術語

1.gas,Gas Limit和Gas Price

在以太坊上,傳送代幣或呼叫智慧合約,在區塊鏈上執行寫入操作,需要支付礦工計算費用,計費是按照Gas計算的,Gas使用ETH來支付。無論您的呼叫的方法是成功還是失敗,都需要支付計算費用。即使失敗,礦工也驗證並執行您的交易(計算),因此必須和成功交易一樣支付礦工費。

簡單的說Gas Limit相當於汽車需要加多少汽油,Gas Price相當於每升汽油的價格。

Gas Limit之所以稱為限額,因為它是您願意在一筆交易中花費Gas的最大數量。交易所需的Gas是通過呼叫智慧合約執行多少程式碼來定義。 如果您不想花太多的Gas,通過降低Gas Limit將不會有太大的幫助。 因為您必須包括足夠的Gas來支付的計算資源,否則由於Gas不夠導致交易失敗,您所設定的所有Gas limit也將消耗光。建議您在有充足ETH情況下,將Gas Limit儘量設高,所有未使用的Gas將在轉賬結束時退還給您。

通過降低Gas Price可以節省礦工費用,但是也會減慢礦工打包的速度。礦工會優先打包 Gas Price設定高的交易,如果您想加快轉賬,您可以把Gas Price設定得更高,這樣您就可以排隊靠前。如果您不急,您只需要設定一個安全的 Gas Price,礦工也會打包您的交易。

檢視礦工可以接受的最低Gas Price:https://etherscan.io/gastracker

.:
.:

2.以太幣單位

以太坊最小的單位是wei,最常用的單位是ether

  • Kwei(Babbage)= 10的3次方Wei
  • Mwei(Lovelace)= 10的6次方Wei
  • Gwei(Shannon)= 10的9次方Wei
  • MicroEther(Szabo)= 10的12次方Wei
  • MilliEther(Finney)= 10的15次方Wei
  • Ether = 10的18次方Wei

3.keystore

keystore是以太坊賬戶的一種表現形式,裡面包含了以太坊賬戶的地址,賬戶密文的私鑰和MAC地址等等一系列的資訊,下面是一個keystore的完整資訊。

{
  "address":"2ffe6e3816b6a84f509ea3be1b8e8bb024c894d8",
  "crypto":
  {
    "cipher":"aes-128-ctr",
    "ciphertext":"c302e468ad640ad6c43d51754caa60964ece820ad98ad0d5aa72a785a93d7a59",
    "cipherparams": {"iv":"f4609a583b28e48a6bda7b6bf1229a26"},
    "mac":"ef8872a3ad0a92a410b15b0e2e662d5cbfc98360d72b190a7b3189bd4151ebcf",
    "kdf":"pbkdf2",
    "kdfparams":
    {
      "c":262144,
      "dklen":32,
      "prf":"hmac-sha256",
      "salt":"5a757ae33c08a46c8a50f34a2a514503fd66481ea56aeab31e25da45ae3f1c39"
    }
 },
 "id":"49a53e88-4f8a-4858-80a4-02ad230da1d3",
 "version":3
}

4.礦工費

轉賬或呼叫智慧合約,在區塊鏈上執行寫入操作,需要支付礦工計算費用,計費是按照Gas計算的,Gas使用ETH來支付,詳細內容請看上面 1 部分

三.以太坊錢包涉及到的開源庫

1.keythereum

Keythereum是一個用於生成,匯入和匯出以太坊金鑰的JavaScript工具。 這提供了一種在本地和Web錢包中使用同一帳戶的簡單方法。 它可用於可驗證和儲存錢包。

Keythereum使用相同的金鑰派生函式(PBKDF2-SHA256或scrypt),對稱密碼(AES-128-CTR或AES-128-CBC)和訊息驗證程式碼作為geth。 您可以將生成的金鑰匯出到檔案,將其複製到資料目錄的金鑰庫,然後立即開始在您的本地以太坊客戶端中使用它。

從版本0.5.0開始,keythereum的加密和解密函式都返回Buffers而不是字串。 對於直接使用這些功能的人來說,這是一個重大改變。

1.1.使用keythereum生產keystore

在生產keystore之前,你必須有一個nodeJs的環境,並且安裝keythereum

npm install keythereum

或者使用壓縮的瀏覽器檔案dist/keythereum.min.js,以便在瀏覽器中使用。 使用一下程式碼引入

<script src="dist/keythereum.min.js" type="text/javascript"></script>

生成一個新的隨機私鑰(256位),以及金鑰派生函式使用的salt(256位),用於AES-128-CTR的初始化向量(128位)對金鑰進行加密。 如果傳遞迴調函式,則create是非同步的,否則是同步的。

下面是生成keystore的程式碼:

var keythereum = require("keythereum");

var params = { keyBytes: 32, ivBytes: 16 };
var dk = keythereum.create(params);
keythereum.create(params, function (dk) {
    var password = "wheethereum";
    var kdf = "pbkdf2";
    var options = {
        kdf: "pbkdf2",
        cipher: "aes-128-ctr",
        kdfparams: {
            c: 262144,
            dklen: 32,
            prf: "hmac-sha256"
        }
    };
    keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options, function (keyObject) {
        console.log(keyObject);
    });
});

下圖是執行結果:

.:
.:

1.2.將keystore到檔案中儲存

dump建立一個物件而不是JSON字串。 在Node中,exportToFile方法提供了一種將此格式化的金鑰物件匯出到檔案的簡便方法。 它在keystore子目錄中建立一個JSON檔案,並使用geth的當前檔案命名約定(ISO時間戳與金鑰派生的以太坊地址連線)。

程式碼如下:

var keythereum = require("keythereum");

var params = { keyBytes: 32, ivBytes: 16 };
var dk = keythereum.create(params);
keythereum.create(params, function (dk) {
    var password = "wheethereum";
    var kdf = "pbkdf2";
    var options = {
        kdf: "pbkdf2",
        cipher: "aes-128-ctr",
        kdfparams: {
            c: 262144,
            dklen: 32,
            prf: "hmac-sha256"
        }
    };
    keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options, function (keyObject) {
        keythereum.exportToFile(keyObject);
    });
});

成功之後在你的keystor目錄下將看到下面這些資訊,如果出現錯誤,最可能的原因就是你的目錄下沒有keystore這個目錄,當然以上程式碼中你也可以指定keystore的儲存目錄

.:
.:

1.3.keystore的匯入

從geth的金鑰庫匯入金鑰只能在Node上完成。 將JSON檔案解析為與上面的keyObject具有相同結構的物件。

var datadir = "/home/jack/.ethereum-test";
var keyObject = keythereum.importFromFile(address, datadir);
console.log(keyObject)
keythereum.importFromFile(address, datadir, function (keyObject) {
   console.log(keyObject)
});

1.4.從keystore中恢復私鑰

這裡恢復出來的私鑰是buffer格式的,password是你設定的密碼,keyObject就是keystore

var privateKey = keythereum.recover(password, keyObject);
console.log(privateKey)
keythereum.recover(password, keyObject, function (privateKey) {
  console.log(privateKey)
});

2.ethereumjs-tx

ethereumjs-tx是用來對以太坊或者ERC20代幣的交易進行交易簽名的javascript庫.

2.1.安裝ethereumjs-tx

npm install ethereumjs-tx

2.2.使用ethereumjs-tx庫對交易簽名

2.2.1.以太坊交易簽名

const Web3 =require ('web3')
const transaction = require('ethereumjs-tx');
if (typeof web3 !== 'undefined')
{
    var web3 = new Web3(web3.currentProvider);
} else {
    var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}

function ethereumSign(privateKey, nonce, toAddress, sendToBalance, sendFee) {
    if(!privateKey || !nonce || !toAddress || !sendToBalance || !sendFee) {
        console.log("one of fromAddress, toAddress, sendToBalance, sendFee is null, please give a valid param");
    } else {
        console.log("param is valid, start sign transaction");
        var transactionNonce = parseInt(nonce).toString(16);
        console.log("transaction nonce is " + transactionNonce);
        var numBalance = parseFloat(sendToBalance);
        var balancetoWei = web3.toWei(numBalance, "ether");
        console.log("balancetoWei is " + balancetoWei);
        var oxNumBalance = parseInt(balancetoWei).toString(16);
        console.log("16 oxNumBalance is " + oxNumBalance);
        var gasFee = parseFloat(sendFee);
        var gasFeeToWei = web3.toWei(gasFee, "ether");
        console.log("gas fee to wei is " + gasFeeToWei);
        var gas = gasFeeToWei / 21000;
        console.log("param gas is " + gas);
        var oxgasFeeToWei = parseInt(gas).toString(16);
        console.log("16 oxgasFeeToWei is " + oxgasFeeToWei);
        var privateKeyBuffer =  Buffer.from(privateKey, 'hex');
        var rawTx = {
            nonce:'0x' + transactionNonce,
            gasPrice: '0x5208',
            gas:'0x4a817c800', //+ oxgasFeeToWei,
            to: '0x' + toAddress,
            value:'0x' + oxNumBalance,
            data: '',
            chainId: 1
        };
        var tx = new transaction(rawTx);
        tx.sign(privateKeyBuffer);
        var serializedTx = tx.serialize();
        if(serializedTx == null) {
            console.log("Serialized transaction fail")
        } else {
            console.log("Serialized transaction success and the result is " + serializedTx.toString('hex'));
            console.log("The sender address is " + tx.getSenderAddress().toString('hex'));
            if (tx.verifySignature()) {
                console.log('Signature Checks out!')
            } else {
                console.log("Signature checks fail")
            }
        }
    }
    return '0x' + serializedTx.toString('hex')
}

var privateKey = "5204627f8e58b3c75be408423c121988a1cb942e73470fb19186ef8986cd50b3";
var nonce = 10;
var toAddress = "c6328b3a137b3be3f01c35ecda4ecda375be7fdf";
var sendToBalance = "0.003";
var sendFee = "0.001";

var sign = ethereumSign(privateKey, nonce, toAddress, sendToBalance, sendFee);
console.log(sign);

簽名結果如下:

.:
.:

2.2.2.ERC20代幣交易簽名

const Web3 =require ('web3')
const transaction = require('ethereumjs-tx');

function addPreZero(num){
    var t = (num+'').length,
        s = '';
    for(var i=0; i<64-t; i++){
        s += '0';
    }
    return s+num;
}

function ethereumErc20CoinSign(privateKey/*私鑰*/, nonce/*標識交易*/, currentAccount/*當前賬戶*/,  contractAddress/*合約地址*/, toAddress/*轉入地址*/,  gasPrice/*gasPrice*/,  gasLimit/*gasLimit*/, totalAmount/*代幣總量*/ , decimal /*代幣的單位換算*/) {
    if(!privateKey || !nonce || !currentAccount || !contractAddress || !toAddress  || !gasPrice || !gasLimit || !totalAmount || !decimal) {
        console.log("one of param is null, please give a valid param");
        return ;
    }
    var transactionNonce = parseInt(nonce).toString(16);
    console.log("transaction nonce is " + transactionNonce);
    var gasLimit = parseInt(gasLimit).toString(16);
    console.log("send transaction gasLimit is " + gasLimit);
    var gasPrice = parseFloat(gasPrice).toString(16);
    console.log("send transaction gasPrice is " + gasPrice)
    var totx = parseFloat(totalAmount*(10**decimal)).toString(16); //
    var txData = {
        nonce: '0x'+ transactionNonce,
        gasLimit: '0x' + gasLimit,
        gasPrice: '0x' +gasPrice,
        to: contractAddress,
        from: currentAccount,
        value: '0x00',
        data: '0x' + 'a9059cbb' + addPreZero(toAddress) + addPreZero(totx)
    }
    var tx = new transaction(txData);
    const privateKey1 = new Buffer(privateKey, 'hex');
    tx.sign(privateKey1);
    var serializedTx = tx.serialize().toString('hex');
    // console.log("transaction sign result is " + serializedTx);
    return serializedTx; /*簽名串*/
}

var privateKey = "5204627f8e58b3c75be408423c121988a1cb942e73470fb19186ef8986cd50b3";
var nonce = "9";
var currentAccount = "0xe558be4e90b2ac96ae5cad47dc39cd08316f2e57";
var contractAddress = "0xfa3118b34522580c35ae27f6cf52da1dbb756288";
var toAddress = "c6328b3a137b3be3f01c35ecda4ecda375be7fdf";
var gasPrice = 400000000;
var gasLimit = 200000;
var totalAmount = 8;
var decimal = 6;
var sign = ethereumErc20CoinSign(privateKey, nonce, currentAccount, contractAddress, toAddress, gasPrice, gasLimit, totalAmount, decimal);

console.log(sign);

簽名結果如下:

.:
.:

2.web3庫

web3是以太坊開發的重要庫,目前有web3.js、web3j(web3java)、web3.py(web3python)、hs-web3(haskell)、web3j-scala、purescript-web3、web3.php、ethereum-php;咱們此處主要介紹web3.js,其他的後面將用一個章節來進行介紹。web3.js是與以太坊相容的JavaScript API,它實現了通用JSON RPC規範。 它在npm上可用作節點模組,Bower和元件可用作可嵌入指令碼,也可用作meteor.js包。

2.1.web3.js庫的安裝

2.1.1.nodejs安裝
npm install web3
2.1.2.yarn安裝
yarn add web3
2.1.3.Meteor.js安裝
meteor add ethereum:web3
2.1.4.做為瀏覽器模組
  • CDN

  • Bower

    bower install web3

  • Component

    component install ethereum/web3.js

2.2.web3.js的使用

2.2.1.使用web3和以太坊互動

web3.js初始化設定Provider

var Web3 = require("web3");

if (typeof web3 !== 'undefined')
{
    web3 = new Web3(web3.currentProvider);
}
else
{
    web3 = new Web3(new Web3.providers.HttpProvider("http://10.23.1.209:8545"));
}
2.2.2.獲取賬戶餘額
web3.eth.getBalance("0x68db18a9cd87403c39e84467b332195b43fc33b5", function(err, result)
{
    if (err == null)
    {
        console.log('~balance Ether:' +web3.fromWei(result, "ether"));
    }
    else
    {
        console.log('~balance Ether:' + web3.fromWei(result, "ether"));
    }
});
2.2.3.獲取交易的nonce
web3.eth.getTransactionCount("0x68db18a9cd87403c39e84467b332195b43fc33b5", function (err, result)
{
    if (err == null)
    {
        console.log('nonce:' + result);
    }
    else
    {
        console.log('nonce:' + result);
    }
});
2.2.4.發起轉賬

轉賬需要使用到ethereumjs-tx庫,對交易進行簽名,關於ethereumjs-tx庫,請參閱上面的內容

var Tx = require('ethereumjs-tx');
var privateKey = new Buffer.from('0f8e7b1b99f49d1d94ac42084216a95fe5967caec6bba35c62c911f6c4eafa95', 'hex')

var rawTx = {
    subId:'0x0000000000000000000000000000000000',
    nonce: '0x1',
    gasPrice: '0x1a13b8600',
    gas: '0xf4240',
    to: '0x82aeb528664bb153d2114ae7ca4ef118ef1e7a98',
    value: '0x8ac7230489e80000',
    data:'0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
    //chainId:"10"
};

var tx = new Tx(rawTx);
tx.sign(privateKey);

var serializedTx = tx.serialize();

if (tx.verifySignature())
{
    console.log('Signature Checks out!')
}

web3.eth.sendRawTransaction('0x' + serializedTx.toString('hex'), function(err, hash) {
    if (!err)
    {
        console.log("hash:" + hash); // "0x7f9fade1c0d57a7af66ab4ead79fade1c0d57a7af66ab4ead7c2c2eb7b11a91385"
    }
    else
    {
        console.log(err);
    }
});

以上是Web3js的使用,web3是一個強大SDK,他的功能遠遠不止這些。後期的文章中還會用到web3.js,用到的時候我們再對相應的內容進行講解。

四.以太坊瀏覽器API介紹(錢包開發中需要用到其中一些API)

Etherscan的API地址:https://etherscan.io/apis

1.簡介

Etherscan的以太坊開發者API是社群毫無保留的提供的服務,你可以在上面使用你需要的介面。 這些介面支援的請求方式是GET/POST請求,並且每秒只能傳送5次請求。要使用API服務,請在ClientPortal-> MyApiKey區域內建立一個免費的Api-Key令牌,然後您可以將其用於所有api請求。

2.Etherscan賬戶體系API

  • 獲取單個地址的賬戶餘額

      https://api.etherscan.io/api?module=account&action=balance&address=0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a&tag=latest&apikey=YourApiKeyToken
    
  • 一次呼叫獲取多個地址的賬戶餘額

      https://api.etherscan.io/api?module=account&action=balancemulti&address=0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a,0x63a9975ba31b0b9626b34300f7f627147df1f526,0x198ef1ec325a96cc354c7266a038be8b5c558f67&tag=latest&apikey=YourApiKeyToken
    

用逗號分隔地址,一次最多可包含20個帳戶

  • 根據地址獲取賬戶交易的列表

引數:startblock:起始區塊,根據起始區塊去檢索結果;endblock:結束區塊,根據結束的區塊去檢索結果

    http://api.etherscan.io/api?module=account&action=txlist&address=0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a&startblock=0&endblock=99999999&sort=asc&apikey=YourApiKeyToken

([BETA]返回’isError’值:0 =無錯誤,1 =得到錯誤)(最多返回最後10000個交易)

或者

    https://api.etherscan.io/api?module=account&action=txlist&address=0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a&startblock=0&endblock=99999999&page=1&offset=10&sort=asc&apikey=YourApiKeyToken

(要獲取分頁結果,請使用page =<頁碼>和offset =<返回最大記錄數>)

  • 根據地址獲取合約交易列表

引數:startblock:起始區塊,根據起始區塊去檢索結果;endblock:結束區塊,根據結束的區塊去檢索結果

    http://api.etherscan.io/api?module=account&action=txlistinternal&address=0x2c1ba59d6f58433fb1eaee7d20b26ed83bda51a3&startblock=0&endblock=2702578&sort=asc&apikey=YourApiKeyToken

([BETA]返回’isError’值:0 =無錯誤,1 =得到錯誤)(最多返回最後10000個交易)

或者

    https://api.etherscan.io/api?module=account&action=txlistinternal&address=0x2c1ba59d6f58433fb1eaee7d20b26ed83bda51a3&startblock=0&endblock=2702578&page=1&offset=10&sort=asc&apikey=YourApiKeyToken

(要獲取分頁結果,請使用page =<頁碼>和offset =<返回最大記錄數>)

  • 根據交易Hash獲取內部交易

      https://api.etherscan.io/api?module=account&action=txlistinternal&txhash=0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170&apikey=YourApiKeyToken
    

返回’isError’值:0=OK,1 =拒絕/取消
僅返回最後10000個交易

  • 通過地址獲取“ERC20-Token轉移事件”列表

可選擇引數:startblock: 根據起始區塊號檢索結果, endblock:根據結束區塊檢索結果

    http://api.etherscan.io/api?module=account&action=tokentx&address=0x4e83362442b8d1bec281594cea3050c8eb01311c&startblock=0&endblock=999999999&sort=asc&apikey=YourApiKeyToken

僅返回最後10000個交易

或者

    https://api.etherscan.io/api?module=account&action=tokentx&contractaddress=0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2&page=1&offset=100&sort=asc&apikey=YourApiKeyToken

要獲得分頁結果,請使用page=和offset=<返回最大記錄>

或者

    https://api.etherscan.io/api?module=account&action=tokentx&contractaddress=0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2&address=0x4e83362442b8d1bec281594cea3050c8eb01311c&page=1&offset=100&sort=asc&apikey=YourApiKeyToken

要獲取特定Token合約的轉移事件,請包括合約地址引數

  • Get list of Blocks Mined by Address

  • 根據按地址獲取挖掘的塊列表

      https://api.etherscan.io/api?module=account&action=getminedblocks&address=0x9dd134d14d1e65f84b706d6f205cd5b1cd03a46b&blocktype=blocks&apikey=YourApiKeyToken
    

或者

    https://api.etherscan.io/api?module=account&action=getminedblocks&address=0x9dd134d14d1e65f84b706d6f205cd5b1cd03a46b&blocktype=blocks&page=1&offset=10&apikey=YourApiKeyToken

要獲取分頁結果,請使用page =<頁碼>和offset =<返回最大記錄數>

3.合約API

新驗證的合約將在5分鐘或更短的時間內同步到API伺服器

此處的程式碼將會涉及到web3.js呼叫智慧合約的程式碼,大家感興趣的話可以仔細看看

  • 獲取已驗證合約原始碼的合約ABI

      https://api.etherscan.io/api?module=contract&action=getabi&address=0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413&apikey=YourApiKeyToken
    

一個簡單的示例,用於使用Web3.js和Jquery檢索contractABI以與合同進行互動

    var Web3 = require('web3');
    var web3 = new Web3(new Web3.providers.HttpProvider());
    var version = web3.version.api;

    $.getJSON('http://api.etherscan.io/api?module=contract&action=getabi&address=0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359', function (data) {
        var contractABI = "";
        contractABI = JSON.parse(data.result);
        if (contractABI != ''){
            var MyContract = web3.eth.contract(contractABI);
            var myContractInstance = MyContract.at("0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359");
            var result = myContractInstance.memberId("0xfe8ad7dd2f564a877cc23feea6c0a9cc2e783715");
            console.log("result1 : " + result);            
            var result = myContractInstance.members(1);
            console.log("result2 : " + result);
        } else {
            console.log("Error" );
        }            
    });
  • 獲取已驗證合約原始碼的合約原始碼

      https://api.etherscan.io/api?module=contract&action=getsourcecode&address=0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413&apikey=YourApiKeyToken
    

[BETA]驗證原始碼

1.需要有效的Etherscan API金鑰,否則將拒絕
2.每位使用者每天提交的每日限額為100(可能會有變化)
3.由於http get的最大傳輸大小限制,僅支援HTTP post
4.最多支援10個不同的庫對
5.使用“匯入”的合同將需要將程式碼連線到一個檔案中,因為我們不支援單獨檔案中的“匯入”。 您可以嘗試使用Blockcat solidity-flattener或SolidityFlattery
6.支援的solc版本列表,僅支援solc版本v0.4.11及更高版本。防爆。v0.4.25+ commit.59dbf8f1
7.成功提交後,您將收到GUID(50個字元)作為收據。
8.您可以使用此GUID來跟蹤提交的狀態
9.已驗證的原始碼將顯示在contractsRerified中

原始碼提交Gist(成功時返回guid作為結果的一部分):

    $.ajax({
        type: "POST",                       //Only POST supported  
        url: "//api.etherscan.io/api", //Set to the  correct API url for Other Networks
        data: {
            apikey: $('#apikey').val(),                     //A valid API-Key is required        
            module: 'contract',                             //Do not change
            action: 'verifysourcecode',                     //Do not change
            contractaddress: $('#contractaddress').val(),   //Contract Address starts with 0x...     
            sourceCode: $('#sourceCode').val(),             //Contract Source Code (Flattened if necessary)
            contractname: $('#contractname').val(),         //ContractName
            compilerversion: $('#compilerversion').val(),   // see http://etherscan.io/solcversions for list of support versions
            optimizationUsed: $('#optimizationUsed').val(), //0 = Optimization used, 1 = No Optimization
            runs: 200,                                      //set to 200 as default unless otherwise         
            constructorArguements: $('#constructorArguements').val(),   //if applicable
            libraryname1: $('#libraryname1').val(),         //if applicable, a matching pair with libraryaddress1 required
            libraryaddress1: $('#libraryaddress1').val(),   //if applicable, a matching pair with libraryname1 required
            libraryname2: $('#libraryname2').val(),         //if applicable, matching pair required
            libraryaddress2: $('#libraryaddress2').val(),   //if applicable, matching pair required
            libraryname3: $('#libraryname3').val(),         //if applicable, matching pair required
            libraryaddress3: $('#libraryaddress3').val(),   //if applicable, matching pair required
            libraryname4: $('#libraryname4').val(),         //if applicable, matching pair required
            libraryaddress4: $('#libraryaddress4').val(),   //if applicable, matching pair required
            libraryname5: $('#libraryname5').val(),         //if applicable, matching pair required
            libraryaddress5: $('#libraryaddress5').val(),   //if applicable, matching pair required
            libraryname6: $('#libraryname6').val(),         //if applicable, matching pair required
            libraryaddress6: $('#libraryaddress6').val(),   //if applicable, matching pair required
            libraryname7: $('#libraryname7').val(),         //if applicable, matching pair required
            libraryaddress7: $('#libraryaddress7').val(),   //if applicable, matching pair required
            libraryname8: $('#libraryname8').val(),         //if applicable, matching pair required
            libraryaddress8: $('#libraryaddress8').val(),   //if applicable, matching pair required
            libraryname9: $('#libraryname9').val(),         //if applicable, matching pair required
            libraryaddress9: $('#libraryaddress9').val(),   //if applicable, matching pair required
            libraryname10: $('#libraryname10').val(),       //if applicable, matching pair required
            libraryaddress10: $('#libraryaddress10').val()  //if applicable, matching pair required
        },
        success: function (result) {
            console.log(result);
            if (result.status == "1") {
                //1 = submission success, use the guid returned (result.result) to check the status of your submission.
                // Average time of processing is 30-60 seconds
                document.getElementById("postresult").innerHTML = result.status + ";" + result.message + ";" + result.result;
                // result.result is the GUID receipt for the submission, you can use this guid for checking the verification status
            } else {
                //0 = error
                document.getElementById("postresult").innerHTML = result.status + ";" + result.message + ";" + result.result;
            }
            console.log("status : " + result.status);
            console.log("result : " + result.result);
        },
        error: function (result) {
            console.log("error!");
            document.getElementById("postresult").innerHTML = "Unexpected Error"
        }
    });

檢查原始碼驗證提交狀態:

    $.ajax({
        type: "GET",
        url: "//api.etherscan.io/api",
        data: {
            guid: 'ezq878u486pzijkvvmerl6a9mzwhv6sefgvqi5tkwceejc7tvn', //Replace with your Source Code GUID receipt above
            module: "contract",
            action: "checkverifystatus"
        },
        success: function (result) {
            console.log("status : " + result.status);   //0=Error, 1=Pass 
            console.log("message : " + result.message); //OK, NOTOK
            console.log("result : " + result.result);   //result explanation
            $('#guidstatus').html(">> " + result.result);
        },
        error: function (result) {
            alert('error');
        }
    });

4.交易API

  • [BETA]檢查合約執行狀態(如果合約執行期間出錯),注意:isError“:”0“= Pass,isError”:“1”=合約執行期間出錯

      https://api.etherscan.io/api?module=transaction&action=getstatus&txhash=0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a&apikey=YourApiKeyToken
    

[BETA]檢查交易背書狀態(僅適用於Post Byzantium fork交易),注意:狀態:0=失敗,1=通過。 將為pre-byzantium fork返回null/empty值

    https://api.etherscan.io/api?module=transaction&action=gettxreceiptstatus&txhash=0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76&apikey=YourApiKeyToken

5.區塊API

  • [BETA] 通過區塊號獲取區塊和叔區塊

      https://api.etherscan.io/api?module=block&action=getblockreward&blockno=2165403&apikey=YourApiKeyToken
    

6.事件日誌

7.Geth/Parity Proxy

以下是通過Etherscan提供的Geth支援的Proxied API的有限列表。 有關引數和說明的列表,請參閱https://github.com/ethereum/wiki/wiki/JSON-RPC。 提供的引數應如下面的示例中所示命名。 為了與Parity相容,請在所有十六進位制字串前加“0x”

  • eth_blockNumber:返回最近的區塊數量

      https://api.etherscan.io/api?module=proxy&action=eth_blockNumber&apikey=YourApiKeyToken
    
  • eth_getBlockByNumber:根據區塊號返回區塊資訊

      https://api.etherscan.io/api?module=proxy&action=eth_getBlockByNumber&tag=0x10d4f&boolean=true&apikey=YourApiKeyToken
    
  • eth_getUncleByBlockNumberAndIndex:根據區塊號返回一個叔區塊資訊

      https://api.etherscan.io/api?module=proxy&action=eth_getUncleByBlockNumberAndIndex&tag=0x210A9B&index=0x0&apikey=YourApiKeyToken
    
  • eth_getBlockTransactionCountByNumber:從匹配給定塊號的塊返回塊中的事務數

      https://api.etherscan.io/api?module=proxy&action=eth_getBlockTransactionCountByNumber&tag=0x10FB78&apikey=YourApiKeyToken
    
  • eth_getTransactionByHash:根據交易hash返回交易信心

      https://api.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1&apikey=YourApiKeyToken
    
  • eth_getTransactionByBlockNumberAndIndex:按塊號和事務索引位置返回有關事務的資訊

      https://api.etherscan.io/api?module=proxy&action=eth_getTransactionByBlockNumberAndIndex&tag=0x10d4f&index=0x0&apikey=YourApiKeyToken
    

eth_getTransactionCount:獲取當前地址的交易nonce,即交易數量

    https://api.etherscan.io/api?module=proxy&action=eth_getTransactionCount&address=0x2910543af39aba0cd09dbb2d50200b3e800a63d2&tag=latest&apikey=YourApiKeyToken
  • eth_sendRawTransaction:傳送一個簽名交易串到區塊鏈網路

      https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&hex=0xf904808000831cfde080&apikey=YourApiKeyToken
    

將十六進位制值替換為要傳送的原始十六進位制編碼事務。如果您的十六進位制程式碼特別長,則作為POST請求傳送

  • eth_getTransactionReceipt:通過交易Hash返回交易的背書

      https://api.etherscan.io/api?module=proxy&action=eth_getTransactionReceipt&txhash=0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1&apikey=YourApiKeyToken
    
  • eth_call:立即執行新的訊息呼叫,而不在塊鏈上建立交易

      https://api.etherscan.io/api?module=proxy&action=eth_call&to=0xAEEF46DB4855E25702F8237E8f403FddcaF931C0&data=0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724&tag=latest&apikey=YourApiKeyToken
    
  • eth_getCode:返回給定地址的程式碼

      https://api.etherscan.io/api?module=proxy&action=eth_getCode&address=0xf75e354c5edc8efed9b59ee9f67a80845ade7d0c&tag=latest&apikey=YourApiKeyToken
    
  • eth_getStorageAt (試驗) 返回給定地址的儲存位置的值。

      https://api.etherscan.io/api?module=proxy&action=eth_getStorageAt&address=0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd&position=0x0&tag=latest&apikey=YourApiKeyToken
    
  • eth_gasPrice:以wei為單位返回當前的gas的價格

      https://api.etherscan.io/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken
    
  • eth_estimateGas:進行呼叫或交易,不會將呼叫和交易的資訊新增到區塊鏈,並且能夠返回估計使用的gas,可用於估算使用的gas

      https://api.etherscan.io/api?module=proxy&action=eth_estimateGas&to=0xf0160428a8552ac9bb7e050d90eeade4ddd52843&value=0xff22&gasPrice=0x051da038cc&gas=0xffffff&apikey=YourApiKeyToken
    

8.代幣相關的API

  • 根據合約地址獲取ERC20 Token代幣總量

      https://api.etherscan.io/api?module=stats&action=tokensupply&contractaddress=0x57d90b64a1a57749b0f932f1a3395792e12e7055&apikey=YourApiKeyToken
    

廢棄API:根據合約名獲取代幣總量,這個API已經被廢棄,取而代之的是上一個API,根據合約地址獲取ERC20代幣總量

    https://api.etherscan.io/api?module=stats&action=tokensupply&tokenname=DGD&apikey=YourApiKeyToken
  • 根據Token的合約地址和賬戶地址獲取ERC20的代幣總量

      https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=0x57d90b64a1a57749b0f932f1a3395792e12e7055&address=0xe04f27eb70e025b78871a2ad7eabe85e61212761&tag=latest&apikey=YourApiKeyToken
    

廢棄API:根據Token名和賬戶地址獲取ERC20的代幣總量,這個API已經被廢棄,取而代之的是上面的這個API

    https://api.etherscan.io/api?module=account&action=tokenbalance&tokenname=DGD&address=0x4366ddc115d8cf213c564da36e64c8ebaa30cdbd&tag=latest&apikey=YourApiKeyToken

9.統計相關API

  • 獲得以太的總供應量,結果以Wei方式返回,以獲得以太坊數結果的值除以1000000000000000000的到最終的結果

      https://api.etherscan.io/api?module=stats&action=ethsupply&apikey=YourApiKeyToken
    
  • 獲得ETHER LastPrice價格

      https://api.etherscan.io/api?module=stats&action=ethprice&apikey=YourApiKeyToken
    

10.雜項、工具和實用程式

下面這些是由社群建立的第三方工具和實用程式

  • py-etherscan-api模組(corpetty),由Corey Petty編寫的第三方EtherScan.io API python繫結模組

      https://github.com/corpetty/py-etherscan-api
    
  • 節點API(SebastianSchürmann),Etherscan的第三方Node API

      https:/github.com/sebs/etherscan-api
    

五.以太坊JSON-RPC介面介紹(錢包開發過程中會用到其中的一些介面)

六.web3j(web3java)簡介

1.web3j概述

Web3j是一個通過java來開發以太坊DAPP的SDK,裡面封裝了很多API。web3j是一個輕量級,高度模組化,反應靈敏,型別安全的Java和Android庫,用於處理智慧合約並與以太坊網路上的客戶端(節點)整合:

.:
.:

這允許您使用以太坊區塊鏈,而無需為平臺編寫自己的整合程式碼的額外開銷,Java和Blockchain對話提供了區塊鏈,以太坊和web3j的概述。

3.web3j的特點

  • 通過HTTP和IPC完成以太坊的JSON-RPC客戶端API的實現
  • 以太坊錢包支援
  • 自動生成Java智慧合約包裝器,以便從本機Java程式碼建立,部署,交易和呼叫智慧合約(支援Solidity和Truffle定義格式)
  • 用於處理過濾器的反應功能API
  • 以太坊名稱服務(ENS)支援
  • 支援Parity的Personal和Geth的個人客戶端API
  • 支援Infura,因此您無需親自執行以太坊客戶端
  • 綜合整合測試展示了上述多種場景
  • 命令列工具
  • Android相容
  • 通過web3j-quorum支援摩根大通的法定人數

3.web3j執行時的依賴

  • RxJava用於其反應功能API
  • OKHttp用於HTTP連線
  • Jackson Core用於快速JSON序列化/反序列化
  • Bouncy Castle(Android上的Spongy Castle)用於加密
  • 適用於* nix IPC的Jnr-unixsocket(不適用於Android)

4.在專案中使用Web3j

以下專案針對的都是java8的庫

4.1.maven專案

<dependency>
  <groupId>org.web3j</groupId>
  <artifactId>core</artifactId>
  <version>4.0.0</version>
</dependency>

4.2.安卓專案

<dependency>
  <groupId>org.web3j</groupId>
  <artifactId>core</artifactId>
  <version>3.3.1-android</version>
</dependency>

4.3.gradle專案

compile (‘org.web3j:core:4.0.0’)

Android:
compile (‘org.web3j:core:3.3.1-android’)

4.4.web3j的簡單使用

在傳送請求前,你需要選擇一個geth客戶端

如果您還沒有執行,請啟動以太坊客戶端,例如Geth:

geth --rpcapi personal,db,eth,net,web3 --rpc --testnet

Parity:

parity --chain testnet

或者使用Infura,它提供在雲中執行的免費客戶端:

Web3j web3 = Web3j.build(new HttpService("https://ropsten.infura.io/your-token"));

傳送同步請求

Web3j web3 = Web3j.build(new HttpService());  // 預設 http://localhost:8545/
Web3ClientVersion web3ClientVersion = web3.web3ClientVersion().send();
String clientVersion = web3ClientVersion.getWeb3ClientVersion();

使用CompletableFuture(Android上的Future)傳送非同步請求:

Web3j web3 = Web3j.build(new HttpService());  // 預設 http://localhost:8545/
Web3ClientVersion web3ClientVersion = web3.web3ClientVersion().sendAsync().get();
String clientVersion = web3ClientVersion.getWeb3ClientVersion();

要使用RxJava Flowable:

Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
web3.web3ClientVersion().flowable().subscribe(x -> {
    String clientVersion = x.getWeb3ClientVersion();
    ...
});

5.web3的IPC機制

web3j還支援通過檔案套接字與執行在與web3j相同主機上的客戶端的快速程序間通訊(IPC)。 在建立服務時,只需使用相關的IpcService實現而不是HttpService:

  • Linux

      Web3j web3 = Web3j.build(new UnixIpcService("/path/to/socketfile"));
    
  • Windows

      Web3j web3 = Web3j.build(new WindowsIpcService("/path/to/namedpipefile"));
    

注意:IPC機制目前在web3j-android上不可使用

使用Java智慧合約“包裝器”處理智慧合約

web3j可以自動生成智慧合約包裝器程式碼,以便在不離開JVM的情況下部署智慧合約並與之互動。

下面命令是生成包裝器程式碼,編譯你的智慧合約

solc <contract>.sol --bin --abi --optimize -o <output-dir>/

然後使用web3j的命令列工具生成包裝器程式碼:

web3j solidity generate -b /path/to/<smart-contract>.bin -a /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name

現在你可以建立和部署你的智慧合約

Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
Credentials credentials = WalletUtils.loadCredentials("password", "/path/to/walletfile");

YourSmartContract contract = YourSmartContract.deploy(
        <web3j>, <credentials>,
        GAS_PRICE, GAS_LIMIT,
        <param1>, ..., <paramN>).send();  // constructor params

或者,如果您使用Truffle,則可以使用其.json輸出檔案:

# Inside your Truffle project
$ truffle compile
$ truffle deploy

然後使用web3j的命令列工具生成包裝器程式碼:

$ cd /path/to/your/web3j/java/project
$ web3j truffle generate /path/to/<truffle-smart-contract-output>.json -o /path/to/src/main/java -p com.your.organisation.name

無論是直接使用Truffle還是solc,無論哪種方式,您都可以獲得適合您合約的Java包裝器。

因此,要使用現有合約:

YourSmartContract contract = YourSmartContract.load(
        "0x<address>|<ensName>", <web3j>, <credentials>, GAS_PRICE, GAS_LIMIT);

與智慧合約進行交易:

TransactionReceipt transactionReceipt = contract.someMethod(
             <param1>,
             ...).send();

呼叫智慧合約

Type result = contract.someMethod(<param1>, ...).send();

控制gasprice價格

contract.setGasProvider(new DefaultGasProvider() {
        ...
        });

6.web3的過濾器

web3j功能反應性使得設定觀察者非常簡單,這些觀察者通知訂閱者在區塊鏈上發生的事件。

要在新增到區塊鏈時接收所有新塊:

Subscription subscription = web3j.blockFlowable(false).subscribe(block -> {
    ...
});

要在新增到區塊鏈時接收所有新交易:

Subscription subscription = web3j.transactionFlowable().subscribe(tx -> {
    ...
});

在提交給網路時(即在將它們一起分組到一個塊之前)接收所有待處理的事務:

Subscription subscription = web3j.pendingTransactionFlowable().subscribe(tx -> {
    ...
});

或者,如果您希望將所有塊重播到最新,並通知正在建立的新後續塊:咱們最後的附錄中(目前還沒有寫)描述了許多其他事務和塊重放Flowable。

支援主題過濾器:

EthFilter filter = new EthFilter(DefaultBlockParameterName.EARLIEST,
        DefaultBlockParameterName.LATEST, <contract-address>)
             .addSingleTopic(...)|.addOptionalTopics(..., ...)|...;
web3j.ethLogFlowable(filter).subscribe(log -> {
    ...
});   

不再需要時,應始終取消訂閱:

subscription.unsubscribe();

注意:Infura不支援過濾器。

7.交易

web3j支援使用以太坊錢包檔案(推薦)和乙太網客戶端管理命令傳送交易。

要使用以太坊錢包檔案將乙太網傳送給另一方:

Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
Credentials credentials = WalletUtils.loadCredentials("password", "/path/to/walletfile");
TransactionReceipt transactionReceipt = Transfer.sendFunds(
        web3, credentials, "0x<address>|<ensName>",
        BigDecimal.valueOf(1.0), Convert.Unit.ETHER)
        .send();

或者,如果您希望建立自己的自定義交易:

  • 構建httpService

      Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
      Credentials credentials = WalletUtils.loadCredentials("password", "/path/to/walletfile");
    
  • 獲取可用的nonce

      EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
                   address, DefaultBlockParameterName.LATEST).sendAsync().get();
      BigInteger nonce = ethGetTransactionCount.getTransactionCount();
    
  • 建立我們的交易

      RawTransaction rawTransaction  = RawTransaction.createEtherTransaction(
                   nonce, <gas price>, <gas limit>, <toAddress>, <value>);
    
  • 簽名併發送我們的交易

      byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
      String hexValue = Hex.toHexString(signedMessage);
      EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
    

雖然使用web3j的Transfer與Ether進行交易簡單得多。

使用以太坊客戶端的管理命令(確保您的錢包在客戶端的金鑰庫中):

Admin web3j = Admin.build(new HttpService());  //預設http://localhost:8545/
PersonalUnlockAccount personalUnlockAccount = web3j.personalUnlockAccount("0x000...", "a password").sendAsync().get();
if (personalUnlockAccount.accountUnlocked()) {
    //此處傳送交易
}

如果您想使用Parity的Personal或Trace或Geth的Personal客戶端API,您可以分別使用org.web3j:parity和org.web3j:geth模組。

8.命令列工具

伴隨每個版本一起釋出web3j fat jar,提供命令列工具。 你可以從命令列使用web3j的一些功能:

  • 錢包創作
  • 錢包密碼管理
  • 資金從一個錢包轉移到另一個錢包
  • 生成Solidity智慧合約函式包裝器

具體的資訊請看Web3j一章

七.使用keystore儲存地址和私鑰的方式開發錢包(不使用助記詞)

使用keystore方式開發以太坊錢包是可以不需要助記詞這一類的東西的,咱們將使用ethereumjs中的keythereum、ethereumjs-tx和web3js來開發錢包,webjs是和web3j類似的一個NodeJs庫。咱們此處的錢包開發只是把生成keystore,匯出匯入keystore,匯入匯出私鑰,交易簽名,傳送轉賬和轉賬確定的片段的nodeJS程式碼編寫出來,如何你想學習怎麼用keythereum、ethereumjs-tx和web3js開發一個以太坊錢包,請閱讀本書的專案實戰一。專案實戰一部分將詳細地講解一個以太坊錢包的設計,開發,測試和上線部署的整個流程。我們的這個錢包將實現的本地私鑰的儲存,私鑰將由錢包的客戶端進行管理。

1.keystore

下面的程式碼是按照keystore相關的程式碼,包含了生成keystore,匯出匯入keystore,匯入匯出私鑰。這是筆者基於NodeJs封裝的一個模組庫,如果你有需求,可以直接使用

const keythereum = require("keythereum");
const fs = require('fs');

var libKeystore = {};

const paramsErr = {code:1000, message:"input params is null"};
const createDkErr = {code:1001, message:"create dk error"};
const createKeystoreErr = {code:1002, message:"create keystore fail"};

/**
 * @param password
 * @returns {*}
 */
// 生成keystore
libKeystore.createKeystore = function (password) {
    if(!password) {
        return paramsErr;
    }
    var keystore = '';
    var params = { keyBytes: 32, ivBytes: 16 };
    var dk = keythereum.create(params);
    var kdf = "pbkdf2";
    var options = {
        kdf: "pbkdf2",
        cipher: "aes-128-ctr",
        kdfparams: {
            c: 262144,
            dklen: 32,
            prf: "hmac-sha256"
        }
    };
    var dk = keythereum.create(params)
    if (!dk) {
        return createDkErr;
    }
    keystore = keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options);
    if(!keystore) {
        return createKeystoreErr;
    }
    return keystore;
}

/**
 * @param keyObject
 * @param path if your path is null, export keystore by default way; if path has value, export keystore by your way
 * @returns {{code: number, message: string}}
 */
 // 匯出keystore
libKeystore.exportKeystore = function(keyObject, path) {
    if(!keyObject) {
        return paramsErr;
    }
    if(!path){
        keythereum.exportToFile(keyObject);
    } else {
        var json = JSON.stringify(keyObject);
        var outfile = keythereum.generateKeystoreFilename(keyObject.address);
        var outpath = path + "/" + outfile;
        console.log(outpath);
        fs.writeFile(outpath, json, function (err) {
            if (err) {
                return err;
            } else{
                outpath;
            }
        });
    }
}

/**
 * @param address
 * @param datadir
 * @returns {*}
 */
 // 匯入keystore
libKeystore.importKeystore = function(address, datadir) {
    if(!address || !datadir) {
        return paramsErr;
    }
    return keythereum.importFromFile(address, datadir);
}

/**
 * @param keyObject
 * @param password
 * @returns {*}
 */
 // 匯出私鑰
libKeystore.exportPrivateKey = function(keyObject, password) {
    if(!keyObject || !password) {
        return paramsErr;
    }
    return keythereum.recover(password, keyObject);
}

/**
 * @param privateKey
 * @param password
 * @returns {*}
 */
 // 匯入私鑰並生成keystore
libKeystore.importPrivateKey = function(privateKey ,password) {
    if(!password || privateKey) {
        return paramsErr;
    }
    var keystore = '';
    var params = { keyBytes: 32, ivBytes: 16 };
    var dk = keythereum.create(params);
    var kdf = "pbkdf2";
    var options = {
        kdf: "pbkdf2",
        cipher: "aes-128-ctr",
        kdfparams: {
            c: 262144,
            dklen: 32,
            prf: "hmac-sha256"
        }
    };
    var dk = keythereum.create(params)
    if (!dk) {
        return createDkErr;
    }
    keystore = keythereum.dump(password, privateKey, dk.salt, dk.iv, options);
    if(!keystore) {
        return createKeystoreErr;
    }
    return keystore;
}

module.exports = libKeystore;

4.交易簽名

以下程式碼是以太坊交易簽名的程式碼

const util = require('ethereumjs-util');
const transaction = require('ethereumjs-tx');

var ethOrErc20Sign = {};

/**
 * @param privateKey
 * @param nonce
 * @param toAddress
 * @param sendAmount
 * @param gasPrice
 * @param gasLimit
 * @returns {*}
 */
ethOrErc20Sign.ethereumSign = function (privateKey, nonce, toAddress, sendAmount, gasPrice, gasLimit) {
    var errData = {code:400, message:"param is null"};
    var serializedErr = {code:400, message:"Serialized transaction fail"};
    if(!privateKey || !nonce || !toAddress || !sendAmount || !gasPrice || !gasLimit) {
        console.log("one of fromAddress, toAddress, sendToBalance, sendFee is null, please give a valid param");
        return errData;
    } else {
        var transactionNonce = parseInt(nonce).toString(16);
        var numBalance = parseFloat(sendAmount);
        var balancetoWei = web3.toWei(numBalance, "ether");
        var oxNumBalance = parseInt(balancetoWei).toString(16);
        var gasPriceHex = parseInt(gasPrice).toString(16);
        var gasLimitHex = parseInt(gasLimit).toString(16);
        var privateKeyBuffer =  Buffer.from(privateKey, 'hex');
        var rawTx = {
            nonce:'0x' + transactionNonce,
            gasPrice: '0x' + gasPriceHex,
            gas:'0x'+ gasLimitHex,
            to:toAddress,
            value:'0x' + oxNumBalance,
        };
        alert(JSON.stringify(rawTx));
        var tx = new transaction(rawTx);
        tx.sign(privateKeyBuffer);
        var serializedTx = tx.serialize();
        if(serializedTx == null) {
            return serializedErr;
        } else {
            if (tx.verifySignature()) {
                console.log('Signature Checks out!');
            } else {
                return serializedErr;
            }
        }
    }
    return '0x' + serializedTx.toString('hex');
}

module.exports = ethOrErc20Sign;

解釋一下上面的程式碼,privateKey:是私鑰,你在生成賬戶的過程中生成的私鑰,私鑰是開啟你賬戶大門的鑰匙,請謹慎保管;nonce:交易nonce,是保證交易唯一性的標識;toAddress:轉入地址,你要轉給的那個使用者的賬