錢包開發經驗分享:ETH篇


錢包開發經驗分享:ETH篇

 

開發前的准備

工欲善其事,必先利其器

一路開發過來,積累了一些錢包的開發利器和網站,與大家分享一下。這些東西在這行開發過的人都知道,只是給行外打算入行的人做個參考。

  • 最好用的ETH錢包--MetaMask

    下載:MetaMask(谷歌插件)

    簡介:這是一款以太坊瀏覽器插件,他可以很方便的查看或操作以太坊、erc20代幣余額,也方便配合remix之類的合約IDE來部署合約,支持自定義代幣,支持多種測試網絡和正式網絡以及自定義網絡節點。總而言之,這是一款十分便利好用的錢包。

  • 最官方的區塊鏈瀏覽器--etherscan

    網址:以太坊官方區塊鏈瀏覽器

    簡介:這是以太坊最最最官方的區塊鏈瀏覽器了,對於開發者而言,它不僅僅只是查詢區塊交易那么簡單,他還有更多有利於程序員開發的功能。它提供了眾多api和小工具,它是所有測試網絡的父域名,可以輕松地切換查看到所有測試網絡的區塊和交易,在部署合約時,它又協助你發布合約,因此對於開發者而言,這是一個不可缺少的網站。

  • 獲取測試幣的網站--rinkeby、ropsten

    網址:rinkebyropsten

    簡介:以太坊有很多共享的測試網絡,這篇博文介紹了各個網絡的區別和其區塊鏈瀏覽器,其中開發者主要使用的區塊鏈瀏覽器不外乎rinkeby和ropsten,上述兩個網址則是這兩種測試幣的水龍頭網站,獲取測試幣的教程如下:獲取rinkeby測試幣獲取ropsten測試幣

  • 免費的第三方節點接入--王站

    網址:infura

    簡介:對於ETH錢包開發而言,這是個不可或缺的網站,當然,可能也有其他第三方節點免費對用戶開放,不過我一直用的是這個網站。這個網站的作用是,我們不用搭建ETH節點也可以正常地進行ETH的開發,我們只需要動動手指注冊一個賬戶,創建我們的項目,就能拿到一個免費接入的ETH節點,而且他還包括了所有流行的測試網絡。而我之所以稱之為王站,是因為它的網站圖標類似一個王字。

  • 最便捷的以太坊IDE--remix

    網址:remix

    簡介:對於ETH錢包開發而言,合約開發和部署或許是必不可少的一部分,為什么我會這樣說?那是因為在錢包開發中,總會需要對接各種erc20的代幣,而我們雖然能夠在獲得ETH的測試幣,但是其他的代幣的測試幣我們是很難獲得的(或者說根本無法獲得),而基於erc20協議的代幣代碼是通用的,所以接入代幣錢包的時候,我們往往是考慮自己在測試網絡部署一份erc20協議的合約,並自己鑄幣,以方便進行后續的開發,而結合remix和MetaMask來部署合約,那就是幾個步驟的事情。部署合約的流程可以參考這篇教程

ETH錢包代碼參考

真正的知識就在經驗中

