区块链开发之ETH钱包APP
目录
基础知识
- 介绍
以太坊(英文Ethereum)是一个开源的有智能合约功能的公共区块链平台,通过其专用加密货币以太币(Ether,简称“ETH”)提供去中心化的以太虚拟机(Ethereum Virtual Machine)来处理点对点合约。
- 合约
合约是一个活在以太坊系统里的自动代理人,他有一个自己的以太币地址,当用户向合约的地址里发送一笔交易后,该合约就被激活,然后根据交易中的额外信息,合约会运行自身的代码,最后返回一个结果,这个结果可能是从合约的地址发出另外一笔交易。需要指出的是,以太坊中的交易,不单只是发送以太币而已,它还可以嵌入相当多的额外信息。如果一笔交易是发送给合约的,那么这些信息就非常重要,因为合约将根据这些信息来完成自身的业务逻辑。
开发准备
Web3J介绍:
web3j是一个高度模块化,反应性,类型安全的Java和Android库,用于与智能合约配合并与以太坊网络上的客户端(节点)集成:
image
功能
- 通过HTTP和IPC 完整实现以太坊的JSON-RPC客户端API
- 以太坊钱包支持
- 自动生成Java智能合约包装器,以从本地Java代码创建,部署,处理和调用智能合约(支持Solidity和Truffle定义格式)
- 响应式API,用于过滤器
- 以太坊名称服务(ENS)支持
- 支持Parity的Personal和Geth的Personal客户端API
- 支持Infura,因此您不必自己运行以太坊客户端
- 支持ERC20和ERC721令牌标准
- 全面的集成测试,证明了上述多种情况
- 命令行工具
- 兼容Android
- 通过web3j-quorum支持JP Morgan的Quorum
依赖
Java 8:
compile ('org.web3j:core:4.5.12')
Android:
implementation ('org.web3j:core:4.2.0-android')
web3j基本使用
1、同步
Web3j web3 = Web3j.build(new HttpService("节点地址"));// defaults to http://localhost:8545/
Web3ClientVersion web3ClientVersion = web3.web3ClientVersion().send();
String version = web3ClientVersion.getWeb3ClientVersion();
System.out.println("version:" + version);
2、异步
Web3j web3 = Web3j.build(new HttpService("节点地址")); // defaults to http://localhost:8545/
web3.web3ClientVersion().sendAsync().thenAccept(new Consumer<Web3ClientVersion>() {
@Override
public void accept(Web3ClientVersion web3ClientVersion) {
System.out.println(web3ClientVersion.getWeb3ClientVersion());
}
}).exceptionally(new Function<Throwable, Void>() {
@Override
public Void apply(Throwable throwable) {
throwable.printStackTrace();
return null;
}
});
实战开发
生成账号
1、随机生成账号
String mnemonic = ChainUtil.genMnemonic();
ECKey ecKey = ChainUtil.genECKey(mnemonic, "m/44'/60'/0'/0/0", "");
ECKeyPair keyPair = ECKeyPair.create(ecKey.getPrivKeyBytes());
File file = new File("Keystore存放的路径");
String walletFile = WalletUtils.generateWalletFile("你的密码", keyPair, file, false);
String keystore = FilesUtils.readFileString(new File(file, walletFile).getAbsolutePath());
Credentials credentials = WalletUtils.loadCredentials("你的密码", new File(file, walletFile));
String address = credentials.getAddress();
System.out.println("mnemonic:" + mnemonic);
System.out.println("privateKey:" + keyPair.getPrivateKey().toString(16));
System.out.println("publicKey:" + keyPair.getPublicKey().toString(16));
System.out.println("keystore:" + keystore);
System.out.println("address:" + address);
2、私钥导入
String privateKey = "你的私钥";
Credentials credentials = Credentials.create(privateKey);
String address = credentials.getAddress();
ECKeyPair keyPair = credentials.getEcKeyPair();
File file = new File("Keystore存放的路径");
String walletFile = WalletUtils.generateWalletFile("你的密码",keyPair,file,false);
String keystore = FilesUtils.readFileString(new File(file, walletFile).getAbsolutePath());
System.out.println("privateKey:" + keyPair.getPrivateKey().toString(16));
System.out.println("publicKey:" + keyPair.getPublicKey().toString(16));
System.out.println("keystore:" + keystore);
System.out.println("address:" + address);
//私钥是无法推算出助记词的
3、KeyStore导入
String keystore = "你的keyStore";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
WalletFile walletFile = objectMapper.readValue(keystore, WalletFile.class);
ECKeyPair keyPair = EthWallet.decrypt("你的密码", walletFile);
String privateKey = keyPair.getPrivateKey().toString(16);
String publicKey = keyPair.getPublicKey().toString(16);
Credentials credentials = Credentials.create(privateKey, publicKey);
String address = credentials.getAddress();
System.out.println("privateKey:" + privateKey);
System.out.println("publicKey:" + publicKey);
System.out.println("keystore:" + keystore);
System.out.println("address:" + address);
4、助记词导入
String mnemonic = "你的助记词,逗号分隔";
ECKey ecKey = ChainUtil.genECKey(mnemonic, "m/44'/60'/0'/0/0", "");
ECKeyPair keyPair = ECKeyPair.create(ecKey.getPrivKeyBytes());
File file = new File("Keystore存放的路径");
String walletFile = WalletUtils.generateWalletFile("你的密码", keyPair, file, false);
String keystore = FilesUtils.readFileString(new File(file, walletFile).getAbsolutePath());
Credentials credentials = WalletUtils.loadCredentials("你的密码", new File(file, walletFile));
String address = credentials.getAddress();
System.out.println("mnemonic:" + mnemonic);
System.out.println("privateKey:" + keyPair.getPrivateKey().toString(16));
System.out.println("publicKey:" + keyPair.getPublicKey().toString(16));
System.out.println("keystore:" + keystore);
System.out.println("address:" + address);
转账
String from = "转出地址";
String to = "转入地址";
String privateKey = "你的私钥";
BigInteger value = "转出多少";
BigInteger gasPrice = "gas价格";
BigInteger gasLimit = BigInteger.valueOf(21000); //单笔转账一般取21000
Web3j web3 = Web3j.build(new HttpService("节点地址")); // defaults to http://localhost:8545/
BigInteger nonce = web3.ethGetTransactionCount(from, DefaultBlockParameterName.LATEST).send().getTransactionCount();
RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, "");
ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
Credentials credentials = Credentials.create(ecKeyPair);
byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
String signData = Numeric.toHexString(signMessage);
EthSendTransaction ethSendTransaction = web3.ethSendRawTransaction(signData).send();
String transactionHash = ethSendTransaction.getTransactionHash();
System.out.println("hash:" + transactionHash);
//获取到hash就可以查询交易状态了。
代币转账
String from = "转出地址";
String to = "转入地址";
String privateKey = "你的私钥";
BigInteger value = "转出多少";
String contract = "";//合约地址
BigInteger gasPrice = "gas价格";
BigInteger gasLimit = BigInteger.valueOf(60000); //代币转账一般取60000
Web3j web3 = Web3j.build(new HttpService("节点地址")); // defaults to http://localhost:8545/
BigInteger nonce = web3.ethGetTransactionCount(from, DefaultBlockParameterName.LATEST).send().getTransactionCount();
Function function = new Function("transfer", Arrays.asList(new Address(to), new Uint256(value)),
Collections.singletonList(new TypeReference<Type>() {}));
String encodedFunction = FunctionEncoder.encode(function);
RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, contract, encodedFunction);
ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
Credentials credentials = Credentials.create(ecKeyPair);
byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
String signData = Numeric.toHexString(signMessage);
EthSendTransaction ethSendTransaction = web3.ethSendRawTransaction(signData).send();
String transactionHash = ethSendTransaction.getTransactionHash();
System.out.println("hash:" + transactionHash);
//获取到hash就可以查询交易状态了。
获取余额
Web3j web3 = Web3j.build(new HttpService("节点地址")); // defaults to http://localhost:8545/
String address = "获取余额的地址";
BigInteger balance = web3.ethGetBalance(address, DefaultBlockParameterName.LATEST).send().getBalance();
System.out.println("balance:" + balance);
获取代币余额
- 参数:
from: 钱包地址
to: 代币地址(智能合约地址)
data:0x70a08231000000000000000000000000b60e8dd61c5d32be8058bb8eb970870f07233155
//注意 data数据格式:最前边的“0x70a08231000000000000000000000000”是固定的,后边的是钱包地址(不带“0x”前缀)
QUANTITY|TAG,”latest”, “earliest” or “pending”
代码示例
Web3j web3 = Web3j.build(new HttpService("节点地址")); // defaults to http://localhost:8545/
String address = "获取余额的地址";
String contract = "合约地址";
String tmpAddress = address;
if (tmpAddress.startsWith("0x")) {
tmpAddress = address.substring(2); //去掉0x
}
String data = "0x70a08231000000000000000000000000"+tmpAddress;
Transaction transaction = Transaction.createEthCallTransaction(address, contract, data);
String balance = web3.ethCall(transaction, DefaultBlockParameterName.LATEST).send().getValue();
System.out.println("balance:" + balance);
总结
1、导入钱包踩坑
导入轻钱包解析失败解决方案Out Of Memory exception when using web3j in Android
解决办法
implementation ‘com.lambdaworks:scrypt:1.4.0’//解决oom错误的依赖
因为在导入keystore解析时,只是这句报错常规的:ECKeyPair keyPair = ECKeyPair.create(privateKeyByte);报错,
这个错误,是因为重钱包不能被解析轻钱包的方法解析,keyStore中crypto.kdfparams.n: 262144,这个数值很大,一般常规的轻钱包n值是:4000左右,或者是个四位数。
对于解决办法,查了很多资料,最后找到了。替换下面代码即可。
//derivedKey = generateDerivedScryptKey(password.getBytes(Charset.forName("UTF-8")), salt, n, r, p, dklen);
derivedKey = com.lambdaworks.crypto.SCrypt.scrypt(password.getBytes(StandardCharsets.UTF_8), salt, n, r, p, dklen);
2、实现以太坊钱包相对还是容易一些,毕竟提供了相关的SDK,实现起来也容易,主要是掌握web3j的API使用,如果选择其他的以太坊SDK也是差不多的,只是熟悉API的调用即可。
参考文献
- ethereum github: https://github.com/ethereum
- web3j github: https://github.com/web3j/web3j/
- web3j 官网: https://docs.web3j.io/
- 感谢代币余额查询文章:https://blog.csdn.net/wypeng2010/article/details/81362562
- 感谢转账和代币转账文章:https://www.jianshu.com/p/8ae984e6bafc
- 感谢解决Android OOM 文章https://blog.csdn.net/jian11058/article/details/100133979