[JAVA] Send signed transactions using Web3j

Article content

How to send a signed transaction using Ethereum's Java library "Web3j"

environment

Implementation

It's pretty rough because it's a code I wrote for my own learning. .. In this sample source, the transaction send method is created with and without signature, respectively, and both are called in order from the main method.

SendTransaction.java


package jp.ethereum.transaction;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Optional;

import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.admin.methods.response.PersonalUnlockAccount;
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.Transfer;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Numeric;

public class SendTransaction {

  // ①
  public static final Admin web3j = Admin.build(new HttpService("http://127.0.0.1:8545"));

  public static void main(String args[]) {
    try {
      // ②
      //Get transaction sender credentials
      //Credentials include public and private keys
      String password = "password";
      Credentials credentials = WalletUtils.loadCredentials(password, "Full path of private key file");

      String toAddress = "0x66b4e7be902300f9a15d900822bbd8803be87391";

      SendTransaction tx= new SendTransaction();

      //Send transaction
      tx.sendTransaction(credentials, password, toAddress, 10);

      //Send signed transaction
      TransactionReceipt receipt = tx.sendSignedTransaction(credentials, password, toAddress);

      if (receipt != null) System.out.println(receipt.getTransactionHash()) ;

    }catch(IOException | CipherException ex) {
      ex.printStackTrace();
    }
  }

  public TransactionReceipt sendTransaction(Credentials credentials, String password, String toAddress, long value) {

    TransactionReceipt receipt = null;
      try {
        //Transaction generation
        // "personal_unlockAccount"Send a request and receive a response
        PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
            credentials.getAddress(), //address
            password  //password
            ).send();

        //If the unlock is successful, send Ether
        if (unlockAccountResponse.getResult()) {
          //Send a Transaction. No response is returned until it is loaded into the Block
          receipt = Transfer.sendFunds(web3j, credentials, toAddress, BigDecimal.valueOf(value), Unit.ETHER).send();
        }
      }catch(IOException | TransactionException ex) {
        ex.printStackTrace();
      }catch(Exception ex) {
        ex.printStackTrace();
      }
    return receipt;
  }
  public TransactionReceipt sendSignedTransaction(Credentials credentials, String password, String toAddress) {

      TransactionReceipt receipt = null;
      try {
        //Transaction generation
        // "personal_unlockAccount"Send a request and receive a response
        PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
            credentials.getAddress(), //address
            password  //password
            ).send();

        //If the unlock is successful, send Ether
        if (unlockAccountResponse.getResult()) {
            // "eth_sendTransaction"Create an object to pass to the argument of
          RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
            BigInteger.valueOf(10),     // nonce
            BigInteger.valueOf(700),     // gasPrice
            BigInteger.valueOf(4712388),   // gasLimit
            toAddress,             // to
            BigInteger.valueOf(101)     // value
            );

          //Sign transaction
          byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
          String hexValue = Numeric.toHexString(signedMessage);

          //Send transaction
          EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
          String response = ethSendTransaction.getRawResponse();
          String transactionHash = ethSendTransaction.getTransactionHash();

          Optional<TransactionReceipt> transactionReceipt = null;
          int retry = 0;

          //Transaction monitoring
          if(transactionHash != null) {
            do {
            System.out.printf("%3d checking if transaction " + transactionHash + " is mined....\n" ,retry);
              EthGetTransactionReceipt ethGetTransactionReceiptResp = 
                  web3j.ethGetTransactionReceipt(transactionHash).send();
                transactionReceipt = ethGetTransactionReceiptResp.getTransactionReceipt();

                Thread.sleep(3000);
                retry++;
            }while(!transactionReceipt.isPresent() && retry < 100);
          } else {
            System.out.println("Transaction Send failed...");
            System.out.println("Message:" + ethSendTransaction.getError().getMessage());
            System.out.println("Data   :" + ethSendTransaction.getError().getData());
          }
        }
      }catch(IOException | InterruptedException ex) {
        ex.printStackTrace();
      }catch(Exception ex) {
        ex.printStackTrace();
      }
    return receipt;
  }
}

