【ETH錢包開發(fā)03】web3j轉(zhuǎn)賬ETH

在之前的文章中抚岗,講解了創(chuàng)建或杠、導(dǎo)出、導(dǎo)入錢包宣蔚。
【ETH錢包開發(fā)01】創(chuàng)建向抢、導(dǎo)出錢包
【ETH錢包開發(fā)02】導(dǎo)入錢包

本文主要講解以太坊轉(zhuǎn)賬相關(guān)的一些知識(shí)。交易分為ETH轉(zhuǎn)賬和ERC-20 Token轉(zhuǎn)賬胚委,本篇先講一下ETH轉(zhuǎn)賬挟鸠。

發(fā)起交易的2種方式

1、解鎖賬戶發(fā)起交易亩冬。錢包keyStore文件保存在geth節(jié)點(diǎn)上艘希,用戶發(fā)起交易需要解鎖賬戶,適用于中心化的交易所硅急。

2覆享、錢包文件離線簽名發(fā)起交易。錢包keyStore文件保存在本地营袜,用戶使用密碼+keystore的方式做離線交易簽名來發(fā)起交易撒顿,適用于dapp,比如錢包荚板。

本文主要講一下第二種方式凤壁,也就是錢包離線簽名轉(zhuǎn)賬的方式。

錢包文件簽名的方式發(fā)起交易

交易流程
1跪另、通過keystore加載轉(zhuǎn)賬所需的憑證Credentials
2拧抖、創(chuàng)建一筆交易R(shí)awTransaction
3、使用Credentials對象對交易簽名
4免绿、發(fā)起交易

/**
     * 發(fā)起一筆交易(自定義參數(shù))
     *
     * @param from       發(fā)起人錢包地址
     * @param to         轉(zhuǎn)入的錢包地址
     * @param value      轉(zhuǎn)賬金額唧席,單位是wei
     * @param privateKey 錢包私鑰
     * @param gasPrice   轉(zhuǎn)賬費(fèi)用
     * @param gasLimit
     * @param data       備注的信息
     * @throws IOException
     * @throws CipherException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public EthSendTransaction transfer(String from,
                                       String to,
                                       BigInteger value,
                                       String privateKey,
                                       BigInteger gasPrice,
                                       BigInteger gasLimit,
                                       String data) throws IOException, CipherException, ExecutionException, InterruptedException {


        //加載轉(zhuǎn)賬所需的憑證,用私鑰
        Credentials credentials = Credentials.create(privateKey);
        //獲取nonce,交易筆數(shù)
        BigInteger nonce = getNonce(from);
        //創(chuàng)建RawTransaction交易對象
        RawTransaction rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, to, value);
        //簽名Transaction袱吆,這里要對交易做簽名
        byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        String hexValue = Numeric.toHexString(signMessage);
        //發(fā)送交易
        EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get();
        return ethSendTransaction;
    }
    /**
     * 獲取nonce,交易筆數(shù)
     *
     * @param from
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private BigInteger getNonce(String from) throws ExecutionException, InterruptedException {
        EthGetTransactionCount transactionCount = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.LATEST).sendAsync().get();
        BigInteger nonce = transactionCount.getTransactionCount();
        Log.i(TAG, "transfer nonce : " + nonce);
        return nonce;
    }

注意以下幾點(diǎn):

1距淫、Credentials
這里绞绒,我是通過獲取私鑰的方式來加載Credentials

//加載轉(zhuǎn)賬所需的憑證,用私鑰
Credentials credentials = Credentials.create(privateKey);

還有另外一種方式榕暇,通過密碼+錢包文件keystore方式來加載Credentials

 ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile);
 Credentials credentials = Credentials.create(ecKeyPair);

2蓬衡、nonce

nonce是指發(fā)起交易的賬戶下的交易筆數(shù),每一個(gè)賬戶nonce都是從0開始彤枢,當(dāng)nonce為0的交易處理完之后狰晚,才會(huì)處理nonce為1的交易,并依次加1的交易才會(huì)被處理缴啡。

可以通過eth_gettransactioncount 獲取nonce

3壁晒、gasPrice和gasLimit
交易手續(xù)費(fèi)由gasPrice 和gasLimit來決定,實(shí)際花費(fèi)的交易手續(xù)費(fèi)是gasUsed * gasPrice业栅。所有這兩個(gè)值你可以自定義秒咐,也可以使用系統(tǒng)參數(shù)獲取當(dāng)前兩個(gè)值

關(guān)于gas,你可以參考我之前的一篇文章碘裕。
以太坊(ETH)GAS詳解

gasPrice和gasLimit影響的是轉(zhuǎn)賬的速度携取,如果gas過低,礦工會(huì)最后才打包你的交易帮孔。在app中雷滋,通常給定一個(gè)默認(rèn)值,并且允許用戶自己選擇手續(xù)費(fèi)文兢。

如果不需要自定義的話晤斩,還有一種方式來獲取。獲取以太坊網(wǎng)絡(luò)最新一筆交易的gasPrice,轉(zhuǎn)賬的話姆坚,gasLimit一般設(shè)置為21000就可以了尸昧。

/**
     * 獲取當(dāng)前以太坊網(wǎng)絡(luò)中最近一筆交易的gasPrice
     */
    public BigInteger requestCurrentGasPrice() throws Exception {
        EthGasPrice ethGasPrice = web3j.ethGasPrice().sendAsync().get();
        return ethGasPrice.getGasPrice();
    }

