Smart contracts with Java
Very often people use JS, Go or Rust to develop Web3 projects, but Java is rarely mentioned. I wanted to see if it is possible and convenient to use Java for such projects. In this guide we will see how to develop a simple Web3 application as a Java Maven project.
Prerequisite
To work on this project, you need first:
- An Ethereum account (on a testnet is enough). See this guide if you don’t know how to create one.
- Access to an Ethereum client (local or remote). We will use Infura for simplicity here.
Project setup
Create a new Maven project like you will do for any Java project.
Dependencies
We will use the Web3J library to interact with the Ethereum client from Java.
Let’s add the dependency to the pom.xml
:
<dependency>
<groupId>org.web3j</groupId>
<artifactId>web3j-maven-plugin</artifactId>
<version>${web3j.version}</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>${web3j.version}</version>
</dependency>
Plugin configuration
The Web3J plugin will be used to compile our smart contracts.
Here is below my configuration below, sourcing Solidity files from contracts/
,
generating the abi and bin files in an output subdirectory, and generating the Java wrapper
in its own package in Java src files.
Please note that you can also use the default configuration which takes the contracts from src/resources. See the doc for details about the plugin configuration.
<plugin>
<groupId>org.web3j</groupId>
<artifactId>web3j-maven-plugin</artifactId>
<version>${web3j.version}</version>
<configuration>
<packageName>com.github.jarnaud.crypto.contract</packageName>
<soliditySourceFiles>
<directory>
contracts/
</directory>
<includes>
<include>**/*.sol</include>
</includes>
</soliditySourceFiles>
<outputDirectory>
<abi>contracts/output</abi>
<bin>contracts/output</bin>
<java>src/main/java</java>
</outputDirectory>
<outputFormat>java,abi,bin</outputFormat>
</configuration>
</plugin>
Contracts directory
At the root of the project, create a contracts
directory to store your Solidity contracts.
In Intellij, the resulting project structure should eventually be something like that:
Create and build a smart contract
Solidity file
Let’s create a very simple smart contract.
In contracts
, create Storage.sol
with the following content:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Storage {
address public owner;
uint private number;
constructor(uint initialValue) {
owner = msg.sender;
number = initialValue;
}
function add_number(uint x) public {
number += x;
}
function store_number(uint x) public {
number = x;
}
function get_number() public view returns (uint) {
return number;
}
}
This simple contract is capable of storing, retrieving and adding a value to a number.
Building the contract
This is where its getting interesting, we can use the Web3J plugin to compile the contract. As defined in the plugin configuration, it will create for us:
- the BIN file
- the ABI file
- the Java wrapper
Run the command below to launch the contract build:
mvn web3j:generate-sources
You should get a new generated Storage.java file (the wrapper), as well as the bin and abi files.
Using the contract
Now that we have our contract ready, let’s see how to use it from Java.
Deployment
The following method will deploy our smart contract to the Sepolia testnet:
void deploy() {
Web3jService service = getSepoliaService();
Web3j web3 = Web3j.build(service);
try {
Credentials credentials = Credentials.create(PRIVATE_KEY);
log.info("Deploying contract...");
Storage storage = Storage.deploy(web3, credentials, new DefaultGasProvider(), BigInteger.valueOf(150)).send();
log.info("Contract deployed at: {}", storage.getContractAddress());
log.info("Testing contract: store_number...");
TransactionReceipt tr = storage.store_number(BigInteger.valueOf(42)).send();
log.info("Transaction receipt: {}", tr);
log.info("Testing contract: get_number...");
BigInteger result = storage.get_number().send();
log.info("Got result from smart contract: {}", result);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
web3.shutdown();
}
}
getSepoliaService()
should return a Web3J service to the testnet. If you use Infura you should have something like:private static Web3jService getSepoliaService() { return new HttpService("https://sepolia.infura.io/v3/" + INFURA_KEY); }
PRIVATE_KEY
is the private key of the account used to issue the deployment and other transactions. You can get it directly from your wallet (e.g. Metamask).
If you call this method, you should see the contract being deployed, then some test interactions being run. Keep the contract address! We will use it later for interacting with it. Note that some SepoliaETH got spent from your account.
Interacting
Once a contract is deployed, you can interact with it later. Here is an example:
void testInteraction() {
Web3jService service = getSepoliaService();
Web3j web3 = Web3j.build(service);
String contractAdress = "0x123123123123...";
try {
Credentials credentials = Credentials.create(PRIVATE_KEY);
Storage storage = Storage.load(contractAdress, web3, credentials, new DefaultGasProvider());
log.info("Got number: {}", storage.get_number().send());
log.info("Adding 1...");
TransactionReceipt tr = storage.add_number(BigInteger.ONE).send();
log.info("Spent {} gas, transaction at block {}", tr.getGasUsed(), tr.getBlockNumber());
log.info("Got number: {}", storage.get_number().send());
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
web3.shutdown();
}
}
You can get plenty of additional information about the gas costs, block numbers, etc. from the transaction receipts. It is very nice to get the Java typing while calling the methods on the Java wrapper, as it prevents errors and simplify development.
Conclusion
In this guide, we saw how to build and deploy a smart contract from Java using the Web3J library. You can now use Web3 applications in addition to the rich Java ecosystem.
comments powered by Disqus