生成錢包地址、公私鑰和助記詞/通過助記詞恢復錢包地址、公私鑰

  • 導入依賴

    <dependency>
    <groupId>org.bitcoinj</groupId>
    <artifactId>bitcoinj-core</artifactId>
    <version>0.14.7</version>
    </dependency>

    <dependency>
    <groupId>org.web3j</groupId>
    <artifactId>core</artifactId>
    <version>4.5.5</version>
    </dependency>
  • 初始化web3j

    private final static Web3j web3j = Web3j.build(new HttpService("https://mainnet.infura.io/v3/你自己從infura申請的id"));

     

  • 參考代碼

     public static Map<String, Object> ethWalletGenerate(String mnemonic, String mnemonicPath, String passWord) {

    try {
    DeterministicSeed deterministicSeed = null;
    List<String> mnemonicArray = null;

    if (null == mnemonic || 0 == mnemonic.length()) {
    deterministicSeed = new DeterministicSeed(new SecureRandom(), 128, "", System.currentTimeMillis() / 1000);
    mnemonicArray = deterministicSeed.getMnemonicCode();// 助記詞
    } else {
    deterministicSeed = new DeterministicSeed(mnemonic, null, "", System.currentTimeMillis() / 1000);
    }

    byte[] seedBytes = deterministicSeed.getSeedBytes();// 種子
    if (null == seedBytes) {
    logger.error("生成錢包失敗");
    return null;
    }

    //種子對象
    DeterministicKey deterministicKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);

    String[] pathArray = mnemonicPath.split("/");// 助記詞路徑
    for (int i = 1; i < pathArray.length; i++) {
    ChildNumber childNumber;
    if (pathArray[i].endsWith("'")) {
    int number = Integer.parseInt(pathArray[i].substring(0, pathArray[i].length() - 1));
    childNumber = new ChildNumber(number, true);
    } else {
    int number = Integer.parseInt(pathArray[i]);
    childNumber = new ChildNumber(number, false);
    }
    deterministicKey = HDKeyDerivation.deriveChildKey(deterministicKey, childNumber);
    }

    ECKeyPair eCKeyPair = ECKeyPair.create(deterministicKey.getPrivKeyBytes());
    WalletFile walletFile = Wallet.createStandard(passWord, eCKeyPair);
    if (null == mnemonic || 0 == mnemonic.length()) {
    StringBuilder mnemonicCode = new StringBuilder();
    for (int i = 0; i < mnemonicArray.size(); i++) {
    mnemonicCode.append(mnemonicArray.get(i)).append(" ");
    }
    return new HashMap<String, Object>() {
    private static final long serialVersionUID = -4960785990664709623L;
    {
    put("walletFile", walletFile);
    put("eCKeyPair", eCKeyPair);
    put("mnemonic", mnemonicCode.substring(0, mnemonicCode.length() - 1));
    }
    };
    } else {
    return new HashMap<String, Object>() {
    private static final long serialVersionUID = -947886783923530545L;
    {
    put("walletFile", walletFile);
    put("eCKeyPair", eCKeyPair);
    }
    };
    }
    } catch (CipherException e) {
    return null;
    } catch (UnreadableWalletException e) {
    return null;
    }
    }

    其中關於助記詞路徑(mnemonicPath)的解釋請參考這篇文章:關於錢包助記詞。erc20代幣的錢包地址和ETH的錢包地址是通用的,所以這套代碼可以用於生成ETH錢包地址,也可以用於生成erc20錢包地址。

  • 測試代碼

     /**
    * 生成錢包地址、公私鑰、助記詞
    */
    @Test
    public void testGenerateEthWallet(){
    Map<String, Object> wallet = AddrUtil.ethWalletGenerate(null, ETH_MNEMONI_PATH, "123456");
    WalletFile walletFile = (WalletFile) wallet.get("walletFile");
    String address = walletFile.getAddress();
    ECKeyPair eCKeyPair = (ECKeyPair) wallet.get("eCKeyPair");
    String privateKey = eCKeyPair.getPrivateKey().toString(16);
    String publicKey = eCKeyPair.getPublicKey().toString(16);
    String mnemonic = (String) wallet.get("mnemonic");
    logger.warn("address: {}, privateKey: {}, publicKey: {}, mnemonic: {}", address, privateKey, publicKey, mnemonic);
    }

    /**
    * 通過助記詞恢復錢包地址、公私鑰
    */
    @Test
    public void testGenerateEthWalletByMnemonic(){
    Map<String, Object> wallet = AddrUtil.ethWalletGenerate("clown cat senior keep problem engine degree modify ritual machine syrup company", ETH_MNEMONI_PATH, "123456");
    WalletFile walletFile = (WalletFile) wallet.get("walletFile");
    String address = walletFile.getAddress();
    ECKeyPair eCKeyPair = (ECKeyPair) wallet.get("eCKeyPair");
    String privateKey = eCKeyPair.getPrivateKey().toString(16);
    String publicKey = eCKeyPair.getPublicKey().toString(16);
    String mnemonic = (String) wallet.get("mnemonic");
    logger.warn("address: {}, privateKey: {}, publicKey: {}, mnemonic: {}", address, privateKey, publicKey, mnemonic);
    }

