Transactions sans paiement de gaz sur Ethereum
Published: 2023-01-06 18:14:57

---
title: Transactions sans paiement de gaz sur Ethereum
published: true
description: 
tags: 
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3bafqgrjeiyf3ip2aci.png
# Use a ratio of 100:42 for best results.
# published_at: 2022-12-20 01:25 +0000
---

L'Account Abstraction, les Rollups et la confidentialité sur le Blockchain sont possibles grâce à la capacité d'exécuter des transactions au nom de quelqu'un d'autre de manière sécurisée. Dans cette vidéo, nous allons créer une transaction sans payer de gaz qui sera exécutée par un Relayer en utilisant un contrat intelligent de vérification. Tout cela se fait de manière sécurisée grâce à la cryptographie. J'espère que cette vidéo vous aidera à comprendre vers où se dirige la chaîne de blocs.

{%youtube qh22MTvaVCg%}

## Avant de commencer
Pour ce tutoriel tu vas avoir besoin de [NodeJs](https://nodejs.org/es/), je vous conseille de le télécharger avec [NVM](https://github.com/nvm-sh/nvm#installing-and-updating), de plus tu auras besoin de [Metamask](https://metamask.io/) ou n'importe quel wallet compatible avec l'EVM et aussi du GoerliETH que tu peux trouver gratuitement dans un [faucet](https://goerlifaucet.com/). Vous aurrez aussi besoin d'une [Infura API Key](https://app.infura.io/).

## Le contrat intelligent de vérification

Nous lancerons d'abord le contrat de vérification sur 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;
    }
}
```

## Le Frontend

Ensuite, nous construisons le frontend qui consiste en un fichier HTML et JS. Le frontend est l'interface qui nous permet de signer des transactions et de les envoyer au 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, n'oubliez pas de définir la variable `MY_CONTRACT_ADDRESS` avec le contrat que vous venez de lancer.

`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!")
}
```

## Le Backend Relayer

Voici un exemple de backend qui s'occupe de transmettre des transactions au Blockchain.

N'oubliez pas de définir la variable `CONTRACT_ADDRESS` avec le contrat que vous venez de lancer. Et `BACKEND_WALLET_ADDRESS` avec la wallet qui paiera les fonds.

`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()
```

Nous devrons également ajouter le fichier `json_abi/Contract.json` qui contient le Json ABI du contrat que nous venons de lancer.

`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"
	}
]
```

Et n'oubliez pas également d'ajouter un `.env` avec votre url RPC et votre clé privée.

`.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"
  }
}
```

Enfin, nous installons les dépendances:

```bash
npm install
```

Ou, alternativement, nous installons manuellement les dépendances: `npm install @alch/alchemy-web3 dotenv node-fetch`.

## Tester la DApp

Pour démarrer le frontend

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

Pour démarrer le backend Relayer

```bash
node backend.js
```

Vous pouvez maintenant signer et relayer des transactions

**¡Merci pour voir cette video!**

Suivez nous ici en dev.to et en [Youtube](https://www.youtube.com/channel/UCl3hXoRETXvoTWI-j0Y6ZTw) pour tout ce qui a à voir avec Blockchain en Francais.