Web3j還提供另外一種簡單的方式來轉(zhuǎn)賬以太幣,這種方式的好處是不需要管理nonce,不需要設(shè)置gasPrice和gasLimit旷偿,會(huì)自動(dòng)獲取最新一筆交易的gasPrice烹俗,gasLimit 為21000(轉(zhuǎn)賬一般設(shè)置成這個(gè)值就夠用了)。

    /**
     * 發(fā)起一筆交易
     * 使用以太錢包文件發(fā)送以太幣給其他人萍程,不能設(shè)置nonce:(推薦)
     *
     * @param privateKey
     */
    public String transfer(String toAddress, BigDecimal value, String privateKey) throws Exception {
        //轉(zhuǎn)賬者私鑰
        Credentials credentials = Credentials.create(privateKey);
        TransactionReceipt transactionReceipt = Transfer.sendFunds(
                web3j, credentials, toAddress,
                value, Convert.Unit.ETHER).sendAsync().get();
        String transactionHash = transactionReceipt.getTransactionHash();
        Log.i(TAG, "transfer: " + transactionHash);
        return transactionHash;
    }

如何驗(yàn)證交易是否成功幢妄?

這個(gè)問題,我想是很多朋友所關(guān)心的吧茫负。但是到目前為止蕉鸳,我還沒有看到有講解這方面的博客。

之前問過一些朋友,他們說可以通過區(qū)塊號潮尝、區(qū)塊哈希來判斷榕吼,也可以通過Receipt日志來判斷。但是經(jīng)過我的一番嘗試勉失,只有BlockHash是可行的羹蚣,在web3j中根據(jù)blocknumbertransactionReceipt都會(huì)報(bào)空指針異常。

原因大致是這樣的:在發(fā)起一筆交易之后乱凿,會(huì)返回txHash顽素,然后我們可以根據(jù)這個(gè)txHash去查詢這筆交易相關(guān)的信息。但是剛發(fā)起交易的時(shí)候徒蟆,由于手續(xù)費(fèi)問題或者以太網(wǎng)絡(luò)擁堵問題胁出,會(huì)導(dǎo)致你的這筆交易還沒有被礦工打包進(jìn)區(qū)塊,因此一開始是查不到的段审,通常需要幾十秒甚至更長的時(shí)間才能獲取到結(jié)果全蝶。我目前的解決方案是輪詢的去刷BlockHash,一開始的時(shí)候BlockHash的值為0x00000000000寺枉,等到打包成功的時(shí)候就不再是0了裸诽。

