Transacciones sin Pagar Gas en Ethereum
Published: 2023-01-06 18:07:23

---
title: Transacciones sin Pagar Gas en Ethereum
published: true
description: 
tags: 
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/615jkpdz0x1kt4dyyfjq.png
# Use a ratio of 100:42 for best results.
# published_at: 2022-12-20 01:25 +0000
---

El Account Abstraction, los Rollups y la privacidad en el blockchain son posibles gracias a la capacidad de ejecutar transacciones en nombre de otra persona de manera segura. En este video, vamos a crear una transacción sin pagar gas que será procesada por un Relayer utilizando un Smart Contract Verificador. Todo esto se realiza de manera segura gracias a la criptografía. Espero que este video les ayude a comprender hacia dónde se está dirigiendo el blockchain.

{%youtube 63NtTSxC0xY%}

## Antes de comenzar

Para este tutorial ocuparás [NodeJs](https://nodejs.org/es/) que recomiendo descargarlo en Linux via [NVM](https://github.com/nvm-sh/nvm#installing-and-updating), y también necesitarás [Metamask](https://metamask.io/) u otra wallet compatible con fondos en Goerli que puedes obetener desde un [faucet](https://goerlifaucet.com/). Adicionalmente ocuparás una llave RPC que puedes conseguir gratuitamente en [Infura](https://app.infura.io/).

## El Smart Contract Verificador

Primero lanzaremos el contrato verificador en Goerli Testnet.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
using ECDSA for bytes32;

contract VerifierHelloWorld  {
    string public hello = "Hello world!";
    address public helloSetter;

    function _verify(bytes32 helloHash, address _helloSetter, bytes memory signature) internal pure returns (bool)
    {
        return helloHash
            .toEthSignedMessageHash()
            .recover(signature) == _helloSetter;
    }

    function relaySetHello(string memory _hello, address _helloSetter, bytes memory signature) public
    {
        require(_verify(bytes32(abi.encodePacked(_hello)), _helloSetter, signature), "Invalid signature");
        hello = _hello;
        helloSetter = _helloSetter;
    }
}
```

## El Frontend

Luego construimos el frontend que consta del archivo de HTML y JS. El frontend es la interfaz que nos permite firmar transacciones y enviarlas al Relayer.

`index.html`
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div>
    <h3>Hello Verifier</h3>
    <input id="connect_button" type="button" value="Connect" onclick="connectWallet()" style="display: none"></input>
    <p id="account_address" style="display: none"></p>
    <p id="web3_message"></p>
    <p id="contract_state"></p>
    <input type="text" id="_hello"></input>
    <button type="button" id="sign" onclick="_signMessage()" value="Hello!">Sign</button>
    <p id="hashed_message"></p>
    <p id="signature"></p>
    <h4>Hello Verifier</h4>
    <span>Hello</span><br>
    <input type="text" id="_helloRelay"></input><br>
    <span>Hello Setter</span><br>
    <input type="text" id="_helloSetterRelay"></input><br>
    <span>Signature</span><br>
    <input type="text" id="_signatureRelay"></input><br>
    <button type="button" id="relay" onclick="_relaySetHello()">Relay</button>
  </div>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.3.5/web3.min.js"></script>
  <script type="text/javascript" src="blockchain_stuff.js"></script>
</body>

  <script>
    function _signMessage()
    {
      _hello = document.getElementById("_hello").value
      signMessage(_hello)
    }

    function _relaySetHello()
    {
      _helloRelay = document.getElementById("_helloRelay").value
      _helloSetterRelay = document.getElementById("_helloSetterRelay").value
      _signatureRelay = document.getElementById("_signatureRelay").value
      relaySetHello(_helloRelay, _helloSetterRelay, _signatureRelay)
    }
  </script>
</html>
```

En Javascript, recuerda establecer la variable `MY_CONTRACT_ADDRESS` con el contrato que recién lanzaste.

`blockchain_stuff.js`
```js
const NETWORK_ID = 5

const MY_CONTRACT_ADDRESS = "0xE59da879e33b71C145b7c526a7B8C5b93195C51D"
const MY_CONTRACT_ABI_PATH = "./json_abi/Contract.json"
var my_contract

var accounts
var web3

function metamaskReloadCallback() {
  window.ethereum.on('accountsChanged', (accounts) => {
    document.getElementById("web3_message").textContent="Se cambió el account, refrescando...";
    window.location.reload()
  })
  window.ethereum.on('networkChanged', (accounts) => {
    document.getElementById("web3_message").textContent="Se el network, refrescando...";
    window.location.reload()
  })
}

const getWeb3 = async () => {
  return new Promise((resolve, reject) => {
    if(document.readyState=="complete")
    {
      if (window.ethereum) {
        const web3 = new Web3(window.ethereum)
        window.location.reload()
        resolve(web3)
      } else {
        reject("must install MetaMask")
        document.getElementById("web3_message").textContent="Error: Porfavor conéctate a Metamask";
      }
    }else
    {
      window.addEventListener("load", async () => {
        if (window.ethereum) {
          const web3 = new Web3(window.ethereum)
          resolve(web3)
        } else {
          reject("must install MetaMask")
          document.getElementById("web3_message").textContent="Error: Please install Metamask";
        }
      });
    }
  });
};

const getContract = async (web3, address, abi_path) => {
  const response = await fetch(abi_path);
  const data = await response.json();

  const netId = await web3.eth.net.getId();
  contract = new web3.eth.Contract(
    data,
    address
    );
  return contract
}

async function loadDapp() {
  metamaskReloadCallback()
  document.getElementById("web3_message").textContent="Please connect to Metamask"
  var awaitWeb3 = async function () {
    web3 = await getWeb3()
    web3.eth.net.getId((err, netId) => {
      if (netId == NETWORK_ID) {
        var awaitContract = async function () {
          my_contract = await getContract(web3, MY_CONTRACT_ADDRESS, MY_CONTRACT_ABI_PATH)
          document.getElementById("web3_message").textContent="You are connected to Metamask"
          onContractInitCallback()
          web3.eth.getAccounts(function(err, _accounts){
            accounts = _accounts
            if (err != null)
            {
              console.error("An error occurred: "+err)
            } else if (accounts.length > 0)
            {
              onWalletConnectedCallback()
              document.getElementById("account_address").style.display = "block"
            } else
            {
              document.getElementById("connect_button").style.display = "block"
            }
          });
        };
        awaitContract();
      } else {
        document.getElementById("web3_message").textContent="Please connect to Goerli";
      }
    });
  };
  awaitWeb3();
}

async function connectWallet() {
  await window.ethereum.request({ method: "eth_requestAccounts" })
  accounts = await web3.eth.getAccounts()
  onWalletConnectedCallback()
}

loadDapp()

const onContractInitCallback = async () => {
  var hello = await my_contract.methods.hello().call()
  var helloSetter = await my_contract.methods.helloSetter().call()

  var contract_state = "Hello: " + hello
    + ", helloSetter: " + helloSetter

  document.getElementById("contract_state").textContent = contract_state;
}

const onWalletConnectedCallback = async () => {
}

// Sign and Relay functions

async function signMessage(message)
{
  const hashedMessage = Web3.utils.asciiToHex(message).padEnd(66,'0');

  const signature = await ethereum.request({
    method: "personal_sign",
    params: [hashedMessage, accounts[0]],
  });

  //document.getElementById("hashed_message").textContent="Hashed Message: " + hashedMessage.padEnd(66,'0');
  document.getElementById("signature").textContent="Signature: " + signature;

  // split signature
  /*
  const r = signature.slice(0, 66);
  const s = "0x" + signature.slice(66, 130);
  const v = parseInt(signature.slice(130, 132), 16);
  console.log({ r, s, v });
  */
}

async function relaySetHello(helloRelay, helloSetterRelay, signatureRelay)
{
  var url = "http://localhost:8080/relay?"
  url += "hello=" + helloRelay
  url += "&helloSetter=" + helloSetterRelay
  url += "&signature=" + signatureRelay

  const myRequest = new Request(url, {
    method: 'GET',
    headers: new Headers(),
    mode: 'cors',
    cache: 'default',
  });

  fetch(myRequest);

  alert("Message sent!")
}
```

## El Relayer Backend

Ahora un ejemplo de un backend que se encarga de transmitir transacciones al blockchain.

Recuerda establecer la variable `CONTRACT_ADDRESS` con el contrato que recién lanzaste. Y `BACKEND_WALLET_ADDRESS` con la wallet que pagará los fondos.

`backend.js`
```js
import createAlchemyWeb3 from "@alch/alchemy-web3"
import dotenv from "dotenv"
import fs from "fs"
import cors from "cors"
import express from "express"

const app = express()
dotenv.config();

const CONTRACT_ADDRESS = "0xE59da879e33b71C145b7c526a7B8C5b93195C51D"
const BACKEND_WALLET_ADDRESS = "0x18747BE67c5886881075136eb678cEADaf808028"
const JSON_CONTRACT_PATH = "./json_abi/Contract.json"
const PORT = 8080
var web3 = null
var contract = null

const loadContract = async (data) => {
  data = JSON.parse(data);

  const netId = await web3.eth.net.getId();
  contract = new web3.eth.Contract(
    data,
    CONTRACT_ADDRESS
  );
}

async function initAPI() {
  const { GOERLI_RPC_URL, PRIVATE_KEY } = process.env;
  web3 = createAlchemyWeb3.createAlchemyWeb3(GOERLI_RPC_URL);

  fs.readFile(JSON_CONTRACT_PATH, 'utf8', function (err,data) {
    if (err) {
      return console.log(err);
    }
    loadContract(data, web3)
  });

  app.listen(PORT, () => {
    console.log(`Listening to port ${PORT}`)
  })
  app.use(cors({
    origin: '*'
  }));
}

async function relaySetHello(hello, helloSetter, signature)
{
  const nonce = await web3.eth.getTransactionCount(BACKEND_WALLET_ADDRESS, 'latest'); // nonce starts counting from 0
  const transaction = {
   'from': BACKEND_WALLET_ADDRESS,
   'to': CONTRACT_ADDRESS,
   'value': 0,
   'gas': 300000,
   'nonce': nonce,
   'data': contract.methods.relaySetHello(
     hello,
     helloSetter,
     signature)
     .encodeABI()
  };
  const { GOERLI_RPC_URL, PRIVATE_KEY } = process.env;
  const signedTx = await web3.eth.accounts.signTransaction(transaction, PRIVATE_KEY);

  web3.eth.sendSignedTransaction(signedTx.rawTransaction, function(error, hash) {
    if (!error) {
      console.log("🎉 The hash of your transaction is: ", hash, "\n");
    } else {
      console.log("❗Something went wrong while submitting your transaction:", error)
    }
  });
}

//http://localhost:8080/relay?helloSetter=0x18747BE67c5886881075136eb678cEADaf808028&hello=hola&signature=0x6903cb647fb3d47b91e8aecc8adc686466557d5edf96814e2b21c745f455a8502e895e696c59f8d65fd9bb57e4f202d45bb6a40c07bc8fd283d666f31264ce411b
app.get('/relay', (req, res) => {
  var hello = req.query["hello"]
  var helloSetter = req.query["helloSetter"]
  var signature = req.query["signature"]
  var message = helloSetter + " setted hello to " + " " + hello
  relaySetHello(hello, helloSetter, signature)
  res.setHeader('Content-Type', 'application/json');
  res.send({
    "message": message
  })
})
initAPI()
```

Necesitaremos agregar también el archivo `json_abi/Contract.json` que contiene el Json ABI del contrato que recién lanzamos.

`json_abi/Contract.json`

```json
[
	{
		"inputs": [],
		"name": "hello",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "helloSetter",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "_hello",
				"type": "string"
			},
			{
				"internalType": "address",
				"name": "_helloSetter",
				"type": "address"
			},
			{
				"internalType": "bytes",
				"name": "signature",
				"type": "bytes"
			}
		],
		"name": "relaySetHello",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	}
]
```

Y recuerda también agregar un `.env` con tu url RPC y tu llave privada.

`.env`
```bash
GOERLI_RPC_URL=YOURURLHERE
PRIVATE_KEY=YOURKEYHERE
```

`package.json`
```json
{
  "name": "relayer-demo",
  "version": "1.0.0",
  "description": "",
  "main": "backend.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node backend.js"
  },
  "keywords": [],
  "author": "Filosofía Código",
  "license": "MIT",
  "dependencies": {
    "@alch/alchemy-web3": "^1.4.7",
    "dotenv": "^16.0.3",
    "node-fetch": "^3.3.0"
  }
}
```

Finalmente instalamos las dependencias:

```bash
npm install
```

O alternativamente instalamos las dependencias manualmente: `npm install @alch/alchemy-web3 dotenv node-fetch`.

## Probar la DApp

Para levantar el frontend.

```bash
npm install -g lite-server
lite-server
```

Para levantar el relayer backend
```bash
node backend.js
```

Ahora puedes firmar y relayear transacciones.

**¡Gracias por ver este tutorial!**

Sígueme en dev.to y en [Youtube](https://www.youtube.com/channel/UCNRB4tgwp09z4391JRjEsRA) para todo lo relacionado al desarrollo en Blockchain en Español.