[TOC]
Preparation before development
Sharp tools make good work
All the way through the development, I have accumulated a number of wallet development tools and websites to share with you. Those who have developed these things in this industry know that they are just for reference to those who intend to enter the industry.
-
The best ETH wallet -- MetaMask
Download: MetaMask (Google plugin)
Introduction: This is an Ethereum browser plug-in. It is very convenient to view or operate the balance of Ethereum and erc20 tokens. It is also convenient to cooperate with contract ides such as remix to deploy contracts, support custom tokens, support multiple test networks, formal networks and custom network nodes. All in all, this is a very convenient and easy-to-use wallet.
-
The most official blockchain browser -- etherscan
Website: Official blockchain browser of Ethereum
Brief introduction: This is the most official blockchain browser of Ethereum. For developers, it is not only as simple as querying block transactions, but also more functions that are beneficial to programmers. It provides many APIs and gadgets. It is the parent domain name of all test networks. It can easily switch and view the blocks and transactions of all test networks. When deploying contracts, it also helps you publish contracts. Therefore, it is an indispensable website for developers.
-
Website for obtaining test currency -- rinkeby, ropsten
Introduction: Ethereum has many shared test networks, This blog post This paper introduces the differences of each network and its blockchain browser. Among them, the blockchain browser mainly used by developers is rinkeby and ropsten. The above two web sites are the faucet websites of these two kinds of test coins. The tutorials for obtaining test coins are as follows: Get rinkeby test currency,Get ropsten test currency.
-
Free third-party node access - Wang station
Website: infura
Profile: for ETH wallet development, this is an indispensable website. Of course, there may be other third-party nodes open to users for free, but I always use this website. The function of this website is that we can develop ETH normally without building ETH nodes. We just need to use our fingers to register an account, create our project, and get a free access ETH node, and it also includes all popular test networks. And I call it Wang Zhan because its website icon is similar to a Wang character.
-
The most convenient ide of Ethereum -- Remix
Website: remix
Introduction: for ETH wallet development, contract development and deployment may be an essential part. Why do I say that? That's because in wallet development, we always need to dock with various erc20 tokens. Although we can get ETH test coins, we can't get other token test coins (or not at all). The token code based on erc20 protocol is universal, so when we access token wallet, we often consider our own deployment in the test network A contract of erc20 protocol and coinage by itself for the convenience of subsequent development, and the deployment of the contract by combining remix and MetaMask is a matter of several steps. For the process of deploying contracts, please refer to This tutorial.
ETH wallet code reference
Real knowledge is in experience
Generate wallet address, public and private keys and mnemonics / recover wallet address, public and private keys by mnemonics
-
Import dependency
<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>
-
Initialize web3j
private final static Web3j web3j = Web3j.build(new HttpService("https://Mainnet. Information. IO / V3 / id of your own application from information ");
-
Reference code
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();// Mnemonic word } else { deterministicSeed = new DeterministicSeed(mnemonic, null, "", System.currentTimeMillis() / 1000); } byte[] seedBytes = deterministicSeed.getSeedBytes();// seed if (null == seedBytes) { logger.error("Failed to generate Wallet"); return null; } //Seed object DeterministicKey deterministicKey = HDKeyDerivation.createMasterPrivateKey(seedBytes); String[] pathArray = mnemonicPath.split("/");// Mnemonic path 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; } }
For an explanation of mnemonic path, please refer to this article: About wallet mnemonics . The wallet address of erc20 token and ETH wallet address are common, so this code can be used to generate ETH wallet address and erc20 wallet address.
-
Test code
/** * Generate wallet address, public and private key, mnemonic */ @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); } /** * Recovery of wallet address, public and private key by mnemonics */ @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); }
Further, we may wish to derive all wallet addresses and keys of the exchange from a unique key or mnemonic. Please refer to this set of codes:
-
Reference code
/** * Generate corresponding sub account by mnemonic and id * * @param mnemonic Mnemonic word * @param id Son id * @return Sub account 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)); } /** * Generated address * * @param id User id * @return address */ public static String getEthAddress(String mnemonic, int id) { DeterministicKey deterministicKey = generateKeyFromMnemonicAndUid(mnemonic, id); ECKeyPair ecKeyPair = ECKeyPair.create(deterministicKey.getPrivKey()); return Keys.getAddress(ecKeyPair); } /** * Generate private key * * @param id User id * @return Private key */ public static BigInteger getPrivateKey(String mnemonic, int id) { return generateKeyFromMnemonicAndUid(mnemonic, id).getPrivKey(); }
-
Test code
/** * Generate wallet address and private key by mnemonic and user 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); }
Get balance / get token balance
-
Reference code
/** * Get eth balance * * @param address Address of incoming query * @return String balance * @throws IOException */ public static String getEthBalance(String address) { EthGetBalance ethGetBlance = null; try { ethGetBlance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send(); } catch (IOException e) { logger.error("[Obtain ETH Balance failed] error message: {}", e.getMessage()); } // Format conversion Wei (currency unit) -- > ether String balance = Convert.fromWei(new BigDecimal(ethGetBlance.getBalance()), Convert.Unit.ETHER).toPlainString(); return balance; }
-
Test code
/** * Get ETH balance */ @Test public void testGetETHBalance(){ String balance = EthUtil.getEthBalance("0x09f20ff67db2c5fabeb9a2c8dd5f6b4afab7887b"); logger.warn("balance: {}", balance); }
-
Reference code
/** * Get account token balance * * @param account Account address * @param coinAddress Contract address * @return Token balance (unit: minimum unit of token) * @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("[Failed to get contract token balance] error message: {}", 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 code
/** * Get token balance */ @Test public void testGetTokenBalance(){ String usdtBalance = EthUtil.getTokenBalance("0x09f20ff67db2c5fabeb9a2c8dd5f6b4afab7887b", "0xdac17f958d2ee523a2206206994597c13d831ec7"); logger.warn("usdtBalance: {}", usdtBalance); }
There are two kinds of ETH addresses, one is a common user address and the other is a contract address. All transfer of token types are initiated to the contract address. Enter the actual entry information (address and quantity) in the input. The contract addresses of various tokens can be viewed The most official block browser of Ethereum . The code for token precision in the reference code above can continue to refer to the code below.
Get token name, precision, and symbol
-
Reference code
/** * Query token symbols */ 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("Failed to get token"); e.printStackTrace(); } catch (ExecutionException e) { logger.error("Failed to get token"); e.printStackTrace(); } List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters()); if (null == results || 0 == results.size()) { return ""; } return results.get(0).getValue().toString(); } /** * Query token name */ 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("Failed to get token name"); e.printStackTrace(); } catch (ExecutionException e) { logger.error("Failed to get token name"); e.printStackTrace(); } List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters()); if (null == results || results.size() <= 0) { return ""; } return results.get(0).getValue().toString(); } /** * Query token precision */ 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("Failed to get token precision"); e.printStackTrace(); } catch (ExecutionException e) { logger.error("Failed to get token precision"); 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 code
/** * Get token name, symbol, and precision */ @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); }
Acquiring transactions
-
Reference code
/** * Obtain block transaction according to block height * @param height block 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("[Get transaction data successfully] block hash: {}, block height: {}", block.getHash(), block.getNumber()); } catch (IOException e) { logger.error("[Failed to get transaction data] error message: {}", e.getMessage()); return null; } return transactions; } /** * Get transaction information according to txid * @param txid Transaction hash * @return */ public static Transaction getTxByTxid(String txid) { Transaction transaction = null; try { transaction = web3j.ethGetTransactionByHash(txid).send().getTransaction().orElse(null); logger.info("[Obtain transaction information successfully] {} : {}", txid, new Gson().toJson(transaction)); } catch (IOException e) { logger.info("[Failed to get transaction information] transaction hash: {}, error message: {}", txid, e.getMessage()); return null; } return transaction; } /** * Analyze token transaction * @param transaction Transaction object * @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; } /** * Analysis of ETH transaction * @param transaction Transaction object * @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; }
-
Test code
/** * Get ETH / token transaction information according to txid */ @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)); } /** * Get transactions based on block height */ @Test public void testGetTransactionByBlockHeight(){ List<Transaction> transactions = EthUtil.getTxByHeight(new BigInteger("9159698")); logger.warn("txCount: {}", transactions.size()); }
ETH / token offline signature transfer
-
Reference code
/** * Send eth offline transaction * * @param from eth Holding address * @param to Send to address * @param amount Amount (unit: eth) * @param credentials Secret key object * @return Transaction 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 Offline transfer failed] error message: {}", e.getMessage()); return null; } } /** * Send token offline transaction * * @param from Token holding address * @param to Token destination address * @param value Amount (unit: token minimum unit) * @param coinAddress Token contract address * @param credentials Secret key object * @return Transaction 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("[Token offline transfer failed] error message: {}", e.getMessage()); return null; } }
-
Test code
/** * Test ETH transfer */ @Test public void testETHTransfer() throws Exception{ String from = "0xB7Cd09d73a1719b90469Edf7Aa1942d8f89Ba21f"; String to = "0xF0B8412C211261B68bc797f31F642Aa14fbDC007"; String privateKey = "Key not visible"; 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 token transfer */ @Test public void testTokenTransfer() throws Exception{ String from = "0xB7Cd09d73a1719b90469Edf7Aa1942d8f89Ba21f"; String to = "0xF0B8412C211261B68bc797f31F642Aa14fbDC007"; String contractAddress = "0x6a26797a73f558a09a47d2dd56fbe03227a31dbb"; String privateKey = "Key not visible"; 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); }
The contract address in the code is where I am rinkeby test network One test currency issued: TestCoin Token(TCT) Students who do not want to deploy their contracts can pay attention to my public address and send me their wallet address. I will send some test money to your wallet address. In the above code, it is interesting to manage the nonce value. For the explanation of nonce value, please refer to This article . In the above code, the nonce value is obtained directly through the RPC interface, which is relatively simple but takes the longest time, because there is network overhead in calling RPC. A more mature processing method is to maintain a nonce field in the transaction information table. On the one hand, when a new transaction is initiated, the nonce value can be obtained faster. On the other hand, when the transaction is wrong (a transaction with the wrong amount is initiated), the nonce value can be modified in time, because the design of Ethereum is that you can initiate a nonce value with the previous nonce value The same transaction, to cover the transaction in the pending state.
In addition, for the calculation of miner's fee, the normal Ethereum transaction is miner's fee = gasPrice*gasLimit, and gasPrice is the cost consumed in each calculation step. gasLimit is the maximum number of calculations allowed to be accepted. Their unit is WEI. For the unit of Ethereum, please refer to This article . But when you use this formula to calculate the handling fee of token, it is not very accurate, because the gas consumed by token transaction is not 100% consumed. On the transaction page of blockchain browser, you can see that each token transaction has a gas usage rate, and because the script size of each token input is different, the usage rate of gas cannot be determined. So far, I haven't found a way to accurately calculate the handling fee of tokens.
If you are interested in my article, please pay attention to my public address.