進一步,我們或許希望能夠從一個唯一的密鑰或者助記詞去推導出交易所所有的錢包地址和密鑰,可以參考這面這套代碼:

  • 參考代碼

        /**
        * 通過助記詞和id生成對應的子賬戶
        *
        * @param mnemonic 助記詞
        * @param id       派生子id
        * @return 子賬戶key
        */
       private static DeterministicKey generateKeyFromMnemonicAndUid(String mnemonic, int id) {
           byte[] seed = MnemonicUtils.generateSeed(mnemonic, "");

           DeterministicKey rootKey = HDKeyDerivation.createMasterPrivateKey(seed);
           DeterministicHierarchy hierarchy = new DeterministicHierarchy(rootKey);

           return hierarchy.deriveChild(BIP44_ETH_ACCOUNT_ZERO_PATH, false, true, new ChildNumber(id, false));
      }

       /**
        * 生成地址
        *
        * @param id 用戶id
        * @return 地址
        */
       public static String getEthAddress(String mnemonic, int id) {
           DeterministicKey deterministicKey = generateKeyFromMnemonicAndUid(mnemonic, id);
           ECKeyPair ecKeyPair = ECKeyPair.create(deterministicKey.getPrivKey());
           return Keys.getAddress(ecKeyPair);
      }

       /**
        * 生成私鑰
        *
        * @param id 用戶id
        * @return 私鑰
        */
       public static BigInteger getPrivateKey(String mnemonic, int id) {
           return generateKeyFromMnemonicAndUid(mnemonic, id).getPrivKey();
      }
  • 測試代碼

     /**
    * 通過助記詞和用戶id生成錢包地址和私鑰
    */
    @Test
    public void testGenerateEthChildWallet(){
    String ethAddress = EthUtil.getEthAddress("clown cat senior keep problem engine degree modify ritual machine syrup company", 1);
    BigInteger privateKey = EthUtil.getPrivateKey("clown cat senior keep problem engine degree modify ritual machine syrup company", 1);
    logger.warn("address: {}, privateKey: {}", ethAddress, privateKey);
    }