這里我使用的是rxjava的方式去輪詢刷的,5s刷新一次型凳。

    /**
     * 開啟輪詢
     * 根據(jù)txhash查詢交易是否被打包進(jìn)區(qū)塊
     *
     * @param txHash
     */
    public static void startPolling(String txHash) {
        //5s刷新一次
        disposable = Flowable.interval(0, 5, TimeUnit.SECONDS)
                .compose(ScheduleCompat.apply())
//                .takeUntil(Flowable.timer(120, TimeUnit.SECONDS))
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long num) throws Exception {
                        Log.i(TAG, "第 : " + num + " 次輪詢");
                        //根據(jù)blockHash來判斷交易是否被打包
                        String blockHash = getBlockHash(txHash);
                        boolean isSuccess = Numeric.toBigInt(blockHash).compareTo(BigInteger.valueOf(0)) != 0;
                        if (isSuccess) {
                            getTransactionReceipt(txHash);
                        }
                    }
                });
    }
    /**
     * 停止輪詢
     *
     * @param disposable
     */
    public static void stopPolling(Disposable disposable) {
        if (!disposable.isDisposed()) {
            disposable.dispose();
        }
    }
    /**
     * 獲取blockhash
     * @param txHash
     * @return
     */
public static String getBlockHash(String txHash) {
        Web3j web3j = Web3jFactory.build(new HttpService("https://rinkeby.infura.io/v3/xxxx"));
        try {
            EthTransaction transaction = web3j.ethGetTransactionByHash(txHash).sendAsync().get();
            Transaction result = transaction.getResult();
            String blockHash = result.getBlockHash();
            Log.i(TAG, "getTransactionResult blockHash : " + blockHash);
            boolean isSuccess = Numeric.toBigInt(blockHash).compareTo(BigInteger.valueOf(0)) != 0;
            if (isSuccess) {
                getTransactionReceipt(txHash);
                stopPolling(disposable);
            }
            return blockHash;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
public static void getTransactionReceipt(String transactionHash) {
        Web3j web3j = Web3jFactory.build(new HttpService("https://rinkeby.infura.io/v3/xxxx"));
        try {
            EthGetTransactionReceipt transactionReceipt = web3j.ethGetTransactionReceipt(transactionHash).sendAsync().get();
            TransactionReceipt receipt = transactionReceipt.getTransactionReceipt();
            String status = receipt.getStatus();
            BigInteger gasUsed = receipt.getGasUsed();
            BigInteger blockNumber = receipt.getBlockNumber();
            String blockHash = receipt.getBlockHash();
            Log.i(TAG, "getTransactionReceipt status : " + status);
            Log.i(TAG, "getTransactionReceipt gasUsed : " + gasUsed);
            Log.i(TAG, "getTransactionReceipt blockNumber : " + blockNumber);
            Log.i(TAG, "getTransactionReceipt blockHash : " + blockHash);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

正常情況下丈冬,幾十秒內(nèi)就可以獲取到區(qū)塊信息了。

區(qū)塊確認(rèn)數(shù)

區(qū)塊確認(rèn)數(shù)=當(dāng)前區(qū)塊高度-交易被打包時(shí)的區(qū)塊高度甘畅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末埂蕊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子疏唾,更是在濱河造成了極大的恐慌蓄氧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件槐脏,死亡現(xiàn)場離奇詭異喉童,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)顿天,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門堂氯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人牌废,你說我怎么就攤上這事咽白。” “怎么了鸟缕?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵晶框,是天一觀的道長排抬。 經(jīng)常有香客問我,道長授段,這世上最難降的妖魔是什么蹲蒲? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮侵贵,結(jié)果婚禮上届搁,老公的妹妹穿的比我還像新娘模燥。我一直安慰自己,他們只是感情好掩宜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布牺汤。 她就那樣靜靜地躺著檐迟,像睡著了一般追迟。 火紅的嫁衣襯著肌膚如雪瓶逃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天廓块,我揣著相機(jī)與錄音厢绝,去河邊找鬼。 笑死带猴,一個(gè)胖子當(dāng)著我的面吹牛昔汉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拴清,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼靶病,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了口予?” 一聲冷哼從身側(cè)響起嫡秕,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苹威,沒想到半個(gè)月后昆咽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年掷酗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了调违。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泻轰,死狀恐怖技肩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浮声,我是刑警寧澤虚婿,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站泳挥,受9級特大地震影響然痊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屉符,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一剧浸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧矗钟,春花似錦唆香、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至东涡,卻和暖如春虑凛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背软啼。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工桑谍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祸挪。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓锣披,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贿条。 傳聞我的和親對象是個(gè)殘疾皇子淋纲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容