WEB3DEV

Cover image for Meta-transações usando o Web3j
Rafael Ojeda
Rafael Ojeda

Posted on

Meta-transações usando o Web3j

Meta-transações usando o Web3j

Image description

As meta-transações são uma forma popular de permitir que os usuários realizem transações em uma blockchain sem pagar diretamente as taxas de gás. Em vez disso, eles assinam uma mensagem fora da cadeia, que é então retransmitida por um retransmissor que paga as taxas de gás.

Image description
Diagrama de interação

Os atores deste esquema são:

  • Usuário (User): assina uma meta-transação (que é uma mensagem contendo informações sobre a transação que ele gostaria de executar).
  • Intermediário (Relayer): um servidor web com uma carteira que assina uma transação Ethereum válida (que possui a meta-transação como carga útil) e a envia para a blockchain.
  • Encaminhador (Forwarder): um contrato Ethereum responsável por verificar a assinatura da meta-transação e, como era de se esperar, encaminha o pedido para um contrato destinatário.
  • Destinatário (Recipient): o contrato Ethereum que o usuário pretendia chamar sem pagar a taxa de gás; este contrato deve ser capaz de preservar a identidade do usuário que solicitou inicialmente a transação.

O contrato do encaminhador

O mecanismo básico é fornecido pela implementação do MinimalForwarder do OpenZeppelin, a ser usado em conjunto com um contrato compatível com ERC2771 como o contrato do destinatário.

Contrato do destinatário

Usaremos o contrato de destinatário abaixo para nosso projeto de amostra, que é uma implementação do 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: JSON de Dados Estruturados com Tipagem

Para permitir a assinatura da solicitação de meta-transação, é preciso criar um JSON com dados estruturados, que deve seguir o seguinte formato:

{

  "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

O JSON acima especifica o separador de domínio EIP712 e os tipos de mensagem para uma meta-transação usando a estrutura ForwardRequest, que possui os seguintes campos:

  • from (endereço): o endereço do remetente
  • to (endereço): o endereço do contrato destinatário
  • value (uint256): a quantidade de Ether a ser enviada (0 no nosso caso)
  • gas (uint256): o limite de gás para a transação
  • nonce (uint256): o nonce do remetente
  • data (bytes): a chamada de função codificada em ABI para o contrato destinatário.

Implementação do Web3j

Crie uma instância do Web3j e conecte-se a um nó:

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

Os contratos inteligentes minimalForwarder e recipient podem ser compilados usando o web3j-sokt.

Os wrappers Java dos contratos inteligentes do Solidity podem ser gerados usando o web3j-maven-plugin ou o web3j-gradle-plugin, e essas classes Java serão usadas nas etapas restantes.

Carregue os contratos MinimalForwarder e Recipient usando os respectivos endereços de contrato, supondo que você já tenha implantado os contratos MinimalForwarder e Recipient.

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

Defina a função do destinatário e codifique-a:

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

Obtenha o nonce atual para o contrato MinimalForwarder:

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

Crie um 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

Agora, leia o json de dados estruturados digitados do EIP712, adicione os valores vazios e assine-o 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

Obter assinatura em 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

Agora, execute a meta-transação e verifique se a saída está correta:

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

Se você observar o resultado de recipient.color().send(), verá que ele apresenta "blue", que foi o parâmetro passado pela meta-transação e, portanto, confirma que tudo está funcionando conforme o esperado.

As meta-transações oferecem uma maneira conveniente de os usuários interagirem com uma blockchain sem se preocuparem com as taxas de gás. Ao utilizar o Web3j e os contratos de exemplo fornecidos, os desenvolvedores podem testar a funcionalidade da meta-transação e implementá-la em seus próprios dApps.

Se você estiver interessado em implementar e experimentar as meta-transações usando o Web3j, recomendamos que se aprofunde no tutorial fornecido e explore os contratos de exemplo. Familiarize-se também com a documentação do Web3j para entender como interagir com os contratos inteligentes da Ethereum usando Java.

Referências -

  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

Este artigo foi escrito por Nischal Sharma e traduzido para o português por Rafael Ojeda

Você pode ler o artigo original aqui

Top comments (0)