獲取余額/獲取代幣余額

  • 參考代碼

        /**
        * 獲取eth余額
        *
        * @param address 傳入查詢的地址
        * @return String 余額
        * @throws IOException
        */
       public static String getEthBalance(String address) {
           EthGetBalance ethGetBlance = null;
           try {
               ethGetBlance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
          } catch (IOException e) {
               logger.error("【獲取ETH余額失敗】 錯誤信息: {}", e.getMessage());
          }
           // 格式轉換 WEI(幣種單位) --> ETHER
           String balance = Convert.fromWei(new BigDecimal(ethGetBlance.getBalance()), Convert.Unit.ETHER).toPlainString();
           return balance;
      }
  • 測試代碼

     /**
    * 獲取ETH余額
    */
    @Test
    public void testGetETHBalance(){
    String balance = EthUtil.getEthBalance("0x09f20ff67db2c5fabeb9a2c8dd5f6b4afab7887b");
    logger.warn("balance: {}", balance);
    }
  • 參考代碼

        /**
         * 獲取賬戶代幣余額
         *
         * @param account     賬戶地址
         * @param coinAddress 合約地址
         * @return 代幣余額 (單位:代幣最小單位)
         * @throws IOException
         */
        public static String getTokenBalance(String account, String coinAddress) {
            Function balanceOf = new Function("balanceOf",
                    Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(account)),
                    Arrays.<TypeReference<?>>asList(new TypeReference<Uint256>() {
                    }));
    
            if (coinAddress == null) {
                return null;
            }
            String value = null;
            try {
                value = web3j.ethCall(Transaction.createEthCallTransaction(account, coinAddress, FunctionEncoder.encode(balanceOf)), DefaultBlockParameterName.PENDING).send().getValue();
            } catch (IOException e) {
                logger.error("【獲取合約代幣余額失敗】 錯誤信息: {}", e.getMessage());
                return null;
            }
            int decimal = getTokenDecimal(coinAddress);
            BigDecimal balance = new BigDecimal(new BigInteger(value.substring(2), 16).toString(10)).divide(BigDecimal.valueOf(Math.pow(10, decimal)));
            return balance.toPlainString();
        }
  • 測試代碼

    	/**
    	 * 獲取代幣余額
    	 */
    	@Test
    	public void testGetTokenBalance(){
    		String usdtBalance = EthUtil.getTokenBalance("0x09f20ff67db2c5fabeb9a2c8dd5f6b4afab7887b", "0xdac17f958d2ee523a2206206994597c13d831ec7");
    		logger.warn("usdtBalance: {}", usdtBalance);
    	}

    ETH的地址分為兩種,一種為普通的用戶地址,另一種則是合約地址,所有代幣類型的轉賬都是向合約地址發起轉賬,在輸入中輸入實際入賬的信息(地址和數量),各種代幣的合約地址可以查閱以太坊最最最官方的區塊瀏覽器。上面參考代碼中獲取代幣精度的代碼可以繼續參考下面的代碼。

獲取代幣名稱、精度和符號

  • 參考代碼

        /**
         * 查詢代幣符號
         */
        public static String getTokenSymbol(String contractAddress) {
            String methodName = "symbol";
            List<Type> inputParameters = new ArrayList<>();
            List<TypeReference<?>> outputParameters = new ArrayList<>();
    
            TypeReference<Utf8String> typeReference = new TypeReference<Utf8String>() {
            };
            outputParameters.add(typeReference);
    
            Function function = new Function(methodName, inputParameters, outputParameters);
    
            String data = FunctionEncoder.encode(function);
            Transaction transaction = Transaction.createEthCallTransaction("0x0000000000000000000000000000000000000000", contractAddress, data);
    
            EthCall ethCall = null;
            try {
                ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
            } catch (InterruptedException e) {
                logger.error("獲取代幣符號失敗");
                e.printStackTrace();
            } catch (ExecutionException e) {
                logger.error("獲取代幣符號失敗");
                e.printStackTrace();
            }
            List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            if (null == results || 0 == results.size()) {
                return "";
            }
            return results.get(0).getValue().toString();
        }
    
        /**
         * 查詢代幣名稱
         */
        public static String getTokenName(String contractAddr) {
            String methodName = "name";
            List<Type> inputParameters = new ArrayList<>();
            List<TypeReference<?>> outputParameters = new ArrayList<>();
    
            TypeReference<Utf8String> typeReference = new TypeReference<Utf8String>() {
            };
            outputParameters.add(typeReference);
    
            Function function = new Function(methodName, inputParameters, outputParameters);
    
            String data = FunctionEncoder.encode(function);
            Transaction transaction = Transaction.createEthCallTransaction("0x0000000000000000000000000000000000000000", contractAddr, data);
    
            EthCall ethCall = null;
            try {
                ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
            } catch (InterruptedException e) {
                logger.error("獲取代幣名稱失敗");
                e.printStackTrace();
            } catch (ExecutionException e) {
                logger.error("獲取代幣名稱失敗");
                e.printStackTrace();
            }
            List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            if (null == results || results.size() <= 0) {
                return "";
            }
            return results.get(0).getValue().toString();
        }
    
        /**
         * 查詢代幣精度
         */
        public static int getTokenDecimal(String contractAddr) {
            String methodName = "decimals";
            List<Type> inputParameters = new ArrayList<>();
            List<TypeReference<?>> outputParameters = new ArrayList<>();
    
            TypeReference<Uint8> typeReference = new TypeReference<Uint8>() {
            };
            outputParameters.add(typeReference);
    
            Function function = new Function(methodName, inputParameters, outputParameters);
    
            String data = FunctionEncoder.encode(function);
            Transaction transaction = Transaction.createEthCallTransaction("0x0000000000000000000000000000000000000000", contractAddr, data);
    
            EthCall ethCall = null;
            try {
                ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
            } catch (InterruptedException e) {
                logger.error("獲取代幣精度失敗");
                e.printStackTrace();
            } catch (ExecutionException e) {
                logger.error("獲取代幣精度失敗");
                e.printStackTrace();
            }
            List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            if (null == results || 0 == results.size()) {
                return 0;
            }
            return Integer.parseInt(results.get(0).getValue().toString());
        }
  • 測試代碼

    	/**
    	 * 獲取代幣名稱、符號和精度
    	 */
    	@Test
    	public void testGetTokenInfo(){
    		String usdtContractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7";
    		String tokenName = EthUtil.getTokenName(usdtContractAddress);
    		String tokenSymbol = EthUtil.getTokenSymbol(usdtContractAddress);
    		int tokenDecimal = EthUtil.getTokenDecimal(usdtContractAddress);
    		logger.warn("name: {}, symbol: {}, decimal: {}", tokenName, tokenSymbol, tokenDecimal);
    	}

