WEB3DEV Español

Cover image for Meta Transacciones Usando Web3j
Paulo Gio
Paulo Gio

Posted on

Meta Transacciones Usando Web3j

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*0nX2fGzc1m9YZubdZT1nig.png

Las meta transacciones son una manera popular de permitir a los usuarios realizar transacciones en una blockchain sin pagar directamente los costos de gas. En lugar de eso, firman un mensaje fuera de la cadena, que luego es retransmitido por un retransmisor que paga las tarifas de gas.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*ka5xwH4MfZDRwSejfiYNvw.png

Diagrama de Interacción

Los actores de este esquema son:

  • User (Usuario): firma una meta transacción (que es un mensaje que contiene información sobre la transacción que le gustaría ejecutar).
  • Relayer (Retransmisor): un servidor web con una cartera que firma una transacción válida de Ethereum (que tiene la meta transacción como carga útil) y la envía a la blockchain.
  • Forwarder (Redireccionador): un contrato de Ethereum encargado de verificar la firma de la meta transacción que, como es de esperarse, reenvía la solicitud a un contrato receptor.
  • Recipient (Receptor): el contrato de Ethereum que el usuario pretendía llamar sin pagar la tarifa de gas, este contrato tiene que ser capaz de preservar la identidad del usuario que solicitó inicialmente la transacción.

El contrato Redireccionador

El mecanismo básico está dado por la implementación de MinimalForwarder de OpenZeppelin, para ser usado junto con un contrato compatible con ERC2771 como el contrato Recipient.

Contrato Receptor

Usaremos el siguiente contrato Recipient para nuestro proyecto de muestra, que es una implementación del contrato ERC2771.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/metatx/ERC2771Context.sol";

contract Recipient is ERC2771Context {

   event FlagCaptured(address previousHolder, address currentHolder, string color);

   address public currentHolder  = address(0);
   string public color = "white";

   constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}

   function setFlagOwner(string memory _color) external {
       address previousHolder = currentHolder;
       currentHolder = _msgSender();
       color = _color;
       emit FlagCaptured(previousHolder, currentHolder, color);
   }

   function getFlagOwner() external view returns (address, string memory) {
       return (currentHolder, color);
   }
}
Enter fullscreen mode Exit fullscreen mode

EIP-712: Datos estructurados tipados JSON

Para poder firmar la solicitud de meta transacción, se debe crear un JSON de datos estructurados y debería verse así:

{
 "types": {
   "EIP712Domain": [
     {"name": "name", "type": "string"},
     {"name": "version", "type": "string"},
     {"name": "chainId", "type": "uint256"},
     {"name": "verifyingContract", "type": "address"}
   ],
   "ForwardRequest": [
     {"name": "from", "type": "address"},
     {"name": "to", "type": "address"},
     {"name": "value", "type": "uint256"},
     {"name": "gas", "type": "uint256"},
     {"name": "nonce", "type": "uint256"},
     {"name": "data", "type": "bytes"}
   ]
 },
 "primaryType": "ForwardRequest",
 "domain": {
   "name": "MinimalForwarder",
   "version": "0.0.1",
   "chainId": <network_chain_id>,
   "verifyingContract": "<forwarder_contract_address>"
 },
 "message": {
   "from": "<user_address>",
   "to": "<recipient_contract_address>",
   "value": 0,
   "gas": 210000,
   "nonce": "",
   "data": ""
 }
}
Enter fullscreen mode Exit fullscreen mode

Los datos JSON anteriores especifican el separador de dominio EIP712 y los tipos de mensajes para una meta transacción utilizando la estructura ForwardRequest, que tiene los siguientes campos:

  • from (dirección): la dirección del remitente
  • to (dirección): la dirección del contrato receptor
  • value (uint256): la cantidad de Ether a enviar (0 en nuestro caso)
  • gas (uint256): el límite de gas para la transacción
  • nonce (uint256): el nonce del remitente
  • data (bytes): la llamada a la función del contrato receptor codificada en ABI

Implementación con Web3j

Crear una instancia Web3j y conectarla a un nodo:

Web3j web3j = Web3j.build(new HttpService("<network_http_rpc_endpoint>"));
Enter fullscreen mode Exit fullscreen mode

Los contratos inteligentes MinimalForwarder y Recipient se pueden compilar usando web3j-sokt.se.

