问题
磨刀不误砍柴工。
但是,在磨刀的过程中,总会遇到一些问题。比如,一个简单的发送ETH代币(你可以理解为GAS费代币),在Goerli上可以正常转账,在mumbai上却失败了。
错误如下:
Error processing transaction request: only replay-protected (EIP-155) transactions allowed over RPC
如果你去谷歌搜索该错误,会得到一些回复,但是他们要么不是使用Java构建的,要么是同样没有解决问题。
我去询问AI,因为没有付费,所以还是GPT3.5,他和我说换一个写法(然后换了数次还是失败了)。
最后,我从其中一个看起来比较有希望的写法入手,查看了源码才构建成功的。
环境
Maven依赖
我创建了Mavne项目,pom.xml中导如了以下依赖:
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.web3j</groupId> <artifactId>core</artifactId> <version>4.8.7</version> </dependency> <dependency> <groupId>org.web3j</groupId> <artifactId>contracts</artifactId> <version>4.8.7</version> </dependency>
|
代码
没有什么比直接上代码更有说服力的了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package tx1559;
import org.web3j.crypto.Credentials; import org.web3j.crypto.WalletUtils; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.methods.response.EthSendTransaction; import org.web3j.protocol.http.HttpService; import org.web3j.tx.RawTransactionManager; import org.web3j.tx.gas.DefaultGasProvider; import org.web3j.utils.Convert; import java.math.BigInteger; public class EIP1559TransactionMATIC {
public static void main(String[] args) throws Exception { Web3j web3 = Web3j.build(new HttpService("https://polygon-mumbai.infura.io/v3/72cc4dbd0f0749089cb4da266cf833bf")); Credentials credentials = Credentials.create("你的私钥"); String toAddress = "目标地址"; BigInteger amountInWei = Convert.toWei("0.001", Convert.Unit.ETHER).toBigInteger(); BigInteger maxPriorityFeePerGas = web3.ethGasPrice().send().getGasPrice(); BigInteger maxFeePerGas = maxPriorityFeePerGas.add(BigInteger.valueOf(1000000000)); EthSendTransaction ethSendTransaction = new RawTransactionManager( web3, credentials) .sendEIP1559Transaction( 80001L, maxPriorityFeePerGas, maxFeePerGas, DefaultGasProvider.GAS_LIMIT, toAddress, "", amountInWei ); String transactionHash = ethSendTransaction.getTransactionHash(); System.out.println(transactionHash); } }
|
最后,他会打印成功的交易哈希。
交易提交成功还需要等待被矿工打包才算完成交易。
在上面的代码中,我们创建了一个类RawTransactionManager()..sendEIP1559Transaction()来完成交易。
前面的参数只有两个,web3和credentials。
后面的参数有多种实现,我尝试了其中的一种,更多的可以根据形参和说明来完成。
除了目标地址和金额外,我们还需要额外的指定链ID、以及GAS费的参数。
在询问够GPT后才写出了什么的代码,虽然不完美,但我感觉已经很好了。
不同的链要切换不同的链ID,比如Goerli的链ID为5,其他的自行搜索。
Java的好处就在于虽然我不知道你是怎么做的,但是给我源码就能运行。
安全的钱包
创建方式
其实官方文档有说的,但是文档那玩意,一般人还真没谁看。毕竟问AI不香吗,现在我觉得真不香,之前看文档就好了。
钱包文件
因为我是直接问AI的,所以就跳过,但是还是建议看看。早看了就没有上面的问题了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package wallet; import org.web3j.crypto.WalletUtils; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths;
public class CreateWalletFileExample {
public static void main(String[] args) throws Exception { String password = "123456789"; String destination = "src/main/resources/wallet"; Path directory = Paths.get(destination); Files.createDirectories(directory); String fileName = WalletUtils.generateFullNewWalletFile(password, directory.toFile()); System.out.println("Wallet file created: " + fileName); } }
|
在项目的Resources目录wallet下面会生成json的钱包文件。
默认名称格式:
UTC–2023-11-24T15-36-52.251000000Z–0d85fdfa3ed8df5c137045074804105312616ab4.json
为了在项目更加方便的引用,你可以进行重命名,比如我重命名为test.json。
加载钱包
我们可以使用JSON文件加载钱包,这样比直接在代码里面上私钥要安全的多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package wallet;
import org.web3j.crypto.Credentials; import org.web3j.crypto.WalletUtils;
public class LoadCredentialsExample {
public static void main(String[] args) throws Exception { String password = "123456789"; String keyFilePath = "src/main/resources/wallet/test.json";
Credentials credentials = WalletUtils.loadCredentials(password, keyFilePath);
System.out.println("Address: " + credentials.getAddress()); } }
|
交易发送
我们可以在最前面的代码里面将Credentials是生成方法来交易。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package tx1559;
import org.web3j.crypto.Credentials; import org.web3j.crypto.WalletUtils; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.methods.response.EthSendTransaction; import org.web3j.protocol.http.HttpService; import org.web3j.tx.RawTransactionManager; import org.web3j.tx.gas.DefaultGasProvider; import org.web3j.utils.Convert; import java.math.BigInteger;
public class EIP1559TransactionExample {
public static void main(String[] args) throws Exception { Web3j web3 = Web3j.build(new HttpService("https://goerli.infura.io/v3/bc809e6905c34cb1b7e3536a0934ce06")); String password = "123456789"; String keyFilePath = "src/main/resources/wallet/test.json"; Credentials credentials = WalletUtils.loadCredentials(password, keyFilePath); String toAddress = "0xefbc3db9875083cc1d00dad3fe9c8989b2d8d9bb"; BigInteger amountInWei = Convert.toWei("0.001", Convert.Unit.ETHER).toBigInteger(); BigInteger maxPriorityFeePerGas = web3.ethGasPrice().send().getGasPrice(); BigInteger maxFeePerGas = maxPriorityFeePerGas.add(BigInteger.valueOf(1000000000)); EthSendTransaction ethSendTransaction = new RawTransactionManager( web3, credentials) .sendEIP1559Transaction( 5L, maxPriorityFeePerGas, maxFeePerGas, DefaultGasProvider.GAS_LIMIT, toAddress, "", amountInWei ); String transactionHash = ethSendTransaction.getTransactionHash(); System.out.println(transactionHash); } }
|
上面的代码中,我们演示了Goerli链的EIP1559交易。
这样的代码安全了很多。
老式钱包生成器
我做了点改进,不在控制台输出,而是保存在txt文本中。
为了方便取用,我分别命名了三个文件:key.txt、address.txt、keyaddress.txt
这样就可以直接调用而不做转换了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package wallet;
import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Keys; import org.web3j.utils.Numeric;
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.math.BigInteger; import java.security.SecureRandom;
public class NewWallet { public static void main(String[] args) { int num = 10; try { BufferedWriter addressWriter = new BufferedWriter(new FileWriter("src/main/resources/address.txt")); BufferedWriter keyAddressWriter = new BufferedWriter(new FileWriter("src/main/resources/keyaddress.txt")); BufferedWriter keyWriter = new BufferedWriter(new FileWriter("src/main/resources/key.txt"));
for (int i = 0; i < num; i++) { SecureRandom secureRandom = new SecureRandom(); byte[] privateKeyBytes = new byte[32]; secureRandom.nextBytes(privateKeyBytes); String privateKey = Numeric.toHexStringNoPrefix(privateKeyBytes); ECKeyPair keyPair = ECKeyPair.create(new BigInteger(privateKey, 16)); String address = Keys.getAddress(keyPair.getPublicKey());
addressWriter.write("0x" + address); addressWriter.newLine(); keyWriter.write(privateKey); keyWriter.newLine(); keyAddressWriter.write("第" + (i+1) +"个钱包:"); keyAddressWriter.newLine(); keyAddressWriter.write("0x" + address); keyAddressWriter.newLine(); keyAddressWriter.write(privateKey); keyAddressWriter.newLine(); }
keyWriter.close(); addressWriter.close(); keyAddressWriter.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
生成后的文件将显示这样。
例如我生成三个作为测试:
key.txt
1 2 3
| 2a3d084065b790fbbf6eb451947e9e77a7fc9222b3738d308bebfb429d8a2bfc 51484a337537de28eefbb8df693e9917440e489f0f42514178269100e39633fa 7cce608c7a2c60d05ae7d08687504893bab15d60dca9625effecffd1dba993c7
|
addre.txt
1 2 3
| 0x33244fdcab048bd249393c03d569235ea0823e3a 0x4ce99a807c4f254ec4743f42aa505a64949d292a 0xe64dad11e301a4fe1deab9740bbb0b41a77a5e52
|
keyaddress.txt
1 2 3 4 5 6 7 8 9
| 第1个钱包: 0x33244fdcab048bd249393c03d569235ea0823e3a 2a3d084065b790fbbf6eb451947e9e77a7fc9222b3738d308bebfb429d8a2bfc 第2个钱包: 0x4ce99a807c4f254ec4743f42aa505a64949d292a 51484a337537de28eefbb8df693e9917440e489f0f42514178269100e39633fa 第3个钱包: 0xe64dad11e301a4fe1deab9740bbb0b41a77a5e52 7cce608c7a2c60d05ae7d08687504893bab15d60dca9625effecffd1dba993c7
|
这样,如果我想批量读取钱包地址,就直接读取addre.txt文件好了。
我让AI帮我写好了代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package test; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * @author HMB-XS * @date 2023年11月23日20:15:05 **/ public class ReadTxt { public static void main(String[] args) { // AI写就是省时间,技术革命 String filePath = "src/main/resources/address.txt"; try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }
|
这就非常棒了,稍微改进下就可以批量发送交易了。
如果你问为什么不直接使用合约,因为要链上交互记录。