獲取交易

  • 參考代碼

        /**
         * 根據區塊高度獲取區塊交易
         * @param height 區塊高度
         * @return
         */
        public static List<Transaction> getTxByHeight(BigInteger height) {
            List<Transaction> transactions = new ArrayList<>();
            try {
                EthBlock.Block block = web3j.ethGetBlockByNumber(DefaultBlockParameter.valueOf(height), false).send().getBlock();
                for (EthBlock.TransactionResult transactionResult : block.getTransactions()) {
                    Transaction transaction = web3j.ethGetTransactionByHash((String) transactionResult.get()).send().getTransaction().get();
                    transactions.add(transaction);
                }
                logger.info("【獲取交易數據成功】 區塊哈希: {}, 區塊高度: {}", block.getHash(), block.getNumber());
            } catch (IOException e) {
                logger.error("【獲取交易數據失敗】 錯誤信息: {}", e.getMessage());
                return null;
            }
            return transactions;
        }
    
    		/**
         * 根據txid獲取交易信息
         * @param txid 交易哈希
         * @return
         */
        public static Transaction getTxByTxid(String txid) {
            Transaction transaction = null;
            try {
                transaction = web3j.ethGetTransactionByHash(txid).send().getTransaction().orElse(null);
                logger.info("【獲取交易信息成功】 {} : {}", txid, new Gson().toJson(transaction));
            } catch (IOException e) {
                logger.info("【獲取交易信息失敗】 交易哈希: {}, 錯誤信息: {}", txid, e.getMessage());
                return null;
            }
            return transaction;
        }
    
        /**
         * 解析代幣交易
         * @param transaction 交易對象
         * @return
         */
        public static Map<String, Object> getTokenTxInfo(Transaction transaction){
            Map<String, Object> result = new HashMap<>();
            String input = transaction.getInput();
            if(!Erc20Util.isTransferFunc(input)) {
                 return null;
            }
            result.put("to", Erc20Util.getToAddress(input));
            result.put("amount", Erc20Util.getTransferValue(input).divide(BigDecimal.valueOf(Math.pow(10, getTokenDecimal(transaction.getTo())))));
            result.put("txid", transaction.getHash());
            result.put("from", transaction.getFrom());
            result.put("height", transaction.getBlockNumber());
            result.put("txFee", Convert.fromWei(transaction.getGasPrice().multiply(transaction.getGas()).toString(10), Convert.Unit.ETHER));
            result.put("gas", transaction.getGas());
            result.put("gasPrice", transaction.getGasPrice());
            return result;
        }
    
        /**
         * 解析ETH交易
         * @param transaction 交易對象
         * @return
         */
        public static Map<String, Object> getEthTxInfo(Transaction transaction){
            Map<String, Object> result = new HashMap<>();
            result.put("to", transaction.getTo());
            result.put("amount", Convert.fromWei(transaction.getValue().toString(10), Convert.Unit.ETHER));
            result.put("txid", transaction.getHash());
            result.put("from", transaction.getFrom());
            result.put("height", transaction.getBlockNumber());
            result.put("txFee", Convert.fromWei(transaction.getGasPrice().multiply(transaction.getGas()).toString(10), Convert.Unit.ETHER));
            result.put("gas", transaction.getGas());
            result.put("gasPrice", transaction.getGasPrice());
            return result;
        }
  • 測試代碼

    	/**
    	 * 根據txid獲取ETH/代幣交易信息
    	 */
    	@Test
    	public void testGetTransactionByTxid(){
    		Transaction ethTx = EthUtil.getTxByTxid("0xd05798408be19ec0adc5e0a7397b4e9d294b8e136eacc1eb606be45533eb97f1");
    		Map<String, Object> ethTxInfo = EthUtil.getEthTxInfo(ethTx);
    
    		Transaction usdtTx = EthUtil.getTxByTxid("0xd5443fad2feafd309f28d86d39af2e3f112b1ca1b8cdce8a2b6b9cdcdef5ad59");
    		Map<String, Object> usdtTxInfo = EthUtil.getTokenTxInfo(usdtTx);
    
    		logger.warn("txInfo: {}, usdtTxInfo: {}", new Gson().toJson(ethTxInfo), new Gson().toJson(usdtTxInfo));
    	}
    
    	/**
    	 * 根據區塊高度獲取交易
    	 */
    	@Test
    	public void testGetTransactionByBlockHeight(){
    		List<Transaction> transactions = EthUtil.getTxByHeight(new BigInteger("9159698"));
    		logger.warn("txCount: {}", transactions.size());
    	}