Se pueden generar envoltorios (wrappers) Java de contratos inteligentes Solidity usando web3j-maven-plugin o web3j-gradle-plugin. Estas clases Java se usarán en los pasos restantes.

Cargar el contrato MinimalForwarder y el contrato Recipient usando las respectivas direcciones de contrato, asumiendo que ya has desplegado el contrato MinimalForwarder y el contrato receptor.

MinimalForwarder minimalForwarder = MinimalForwarder.load("<minimalForwarder_contract_address>", web3j, credentials, new StaticGasProvider(BigInteger.valueOf(4_100_000_000L),BigInteger.valueOf(6_721_975L)));
Recipient recipient = Recipient.load("<recipient_contract_address>", web3j, credentials, new StaticGasProvider(BigInteger.valueOf(4_100_000_000L),BigInteger.valueOf(6_721_975L)));
Enter fullscreen mode Exit fullscreen mode

Definir la función del receptor y codificarla:

final Function recipientFunction = new Function(
       "setFlagOwner",
       List.of(new org.web3j.abi.datatypes.Utf8String("blue")),
       Collections.emptyList()) {
   };

String encodedFunction = FunctionEncoder.encode(recipientFunction);
Enter fullscreen mode Exit fullscreen mode

Obtener el nonce actual para el contrato MinimalForwarder:

BigInteger nonce = minimalForwarder.getNonce(credentials.getAddress()).send();
Enter fullscreen mode Exit fullscreen mode

Crear un objeto ForwardRequest:

MinimalForwarder.ForwardRequest forwardRequest = new MinimalForwarder.ForwardRequest(
       credentials.getAddress(),
       recipient.getContractAddress(),
       BigInteger.ZERO,
       BigInteger.valueOf(210000),
       nonce,
       Numeric.hexStringToByteArray(encodedFunction));
Enter fullscreen mode Exit fullscreen mode

Ahora lee el JSON de datos estructurados tipados EIP712, agregue los valores vacíos y firme usando Sign.signTypedData():

String jsonMessageString = Files.readString(Paths.get("src/main/resources/data.json").toAbsolutePath());
JSONObject jsonObject = new JSONObject(jsonMessageString);
jsonObject.getJSONObject("message").put("from", credentials.getAddress());
jsonObject.getJSONObject("message").put("nonce", nonce);
jsonObject.getJSONObject("message").put("data", encodedFunction);

String modifiedJsonString = jsonObject.toString();
Sign.SignatureData signature = Sign.signTypedData(modifiedJsonString, credentials.getEcKeyPair());
Enter fullscreen mode Exit fullscreen mode

Obtener la firma en Bytes:

byte[] retval = new byte[65];
System.arraycopy(signature.getR(), 0, retval, 0, 32);
System.arraycopy(signature.getS(), 0, retval, 32, 32);
System.arraycopy(signature.getV(), 0, retval, 64, 1);
Enter fullscreen mode Exit fullscreen mode

Ahora ejecuta la meta transacción y verifica si la salida es correcta:

minimalForwarder.execute(forwardRequest, getSignatureBytes(retval, BigInteger.valueOf(210000)).send();
System.out.println(recipient.color().send());  // devuelve color "azul"
Enter fullscreen mode Exit fullscreen mode

Si ves la salida a recipient.color().send(), te dará “azul”, que fue el parámetro pasado a través de la meta transacción y por lo tanto, confirma que todo está funcionando como se esperaba.

Las meta transacciones ofrecen una manera conveniente para que los usuarios interactúen con una blockchain sin preocuparse por las tarifas de gas. Al utilizar el Web3j y los contratos de ejemplo proporcionados, los desarrolladores pueden probar la funcionalidad de meta transacciones e implementarlas en sus propios dApps.

Si estás interesado en implementar y experimentar con meta transacciones usando Web3j, te recomendamos profundizar en el tutorial proporcionado y explorar los contratos de ejemplo. También familiarízate con la documentación de Web3j para entender cómo interactuar con contratos inteligentes de Ethereum usando Java.

Referencias

  1. https://docs.web3j.io/4.10.0/use_cases/meta_transaction/
  2. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/MinimalForwarder.sol
  3. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/ERC2771Context.sol
  4. https://github.com/donpabblo/meta-transaction/blob/main/contracts/Recipient.sol
  5. https://medium.com/coinmonks/gas-free-transactions-meta-transactions-explained-f829509a462d

Artículo original publicado por Nischal Sharma. Traducción de Paulinho Giovannini.

Discussion (0)