The source code of this article can be found at https://github.com/pigeon2049/planet-universal
As a full-stack developer writing Java, the solidity language seems difficult to get started with, so let's start with a familiar field first.
web3j
Start with a traditional Spring Boot Maven project and add the web3j dependency:
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>${web3j.version}</version>
</dependency>
<dependency>
<groupId>org.web3j</groupId>
<artifactId>contracts</artifactId>
<version>${web3j.version}</version>
</dependency>
To quickly develop and learn, you need to install Docker on your local machine.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
The new version of Spring Boot supports Docker integration, so there is no need to install the environment. Just start it and you will get an IPFS Kubo client and an InfluxDB time series database.
Let's start learning:
web3j requires an Ethereum client node. You can set up your own using geth, or you can use the public port provided by Cloudflare to save time. When using the public port, be careful not to use your own address and private key to interact with contracts, as this may cause security issues.
Add Cloudflare ETH gateway to application.properties:
web3j.node.address=https://cloudflare-eth.com/
Define a configuration file to generate the web3j bean:
@Configuration
public class Web3jConfig {
@Value("${web3j.node.address}")
private String ethNode;
@Bean
public Web3j web3j() throws ExecutionException, InterruptedException {
Web3j build = Web3j.build(new HttpService(ethNode));
Web3ClientVersion web3ClientVersion = build.web3ClientVersion().sendAsync().get();
System.out.println(web3ClientVersion.getWeb3ClientVersion());
return build;
}
}
Now we have a usable web3j client.
Next, let's write a simple method to get the real-time gas fee on the Ethereum blockchain:
@Service
public class GasPrice {
private final Web3j web3j;
public GasPrice(Web3j web3j) {
this.web3j = web3j;
}
public BigInteger getGasPrice() throws IOException {
return web3j.ethGasPrice().send().getGasPrice();
}
}
It's simple, isn't it?
But you may notice that the unit seems incorrect. Normally, we see it as Gwei. Let's convert it:
BigInteger gas = gasPrice.getGasPrice();
BigDecimal value = BigDecimal.valueOf(gas.longValue() * 0.000000001).setScale(2, RoundingMode.HALF_UP);
By rounding to two decimal places, we get Gwei.
Let's write a simple timer to fetch gas data every second and store it in the database:
@Service
public class UpdateGasPrice implements CommandLineRunner {
@Value("${influx.url}")
String url;
@Value("${influx.token}")
String token;
@Value("${influx.org}")
String org;
@Value("${influx.bucket}")
String bucket;
private final GasPrice gasPrice;
public UpdateGasPrice(GasPrice gasPrice) {
this.gasPrice = gasPrice;
}
@Override
public void run(String... args) throws Exception {
InfluxDBClient client = InfluxDBClientFactory.create(url, token.toCharArray(), org, bucket)
.setLogLevel(LogLevel.BASIC);
WriteApi writeApi = client.makeWriteApi(WriteOptions.builder().flushInterval(2_000).build());
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
BigInteger gas = gasPrice.getGasPrice();
if (gas != null) {
BigDecimal value = BigDecimal.valueOf(gas.longValue() * 0.000000001).setScale(2, RoundingMode.HALF_UP);
Point point = Point
.measurement("eth")
.addField("gas", gas.longValue())
.addField("gasGwei", value)
.time(Instant.now(), WritePrecision.S);
System.out.println("Produced DataPoint: " + point.toLineProtocol());
writeApi.writePoint(point);
}
} catch (IOException ignored) {}
}
}, 0, 1000);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Close Client and Producer");
timer.cancel();
client.close();
}));
}
}
Run the code.
Open http://localhost:8086 and go to the InfluxDB management page. The password is in the env file.
You can see that gas data is being written to the database.
Get the Ethereum balance of an address.
It's simple on the Ethereum blockchain:
public BigDecimal getBalance(String address) throws IOException {
BigInteger balance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send().getBalance();
BigDecimal ether = Convert.fromWei(String.valueOf(balance), Convert.Unit.ETHER);
System.out.println(address + " balance: " + ether.toString());
return ether;
}
Get the balance of an ERC20 token:
public BigDecimal getErc20Balance(String address, String tokenContractAddress) throws Exception {
ReadonlyTransactionManager txManager = new ReadonlyTransactionManager(web3j, null);
DefaultGasProvider gasPriceProvider = new DefaultGasProvider();
ERC20 contract = ERC20.load(tokenContractAddress, web3j, txManager, gasPriceProvider);
BigInteger balance = contract.balanceOf(address).send();
BigDecimal ether = Convert.fromWei(String.valueOf(balance), Convert.Unit.ETHER);
System.out.println("Balance of " + address + ": " + ether);
return ether;
}
In this study, we only covered simple on-chain operations for reading. In the future, we will delve into contract interactions.