I will supplement the points that are likely to be points.

Connect to Ethereum

Connect to Ethereum

SendTransaction.java


// ①
public static final Admin admin = Admin.build(new HttpService("http://127.0.0.1:8545"));

Here, we are creating an instance of the Admin class. The Admin class is a class that inherits from the Web3j class, and you can use methods that use personal information that are not implemented in the Web3j class. Use the Admin class instead of Web3j because sending transactions requires unlocking the account.

The URL specified in the argument will be the address specified by the "--rpcaddr" option when geth is started. If you do not specify the port, it will be "8545".

Get the private key

SendTransaction.java


//Get transaction sender credentials
//Credentials include public and private keys
String password = "password";
Credentials credentials = WalletUtils.loadCredentials(password, "Full path of private key file");

Get the private key information with the WalletUtils.loadCredentials method. User account password as the first argument Specify the path (including the file name) of the private key file (file starting with UTC) in the second argument.

Send transaction

All you have to do is unlock your account and send a transaction.

SendTransaction.java


  public TransactionReceipt sendTransaction(Credentials credentials, String password, String toAddress, long value) {

    TransactionReceipt receipt = null;
      try {
        //Transaction generation
        // "personal_unlockAccount"Send a request and receive a response
        PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
            credentials.getAddress(), //address
            password  //password
            ).send();

        //If the unlock is successful, send Ether
        if (unlockAccountResponse.getResult()) {
          //Send a Transaction. No response is returned until it is loaded into the Block
          receipt = Transfer.sendFunds(web3j, credentials, toAddress, BigDecimal.valueOf(value), Unit.ETHER).send();
        }
      }catch(IOException | TransactionException ex) {
        ex.printStackTrace();
      }catch(Exception ex) {
        ex.printStackTrace();
      }
    return receipt;
  }

The point is that no response was returned until the transaction was taken into the block. Although the block generation interval is shorter than Bitcoin, it seems that it will be necessary to consider it if there is a situation to use it in an actual application.

receipt = Transfer.sendFunds(web3j, credentials, toAddress, BigDecimal.valueOf(value), Unit.ETHER).send();

Execution result of transaction transmission

First, check with "eth.getTransaction" when the transaction is generated.

INFO [09-15|23:28:07.213] Submitted transaction                    fullhash=0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97 recipient=0x66B4e7bE902300F9a15D900822Bbd8803Be87391

> eth.getTransaction("0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0x945cd603a6754cb13c3d61d8fe240990f86f9f8a",
  gas: 21000,
  gasPrice: 1000000000,
  hash: "0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97",
  input: "0x",
  nonce: 7,
  r: "0x3ca5a820995553d30656f2218dc10729d3e0f660c35817bbd69845ac96dc6279",
  s: "0x2e915ae47699771108f65881273f464d70db0229ad12a94ac38746f498ea7ed3",
  to: "0x66b4e7be902300f9a15d900822bbd8803be87391",
  transactionIndex: 0,
  v: "0x1c",
  value: 10000000000000000000
}

The transaction has been created. Mining and see if it gets into the block.

> eth.getTransaction("0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97")
{
  blockHash: "0xc9634aec9670d312759a0e12ea5fee54948688c88e4d45a5b9cdbeef3c44c681",
  blockNumber: 2792,
  from: "0x945cd603a6754cb13c3d61d8fe240990f86f9f8a",
  gas: 21000,
  gasPrice: 1000000000,
  hash: "0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97",
  input: "0x",
  nonce: 7,
  r: "0x3ca5a820995553d30656f2218dc10729d3e0f660c35817bbd69845ac96dc6279",
  s: "0x2e915ae47699771108f65881273f464d70db0229ad12a94ac38746f498ea7ed3",
  to: "0x66b4e7be902300f9a15d900822bbd8803be87391",
  transactionIndex: 0,
  v: "0x1c",
  value: 10000000000000000000
}

It was taken in safely.

Send signed transaction

