Java and smart contract interaction (Web3j) - write function

say up front

Web3 is an emerging network concept. Due to some reasons, the relevant technical knowledge we can access is really limited. Whenever I encounter technical bottlenecks, I can't always find sufficient information. This also gave me the idea to fill this gap in knowledge impulse. The column "Hello Web3" will try my best to share the web3 knowledge I have mastered with you. If the shared knowledge can help everyone, I hope to follow , like and support the author! I have released the Web3j tool
on github , welcome to use and star

Java interacts with smart contracts (Web3j)

The reason why I choose to use java to interact with smart contracts is entirely because I only know Java, and Java is the best language in the world.

what can you do

  • Monitor the status of the contract, read the key parameters of the contract, and can be used as a background data source.
  • Basic interactions such as transfer and authorization.
  • Realize complex interactions such as snapping up, picking up and buying.

code sharing

  1. Introduce dependencies
    <dependency>
        <groupId>org.web3j</groupId>
        <artifactId>core</artifactId>
        <version>5.0.0</version>
    </dependency>
  1. Create a new Web3j object
Web3j web3 = Web3j.build(new HttpService(rpcUrl));
  • The rpcUrl variable is the url link of the blockchain network nodes. These nodes will provide many standard api methods to call through this url. The web3j module is encapsulated on this api.
  • The rpcUrl of different networks can be found in the corresponding blockchain browser api documents, and Baidu keywords are also easy to obtain.
  1. Query the gasPrice of the current network
// 获取gasPrice方法
BigInteger gasPrice = web3.ethGasPrice().send().getGasPrice();

/**
 * 获取当期的gasPrice,如果超过最大的限制,取最大限制
 *
 * @return 在区间内的gasPrice
 * @throws IOException 与节点交互出现异常
 */
public BigInteger getGasPriceWithLimit() throws IOException {
    
    
    BigInteger gasPrice = web3.ethGasPrice().send().getGasPrice();
    log.info("Gas price: {} Gwei, Min gas price: {} Gwei, Max gas price: {} Gwei",
    Convert.fromWei(String.valueOf(gasPrice), Convert.Unit.GWEI),
    Convert.fromWei(String.valueOf(minGasPrice), Convert.Unit.GWEI),
    Convert.fromWei(String.valueOf(maxGasPrice), Convert.Unit.GWEI));
    // 超过最大限制返回最大的gasPrice
    if (maxGasPrice.compareTo(gasPrice) < 0) {
    
    
       return maxGasPrice;
    }
    // 小于最小的限制返回最小的gasPrice
    if (minGasPrice.compareTo(gasPrice) > 0) {
    
    
       return minGasPrice;
    }
       return gasPrice;
}
  • Note that this method will obtain the average gasPrice of recent blocks, which may be too high.
  • Refer to the author's getGasPriceWithLimit method. If you are not in an urgent need such as panic buying, it is recommended not to directly use the return value of this method as gasPrice, or you will cry!
  1. Query the number of transactions in the current account and use it as a transaction nonce
	/**
     * 获取交易数量
     *
     * @return 账户交易次数
     * @throws IOException 与节点交互失败
     */
    public BigInteger getTransactionCount() throws IOException {
    
    
        EthGetTransactionCount ethGetTransactionCount = web3.ethGetTransactionCount(
                ownerAddress, DefaultBlockParameterName.LATEST).send();
        return ethGetTransactionCount.getTransactionCount();
    }
  • The Ethereum virtual machine uses this nonce field as a sequence to process your multiple transactions. By default, the general nonce takes the number of transactions in the account
  • If the nonce of this transaction is smaller than the previous successful transaction, the transaction will fail
  1. Query the chainId of the chain of the current blockchain network
long chainId = web3.ethChainId().send().getChainId().longValue();
  • After Ethereum Classic was forked from Ethereum, in order to prevent double-spending attacks, Chain ID was introduced in EIP155.
  • By adding the Chain ID to the signature information, a transaction is prevented from being repeatedly submitted on different chains after signing.
  1. Import the private key (the private key in the code must not be passed to the github public warehouse!!!)
