
Hola amigos, hoy les quiero hablar de la librería Web3j en la cual he hecho varias pruebas y me parece genial ya que es muy ligera y contiene todo lo necesario para crear aplicación y contratos inteligentes en la blockchain de ethereum.
¿Que es Web3j?
Es una biblioteca ligera escrita en Java 8, esta en una capa superior ya que necesita integrarse con un cliente o nodo de la red Ethereum, en mi entrada anterior Instalar un nodo testnet de ethereum rinkeby esta todo lo necesario para crear tu propio nodo y obtener monedas testnet para comenzar las pruebas.
Características principales:
- Interacción con los clientes Ethereum usando el API JSON-RPC dando soporte a todos los tipos de métodos disponibles
- Soporta todos los nodos Geth y Parity para administrar cuentas y firmar transacciones
- Envío de solicitudes de clientes de forma asíncrona y sincrónica
- Generación automática de wrappers de función de contrato inteligente Java de archivos Solidity ABI
Enlaces de interés:
Si necesitas más información acerca de este proyecto te dejo unos enlaces de la documentación oficial:
Creando una cuenta Ethereum en Java usando Web3j
Lo primero que vamos a hacer es crear una cuenta ethereum para recibir nuestro ether, para ello vamos a utilizar el api de web3j
public static String[] createNewWallet(String password) throws Exception {
try {
File file = new File(WALLET_PATH);
String name = null;
String json = null;
if (file.exists()) {
// al crear el monedero nos retorna el nombre del archivo generado dentro de la carpeta indicada
name = WalletUtils.generateFullNewWalletFile(password, file);
// vamos a abrir el monedero y retornar el json generado
Path path = FileSystems.getDefault().getPath(WALLET_PATH, name);
byte[] b = java.nio.file.Files.readAllBytes(path);
json = new String(b);
return new String[]{name, json};
} else {
throw new Exception("Invalid WALLET_PATH " + WALLET_PATH);
}
} catch (Exception ex) {
throw ex;
}
}
En la sentencia anterior podemos destacar la siguiente linea: WalletUtils.generateFullNewWalletFile(password, file); Como podemos ver hemos usando la clase WalletUtils y hemos creado una cuenta ethereum de una forma muy simple.
Invocamos nuestro método de la forma siguiente:
EthereumWallet e = EthereumWallet.getInstance();
String[] wallet = e.createNewWallet("123456");
for (String string : wallet) {
System.out.println(string);
}
El arreglo generado contiene las siguientes cadenas:
1.- Nombre del monedero:
UTC--2017-07-16T16-58-10.930000000Z--54f80f2634207fba30525ebdd779cedd7e745cb4.json
2.- JSON con los datos del monedero:
{
"address": "54f80f2634207fba30525ebdd779cedd7e745cb4",
"id": "c3484c26-54af-4c4f-baf5-a10caf813031",
"version": 3,
"crypto": {
"cipher": "aes-128-ctr",
"ciphertext": "1a70945a3f05a3c2e2bb3def0cd3b88b735462067ebe5acc1f63a74f6b98bb15",
"cipherparams": {
"iv": "d3d05d62e600f3d1f91f64856db9a8c2"
},
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "ba10abe0b7adb51bc52dacc477ebeb5e3008d5f72fa60bfee3a6d409483248fd"
},
"mac": "96ae81b9a7233c544cca25616f0b85adad7b9ec9b1e5a2e81dc686d7e4576086"
}
}
Las cuentas en ethereum son ficheros JSON encriptados con una contraseña. Este fichero debe guardarse con mucho cuidado y en un lugar seguro ya que podrias perder tus ether.
Abrir una cuenta Ethereum en Java usando Web3j
Ahora queremos comenzar a utilizar nuestra cuenta ethereum para ello vamos a ver el siguiente método:
public Credentials openWallet(String password, String walletName) throws Exception {
Credentials credentials = WalletUtils.loadCredentials(password, WALLET_PATH + walletName);
return credentials;
}
Hemos usado nuevamente la clase WalletUtils, esta vez el método loadCredentials que necesita el nombre de la cuenta ethereum y la contraseña. Este nos retorna un objeto Credencial el cual nos da acceso a administrar nuestros ether de ese monedero.
Este seria un ejemplo para utilizar el método anterior:
Credentials credentials = e.openWallet("123456", "UTC--2017-07-16T16-58-10.930000000Z--54f80f2634207fba30525ebdd779cedd7e745cb4.json");
String addressSender = credentials.getAddress();
System.out.println(addressSender);
Crear una instancia del objecto Web3j
Para comenzar a usar los métodos de webj3 necesitamos conectarnos al nodo que tenemos en ejecución, recordando mi post anterior hemos colocado el socket IPC en la siguiente ruta «/opt/apps/geth.ipc», ahora bien si queremos conectarnos directamente al socket y tener los eventos de la blockchain en tiempo real usaremos:
private final static String IPC_SOCKET_PATH = "/opt/apps/geth.ipc"; web3j = Web3j.build(new UnixIpcService(IPC_SOCKET_PATH));
O bien si no queremos usar Socket podemos usar HTTP con las siguiente sentencia:
private final static String URL_JSON_RPC = "http://127.0.0.1:38904"; web3j = Web3j.build(new HttpService(URL_JSON_RPC));
Ver el balance o saldo de una cuenta Ethereum en Java usando Web3j
Ahora si a todos se nos viene la primera pregunta y ¿Como vemos el saldo de nuestro monedero? Vamos a crear el siguiente método que nos retorna el saldo del monedero.
public BigInteger getAddressBalance(String address) throws Exception {
try {
// Vamos a enviar una solicitud asíncrona usando el objecto web3j
EthGetBalance ethGetBalance = web3j
.ethGetBalance(address, DefaultBlockParameterName.LATEST)
.sendAsync()
.get();
// saldo en wei
BigInteger wei = ethGetBalance.getBalance();
return wei;
} catch (Exception ex) {
throw ex;
}
}
Ya estamos utilizando el objecto web3j en esta caso hemos usado web3j .ethGetBalance el cual en realidad hace un llamado al Api de Ethereum como se ve a continuación:
{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x54f80f2634207fba30525ebdd779cedd7e745cb4","latest"],"id":1}
Vamos a ver un ejemplo del uso del método que hemos creado:
BigInteger wei = e.getAddressBalance(addressSender);
System.out.println(wei + " wei"); // Salida en wei
BigDecimal x18 = new BigDecimal("1000000000000000000");
BigDecimal bigDecimal = new BigDecimal(wei);
BigDecimal result = bigDecimal.divide(x18);
System.out.println(result + " ether"); // salida en ether
Recibiendo ether en nuestra cuenta Ethereum usando Web3j:
Para recibir ether tenemos los métodos Observable que implementa Java RX para el manejo de eventos, vamos a crear el siguiente método:
public void onTransactionReceived(Transaction tx) {
BigInteger wei = tx.getValue();
BigDecimal ether = Convert.fromWei(new BigDecimal(tx.getValue()), Convert.Unit.ETHER);
String to = tx.getTo();
String hash = tx.getHash();
String from = tx.getFrom();
if (addressMap.containsKey(tx.getTo())) {
// credito
System.out.println("Se ha recibido su depósito de " + ether + " ether enviado a su cuenta " + to + " enviado por " + from + " tx " + hash);
} else if (addressMap.containsKey(tx.getFrom())) {
//debito
System.out.println("Se ha hecho un retiro de " + ether + " ether desde su cuenta " + to + " hacia la cuenta destino " + from + " tx " + hash);
}
}
Creamos el objecto Subscription para ver los eventos de web3j con:
subscription = web3j.blockObservable(false).subscribe(block -> {
blockObservable(block);
}, Throwable::printStackTrace, () -> System.out.println("block done"));
web3j.transactionObservable().subscribe(tx -> {
transactionObservable(tx);
}, Throwable::printStackTrace, () -> System.out.println("tx done"));
web3j.pendingTransactionObservable().subscribe(tx -> {
pendingTransactionObservable(tx);
}, Throwable::printStackTrace, () -> System.out.println("ptx done"));
Ahora si recarguemos nuestro monedero en nuestro caso he enviado 0.03 ether desde la linea de comando de geth, en la cual tenemos saldo:
> eth.accounts[0];
"0xe2632021f255da92be0c0434855a63acbcbae286"
> var sender = eth.accounts[0];
undefined
> var receiver = '0x54f80f2634207fba30525ebdd779cedd7e745cb4'
undefined
> var amount = web3.toWei(0.03, "ether")
undefined
> personal.unlockAccount(sender, "123456")
> eth.sendTransaction({from:sender, to:receiver, value: amount})
"0xcc6403a6179698a653419cccac7458445ff318482054b6bfb0141e9323c9382e"
y lo hemos recibido en nuestro monedero :
Se ha recibido su depósito de 0.03 ether enviado a su cuenta 0x54f80f2634207fba30525ebdd779cedd7e745cb4 enviado por 0xe2632021f255da92be0c0434855a63acbcbae286 tx 0xcc6403a6179698a653419cccac7458445ff318482054b6bfb0141e9323c9382e
Enviar ether desde nuestra cuenta Ethereum usando Web3j:
Ahora ya que ya tenemos balance en nuestra cuenta podemos enviar ethers usando el siguiente método:
public TransactionReceipt sendCoins(Credentials credentials, String toAddress, BigDecimal value) throws Exception {
try {
TransactionReceipt transactionReceipt = Transfer.sendFunds(web3j, credentials, toAddress, value, Convert.Unit.ETHER);
return transactionReceipt;
} catch (Exception ex) {
throw ex;
}
}
Ahora vamos a enviar ether usando el método creado
e.sendCoins(credentials, sendAccount, new BigDecimal(ethers));
Y vemos como se dispara el evento en nuestra billetera.
Se ha hecho un retiro de 0.02 ether desde su cuenta 0xe2632021f255da92be0c0434855a63acbcbae286 hacia la cuenta destino 0x54f80f2634207fba30525ebdd779cedd7e745cb4 tx 0x9b0c842a143678cc7aeca7818ed82ea414727605e409a540bb84d92ca62b36c7
Ver transacciones de mi monedero usando ETHERSCAN
Ahora quiero listar todas las entradas y salidas de mi cuenta, de momento no he podido hacerlo con el api de web3j y al parecer es un poco mas complejo ya que tendríamos que desarrollar un script en la consola de geth. Por ahora vamos a usar el API de ETHERSCAN
private final static String ENDPOINT_TRANSACTIONS_LIST = "/api?module=account&action=txlist";
private final static String API_RESTFUL_URI = "http://rinkeby.etherscan.io";
public static Transactions getTransactionsByAddress(String address) {
String endpoint = API_RESTFUL_URI + ENDPOINT_TRANSACTIONS_LIST;
Client client = ClientBuilder.newClient();
Response response = client.target(endpoint)
.queryParam("address", address)
.queryParam("startblock", "0")
.queryParam("endblock", "99999999")
.queryParam("sort", "asc")
.queryParam("apikey", ETHERSCAN_APIKEY)
.request(MediaType.APPLICATION_JSON)
.get();
String json = response.readEntity(String.class);
client.close();
ObjectMapper mapper = new ObjectMapper();
Transactions transactions = null;
if (response.getStatus() == 200) {
try {
transactions = mapper.readValue(json, Transactions.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return transactions;
}
Probamos el método con la siguiente sentencia de código:
Transactions tx = EtherscanService.getTransactionsByAddress(addressSender);
List<Result> list = tx.getResult();
for (Result result : list) {
System.out.print(" BlockHash " + result.getBlockHash());
}
He creado un pequeño monedero muy básico en ethereum con webj3 si quieres puedes hacer un fork del código desde Github o si quieres colaborar eres bienvenido.
Si te gusta lo que publico puedes seguirme en las redes sociales y unos wei o satoshis son bienvenidos!