SendTransaction.java


  public TransactionReceipt sendSignedTransaction(Credentials credentials, String password, String toAddress) {

      TransactionReceipt receipt = null;
      try {
        //Transaction generation
        // "personal_unlockAccount"Send a request and receive a response
        PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
            credentials.getAddress(), //address
            password  //password
            ).send();

        //If the unlock is successful, send Ether
        if (unlockAccountResponse.getResult()) {
            // "eth_sendTransaction"Create an object to pass to the argument of
          RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
            BigInteger.valueOf(10),     // nonce
            BigInteger.valueOf(700),     // gasPrice
            BigInteger.valueOf(4712388),   // gasLimit
            toAddress,             // to
            BigInteger.valueOf(101)     // value
            );

          //Sign transaction
          byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
          String hexValue = Numeric.toHexString(signedMessage);

          //Send transaction
          EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
          String response = ethSendTransaction.getRawResponse();
          String transactionHash = ethSendTransaction.getTransactionHash();

          Optional<TransactionReceipt> transactionReceipt = null;
          int retry = 0;

          //Transaction monitoring
          if(transactionHash != null) {
            do {
            System.out.printf("%3d checking if transaction " + transactionHash + " is mined....\n" ,retry);
              EthGetTransactionReceipt ethGetTransactionReceiptResp = 
                  web3j.ethGetTransactionReceipt(transactionHash).send();
                transactionReceipt = ethGetTransactionReceiptResp.getTransactionReceipt();

                Thread.sleep(3000);
                retry++;
            }while(!transactionReceipt.isPresent() && retry < 100);
          } else {
            System.out.println("Transaction Send failed...");
            System.out.println("Message:" + ethSendTransaction.getError().getMessage());
            System.out.println("Data   :" + ethSendTransaction.getError().getData());
          }
        }
      }catch(IOException | InterruptedException ex) {
        ex.printStackTrace();
      }catch(Exception ex) {
        ex.printStackTrace();
      }
    return receipt;
  }
}

What you are doing

  1. Unlock your account
  2. Creation of transaction object
  3. Sign the transaction
  4. Send transaction
  5. Transaction monitoring

It will be the flow. Transaction monitoring checks to see if the hash value of the specified transaction has been populated into the block.

Execution result

The transaction has been created.

INFO [09-15|23:29:23.752] Submitted transaction                    fullhash=0x533ff5d2635284d01d8e85015da00d8962de8b98bf5efaa6d9ceca3200243a88 recipient=0x66B4e7bE902300F9a15D900822Bbd8803Be87391

> eth.getTransaction("0x533ff5d2635284d01d8e85015da00d8962de8b98bf5efaa6d9ceca3200243a88")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0x945cd603a6754cb13c3d61d8fe240990f86f9f8a",
  gas: 4712388,
  gasPrice: 800,
  hash: "0x533ff5d2635284d01d8e85015da00d8962de8b98bf5efaa6d9ceca3200243a88",
  input: "0x",
  nonce: 10,
  r: "0xc8022528f46078b3e9a8f2a3174d147b85c23af6794fc8b07d58651931b7556f",
  s: "0x1ab35d15cf3afa6990bc6e96a593e6c2cba6432f769d0de919c770cec4a3f2c7",
  to: "0x66b4e7be902300f9a15d900822bbd8803be87391",
  transactionIndex: 0,
  v: "0x1b",
  value: 101
}

However, this transaction is not included in the block no matter how much mining is done, because my understanding is not catching up. Is there something wrong with the value specified for the transaction? Or is it not included in the block without validating the signed transaction?

I will continue to study this area.

Finally

I was just learning by vaguely inputting the contents written in the book, but when I investigated how to use it in Java, which is my best language, the points that had been pointed up until now became more and more connected.

Since there is still little information available, I would like to output what I have learned positively.

2019/09/17 Added

I mentioned that the signed transaction is not included in the block, but I found the solution (I asked my senior and found it in 1 minute), so I wrote it in the following article.

Check if Ethereum transaction is not included in the block

Recommended Posts

Send signed transactions using Web3j
[Hidden_field] Let's send information using rails hidden_field !!!!
[Ethereum] Get block information using web3j