Credentials credentials = Credentials.create(privateKey);
// 私钥对应的地址
String address = credentials.getAddress();
  1. Estimate, sign, send transaction data
    /**
     * 与合约交互
     *
     * @param contractAddress 交互合约地址
     * @param functionName    交互函数名称
     * @param value           携带的eth数量(单位Ether)
     * @param input           输入参数 eg:Arrays.asList(new Address("0x6dF655480F465DC36347a5616E875D155804F0c5"), new Uint256(10000000));
     * @param output          输出参数类型 eg: Arrays.asList(new TypeReference<Bool>(){});
     *                        类型映射关系
     *                        boolean - bool
     *                        BigInteger - uint/int
     *                        byte[] - bytes
     *                        String - string and address types
     *                        List - dynamic/static array
     *                        T - struct/tuple types
     * @return 交易hash
     * @throws Exception 与节点交互出现异常
     */
    public String writeContract(String contractAddress, String functionName, String value, List<Type> input, List<TypeReference<?>> output) throws Exception {
    
    
        // 转换value的单位
        BigInteger valueWei = Convert.toWei(value, Convert.Unit.ETHER).toBigInteger();
        // 生成需要调用函数的data
        Function function = new Function(functionName, input, output);
        String data = FunctionEncoder.encode(function);
        // 估算gasLimit
        BigInteger gasLimit = estimateGasLimit(contractAddress, data, valueWei);
        // 获取gasPrice
        BigInteger gasPrice = getGasPriceWithLimit();
        // 获取chainId
        long chainId = web3.ethChainId().send().getChainId().longValue();
        // 正式请求
        RawTransaction rawTransaction = RawTransaction.createTransaction(getNonce(), gasPrice, gasLimit, contractAddress, valueWei, data);
        // 签名数据
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
        String hexValue = Numeric.toHexString(signedMessage);
        // 发送数据
        EthSendTransaction response = web3.ethSendRawTransaction(hexValue).send();
        // 查看是否有错误
        if (response.hasError()) {
    
    
            throw new Exception("trade hash: " + response.getTransactionHash() +
                    "\nerror: " + response.getError().getMessage());
        }
        log.info("function: {} data: {}", functionName, data);
        log.info("Gas fee: {} ETH", Convert.fromWei(String.valueOf(gasLimit.multiply(gasPrice)), Convert.Unit.ETHER));
        log.info("Trade Hash: {}", response.getTransactionHash());
        return response.getTransactionHash();
    }
    
    /**
     * 估算GasLimit
     *
     * @param to    发送的地址
     * @param data  发送的数据
     * @param value 携带的eth数量(单位wei)
     * @return GasLimit
     * @throws Exception 与节点交互失败
     */
    public BigInteger estimateGasLimit(String to, String data, BigInteger value) throws Exception {
    
    
        if (gasLimit.intValue() != 0) {
    
    
            return gasLimit;
        }
        Transaction testTransaction = Transaction.createFunctionCallTransaction(ownerAddress, null, null, null, to, value, data);
        EthEstimateGas response = web3.ethEstimateGas(testTransaction).send();
        // 查看是否有错误
        if (response.hasError()) {
    
    
            throw new Exception("error: " + response.getError().getMessage());
        }
        return response.getAmountUsed();
    }
  • For the description of the relevant parameters of the writeContract function, please refer to the previous article "read function"
  • The estimateGasLimit function is used to calculate the amount of Gas that the transaction needs to consume this time. In fact, it transfers your transaction data to the chain to simulate the execution function but does not submit the result, and returns the Gas spent for the simulation execution as the request result.
  • When you get all the data required for the transaction, you need to sign the data with the private key before it can be executed
  1. Call the write function to perform the transfer operation

    /**
     * 转账操作
     *
     * @param contractAddress 交互合约地址
     * @param recipient       接收转账地址
     * @param amount          转账数量
     * @return 交易hash
     * @throws Exception 与节点交互失败
     */
    public String transfer(String contractAddress, String recipient, String amount) throws Exception {
    
    
        List input = Arrays.asList(new Address(recipient)
                , new Uint256(new BigInteger(amount, 10)));
        List output = Arrays.asList(new TypeReference<Bool>() {
    
    
        });
        return writeContract(contractAddress, "transfer", input, output);
    }

The best way to master these knowledge points is to run the code yourself and go to the chain to get the information you want

Welcome to pay attention to this column, the author knows everything~

Guess you like

Origin blog.csdn.net/weixin_43855305/article/details/124633724