Permit Smart Contracts with EIP 712
Published: 2023-02-04 01:51:49

---
title: Permit Smart Contracts with EIP 712
published: true
description: 
tags: 
# cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/75echgtdmcqa691rzj5g.png
# Use a ratio of 100:42 for best results.
# published_at: 2022-12-20 01:25 +0000
---

Account Abstraction, Rollups, and privacy on blockchain are possible thanks to the ability to execute transactions on behalf of someone else securely. In this blog post, we are going to create a "Permit" setup where transactions will be executed by a Relayer using a Verifying Smart Contract. All of this is done securely thanks to cryptography. I hope this video helps you understand where blockchain is headed.

## Before we start

For this tutorial you will need [NodeJs](https://nodejs.org/es/) that I recommend downloading it from Linux via [NVM](https://github.com/nvm-sh/nvm#installing-and-updating), and also you will need [Metamask](https://metamask.io/) or another compatible wallet with Goerli funds that you can get from a [faucet](https://goerlifaucet.com/). You will also need an  [Infura API Key](https://app.infura.io/).

## The Verifying Smart Contract

First, we will launch the verifying contract on Goerli Testnet.

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

contract Greeter {
    string public greetingText = "Hello World!";
    address public greetingSender;

    struct EIP712Domain {
        string  name;
        string  version;
        uint256 chainId;
        address verifyingContract;
    }

    struct Greeting {
        string text;
        uint deadline;
    }

    bytes32 DOMAIN_SEPARATOR;

    constructor () {
        DOMAIN_SEPARATOR = hash(EIP712Domain({
            name: "Ether Mail",
            version: '1',
            chainId: block.chainid,
            verifyingContract: address(this)
        }));
    }

    function hash(EIP712Domain memory eip712Domain) internal pure returns (bytes32) {
        return keccak256(abi.encode(
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
            keccak256(bytes(eip712Domain.name)),
            keccak256(bytes(eip712Domain.version)),
            eip712Domain.chainId,
            eip712Domain.verifyingContract
        ));
    }

    function hash(Greeting memory greeting) internal pure returns (bytes32) {
        return keccak256(abi.encode(
            keccak256("Greeting(string text,uint deadline)"),
            keccak256(bytes(greeting.text)),
            greeting.deadline
        ));
    }

    function verify(Greeting memory greeting, address sender, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
        bytes32 digest = keccak256(abi.encodePacked(
            "\x19\x01",
            DOMAIN_SEPARATOR,
            hash(greeting)
        ));
        return ecrecover(digest, v, r, s) == sender;
    }

    function greet(Greeting memory greeting, address sender, uint8 v, bytes32 r, bytes32 s) public {
        require(verify(greeting, sender, v, r, s), "Invalid signature");
        require(block.timestamp <= greeting.deadline, "Deadline reached");
        greetingText = greeting.text;
        greetingSender = sender;
    }
}
```

## The Frontend

Then we build the frontend consisting of the HTML and JS file. The frontend is the interface that allows us to sign transactions and send them to the Relayer.

`index.html`
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div>
    <input id="connect_button" type="button" value="Connect" onclick="connectWallet()" style="display: none"></input>
    <p id="account_address" style="display: none"></p>
    <h1>Greeter Verifier</h1>
    <p id="web3_message"></p>
    <p id="contract_state"></p>
    <h3>Sign Greeting</h3>
    <span>Greeting Text</span><br>
    <input type="text" id="_greetingText"></input><br>
    <span>Greeting Deadline</span><br>
    <input type="text" id="_greetingDeadline"></input><br>
    <button type="button" id="sign" onclick="_signMessage()">Sign</button>
    <p id="hashed_message"></p>
    <p id="signature"></p>
    <h3>Verifiy Greeting</h4>
    <span>Greeting Text</span><br>
    <input type="text" id="_greetingTextRelay"></input><br>
    <span>Greeting Deadline</span><br>
    <input type="text" id="_greetingDeadlineRelay"></input><br>
    <span>Greeting Setter</span><br>
    <input type="text" id="_greetingSenderRelay"></input><br>
    <span>Signature</span><br>
    <input type="text" id="_signatureRelay"></input><br>
    <button type="button" id="relay" onclick="_relayGreeting()">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()
    {
      _greetingText = document.getElementById("_greetingText").value
      _greetingDeadline = document.getElementById("_greetingDeadline").value
      signMessage(_greetingText, _greetingDeadline)
    }

    function _relayGreeting()
    {
      _greetingTextRelay = document.getElementById("_greetingTextRelay").value
      _greetingDeadlineRelay = document.getElementById("_greetingDeadlineRelay").value
      _greetingSenderRelay = document.getElementById("_greetingSenderRelay").value
      _signatureRelay = document.getElementById("_signatureRelay").value
      relayGreeting(_greetingTextRelay, _greetingDeadlineRelay, _greetingSenderRelay, _signatureRelay)
    }
  </script>
</html>
```
In JavaScript, remember to set the `GREETER_CONTRACT_ADDRESS` variable with the contract you just launched

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

const GREETER_CONTRACT_ADDRESS = "0x374257dC5707AEDCC1D4F7D0d1b476a57Fc11194"
const GREETER_CONTRACT_ABI_PATH = "./json_abi/Greeter.json"
var greeterContract

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 () {
          greeterContract = await getContract(web3, GREETER_CONTRACT_ADDRESS, GREETER_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 greetingText = await greeterContract.methods.greetingText().call()
  var greetingSender = await greeterContract.methods.greetingSender().call()

  var contract_state = "Greeting Text: " + greetingText
    + ", Greeting Setter: " + greetingSender

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

const onWalletConnectedCallback = async () => {
}

// Sign and Relay functions

async function signMessage(message, deadline)
{
  const msgParams = JSON.stringify({
    types: {
        EIP712Domain: [
            { name: 'name', type: 'string' },
            { name: 'version', type: 'string' },
            { name: 'chainId', type: 'uint256' },
            { name: 'verifyingContract', type: 'address' },
        ],
        Greeting: [
            { name: 'text', type: 'string' },
            { name: 'deadline', type: 'uint' }
        ],
    },
    primaryType: 'Greeting',
    domain: {
        name: 'Ether Mail',
        version: '1',
        chainId: NETWORK_ID,
        verifyingContract: GREETER_CONTRACT_ADDRESS,
    },
    message: {
        text: message,
        deadline: deadline,
    },
  });
  console.log(msgParams)

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

  document.getElementById("signature").textContent="Signature: " + signature;
}

async function relayGreeting(greetingText, greetingDeadline, greetingSender, signature)
{
  const r = signature.slice(0, 66);
  const s = "0x" + signature.slice(66, 130);
  const v = parseInt(signature.slice(130, 132), 16);
  console.log({v,r,s})

  var url = "http://localhost:8080/relay?"
  url += "greetingText=" + greetingText
  url += "&greetingDeadline=" + greetingDeadline
  url += "&greetingSender=" + greetingSender
  url += "&v=" + v
  url += "&r=" + r
  url += "&s=" + s

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

  fetch(relayRequest);

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

## The Relayer Backend

Now an example of a backend that is responsible for transmitting transactions to blockchain.

Remember to set the `GREETER_CONTRACT_ADDRESS` variable with the contract you just launched. And `BACKEND_WALLET_ADDRESS` with the wallet that will pay the funds.

`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 GREETER_CONTRACT_ADDRESS = "0x374257dC5707AEDCC1D4F7D0d1b476a57Fc11194"
const BACKEND_WALLET_ADDRESS = "0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB"
const GREETER_CONTRACT_ABI_PATH = "./json_abi/Greeter.json"
const PORT = 8080
var web3 = null
var greeterContract = null

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

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

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

  fs.readFile(GREETER_CONTRACT_ABI_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 relayGreeting(greetingText, greetingDeadline, greetingSender, v, r, s)
{
  const nonce = await web3.eth.getTransactionCount(BACKEND_WALLET_ADDRESS, 'latest'); // nonce starts counting from 0
  const transaction = {
   'from': BACKEND_WALLET_ADDRESS,
   'to': GREETER_CONTRACT_ADDRESS,
   'value': 0,
   'gas': 300000,
   'nonce': nonce,
   'data': greeterContract.methods.greet(
     [greetingText, greetingDeadline],
     greetingSender,
     v,
     r,
     s)
     .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)
    }
  });
}

app.get('/relay', (req, res) => {
  var greetingText = req.query["greetingText"]
  var greetingDeadline = req.query["greetingDeadline"]
  var greetingSender = req.query["greetingSender"]
  var v = req.query["v"]
  var r = req.query["r"]
  var s = req.query["s"]
  var message = greetingSender + " sent a greet: " + " " + greetingText
  relayGreeting(greetingText, greetingDeadline, greetingSender, v, r, s)
  res.setHeader('Content-Type', 'application/json');
  res.send({
    "message": message
  })
})
initAPI()
```

We will also need to add the `json_abi/Contract.json` file which contains the Json ABI of the contract we just launched. 
`json_abi/Greeter.json`

```json
[
	{
		"inputs": [
			{
				"components": [
					{
						"internalType": "string",
						"name": "text",
						"type": "string"
					},
					{
						"internalType": "uint256",
						"name": "deadline",
						"type": "uint256"
					}
				],
				"internalType": "struct Example.Greeting",
				"name": "greeting",
				"type": "tuple"
			},
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			},
			{
				"internalType": "uint8",
				"name": "v",
				"type": "uint8"
			},
			{
				"internalType": "bytes32",
				"name": "r",
				"type": "bytes32"
			},
			{
				"internalType": "bytes32",
				"name": "s",
				"type": "bytes32"
			}
		],
		"name": "greet",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"inputs": [],
		"name": "greetingSender",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "greetingText",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"components": [
					{
						"internalType": "string",
						"name": "text",
						"type": "string"
					},
					{
						"internalType": "uint256",
						"name": "deadline",
						"type": "uint256"
					}
				],
				"internalType": "struct Example.Greeting",
				"name": "greeting",
				"type": "tuple"
			},
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			},
			{
				"internalType": "uint8",
				"name": "v",
				"type": "uint8"
			},
			{
				"internalType": "bytes32",
				"name": "r",
				"type": "bytes32"
			},
			{
				"internalType": "bytes32",
				"name": "s",
				"type": "bytes32"
			}
		],
		"name": "verify",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	}
]
```

And also remember to add a `.env` with your RPC url and your private key.

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

Remember to add your .env files to your gitignore!

`.gitignore`
```bash
.env
```

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

Finally, we install the dependencies:

```bash
npm install
```

Alternatively, we install the dependencies manually: `npm install @alch/alchemy-web3 dotenv node-fetch`.

## Test the DApp

To start the frontend.

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

To start the relayer backend

```bash
node backend.js
```

Now you can sign and relay transactions.

**Thanks for watching this video!**

Follows us on dev.to and in [Youtube]( https://www.youtube.com/channel/UCbF5CFuXv2vTkcGfepWV2nA) for everything related to Blockchain development.