ETH/代幣離線簽名轉賬

  • 參考代碼

        /**
         * 發送eth離線交易
         *
         * @param from        eth持有地址
         * @param to          發送目標地址
         * @param amount      金額(單位:eth)
         * @param credentials 秘鑰對象
         * @return 交易hash
         */
        public static String sendEthTx(String from, String to, BigInteger gasLimit, BigInteger gasPrice, BigDecimal amount, Credentials credentials) {
    
            try {
                BigInteger nonce = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.PENDING).send().getTransactionCount();
    
                BigInteger amountWei = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
    
                RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, amountWei, "");
    
                byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
    
                return web3j.ethSendRawTransaction(Numeric.toHexString(signMessage)).sendAsync().get().getTransactionHash();
            }catch (Exception e) {
                logger.error("【ETH離線轉賬失敗】 錯誤信息: {}", e.getMessage());
                return null;
            }
        }
    
        /**
         * 發送代幣離線交易
         *
         * @param from        代幣持有地址
         * @param to          代幣目標地址
         * @param value      金額(單位:代幣最小單位)
         * @param coinAddress 代幣合約地址
         * @param credentials 秘鑰對象
         * @return 交易hash
         */
        public static String sendTokenTx(String from, String to, BigInteger gasLimit, BigInteger gasPrice, BigInteger value, String coinAddress, Credentials credentials) {
    
            try {
                BigInteger nonce = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.PENDING).send().getTransactionCount();
    
                Function function = new Function(
                        "transfer",
                        Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(to),
                                new org.web3j.abi.datatypes.generated.Uint256(value)),
                        Collections.<TypeReference<?>>emptyList());
                String data = FunctionEncoder.encode(function);
    
                RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, coinAddress, data);
                byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
    
                return web3j.ethSendRawTransaction(Numeric.toHexString(signMessage)).sendAsync().get().getTransactionHash();
            }catch (Exception e) {
                logger.error("【代幣離線轉賬失敗】 錯誤信息: {}", e.getMessage());
                return null;
            }
        }
  • 測試代碼

    	/**
    	 * 測試ETH轉賬
    	 */
    	@Test
    	public void testETHTransfer() throws Exception{
    		String from = "0xB7Cd09d73a1719b90469Edf7Aa1942d8f89Ba21f";
    		String to = "0xF0B8412C211261B68bc797f31F642Aa14fbDC007";
    		String privateKey = "密鑰不可見";
    		BigDecimal value = Convert.toWei("1", Convert.Unit.WEI);
    		BigInteger gasPrice = EthUtil.web3j.ethGasPrice().send().getGasPrice();
    		BigInteger gasLimit = EthUtil.web3j.ethEstimateGas(new Transaction(from, null, null, null, to, value.toBigInteger(), null)).send().getAmountUsed();
    		String txid = EthUtil.sendEthTx(from, to, gasLimit, gasPrice, value, Credentials.create(privateKey));
    		logger.warn("txid: {}", txid);
    	}
    
    	/**
    	 * 測試代幣轉賬
    	 */
    	@Test
    	public void testTokenTransfer() throws Exception{
    		String from = "0xB7Cd09d73a1719b90469Edf7Aa1942d8f89Ba21f";
    		String to = "0xF0B8412C211261B68bc797f31F642Aa14fbDC007";
    		String contractAddress = "0x6a26797a73f558a09a47d2dd56fbe03227a31dbb";
    		String privateKey = "密鑰不可見";
    		BigInteger value = BigDecimal.valueOf(Math.pow(10, EthUtil.getTokenDecimal(contractAddress))).toBigInteger();
    		BigInteger gasPrice = EthUtil.web3j.ethGasPrice().send().getGasPrice();
    		BigInteger gasLimit = EthUtil.getTransactionGasLimit(from, to, contractAddress, value);
    		String txid = EthUtil.sendTokenTx(from, to, gasLimit, gasPrice, gasLimit, contractAddress, Credentials.create(privateKey));
    		logger.warn("txid: {}", txid);
    	}

    代碼里的合約地址是我在rinkeby測試網絡發布的一個測試幣:TestCoin Token(TCT),不想自己部署合約的同學可以關注我的公眾號,發錢包地址給我,我會發一些測試幣到你的錢包地址。在上面的代碼里,比較玩味的是關於nonce值的管理,關於nonce值的解釋可以參考這篇文章。在上面的代碼里,nonce值我們是通過RPC接口直接獲取,這樣的操作是相對簡單但是卻耗時最長,因為調用RPC存在網絡上的開銷。比較成熟的處理方式是在交易信息表中維護一個nonce字段,這樣做一方面是發起一筆新的交易的時候可以更快的獲取nonce值,另一方面,當交易發生錯誤(發起了一筆金額錯誤的交易)的時候,可以及時進行修改,因為以太坊的設計是:你可以發起一筆與之前nonce值一樣的交易,去覆蓋處於pending狀態的交易。

    另外,關於礦工費的計算,正常的以太坊交易是礦工費=gasPrice*gasLimit,gasPrice是每一步計算消耗的費用,gasLimit是允許接受的最大計算次數,它們的單位是WEI,關於以太坊的單位,請參考這篇文章。而當你使用這個計算公式來計算代幣的手續費時就不怎么精確了,因為代幣交易消耗的gas並不是百分百消耗完的,在區塊鏈瀏覽器交易頁面,你能看到,每一筆代幣交易都有一個gas使用率,而由於每種代幣輸入的腳本大小不同,因而無法確定gas的使用率。目前為止,我還沒找到一個方法可以去精確計算代幣的手續費。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM