EIP712 Signature Based Workflow: Implementing Off-chain Transactions
As a web3 developer, you might have come across terms like gas-less transactions, off-chain transactions. A popular example is the way OpenSea marketplace works.
Hi! this article is a continuation of my previous article which contains an abstract explanation of off-chain transactions. This article contains a small demonstration of off-chain transaction based workflow (or signature based workflow). But before starting any of it, let’s have an overview of a fixed price sale listing on OpenSea:
Notice that the seller doesn’t have to execute an on-chain transaction for listing their NFT for sale. So, listing on OpenSea is an off-chain operation (one-time on-chain transaction may be required for approving the collection to Opensea’s sale contract).
Demonstration — Signature based workflow
Once you’ve understood the above diagram, we can continue to our example which practically implements signature verification and execution.
For the sake of simplicity, we’ll take a smart contract which contains a structure with two fields: value and setter. value is an integer which can be set by any blockchain account, i.e. setter.
Find the smart contract code here: https://github.com/d-Bharti001/signature-flow-tutorial/blob/master/contracts/SigFlow.sol
Now, the catch is that the setter should not execute any on-chain transaction; only the contract owner can call a function (setNum) to set the value on behalf of setter.
For this, the setter needs to sign an Order (also a structure defined in the contract). The contract owner can then take this Order data and generated signature and call the setNum function with these parameters, which would update the structure num.
Clone the project and launch local Truffle blockchain and frontend (steps provided in README): https://github.com/d-Bharti001/signature-flow-tutorial. We’ll be using Metamask wallet for signing Order data.
Blockchain and smart contract
We’ll be interacting with the SigFlow contract through Truffle developer console.
Frontend
Run npm start inside client folder, then launch the frontend interface in a browser with Metamask extension (or any other injected provider, e.g. Coinbase wallet). Click the button on the top of the page to connect the webpage with your wallet account.
Enter a value in the input field and click Sign message button. Metamask would pop up with a signature request.
After signing the Order, result is displayed in the frontend only:
Copy-paste the data obtained and call smart contract’s setNum function with the owner account (i.e. accounts[0] in Truffle):
Transaction succeeded. Now you can check the updated value of num:
It’s a success! congratulations. This way, value is updated to 256 by the account 0xBCE14… without the need for that account to spend on any gas fee.
It’s the same way that an OpenSea user doesn’t need to spend on any gas for listing their NFTs for sale.
It’s also the same way lazy minting works. You just have to sign a message containing token id and your address. When someone buys your NFT, a new token is minted in the same transaction with the token id set by you and your address as the first owner of the token. Find more details here: https://nftschool.dev/tutorial/lazy-minting/
Safety features
Validity period
In the Order to be signed, there is a field validPeriod. The function setNum checks if the current block time has exceeded validPeriod (line 77). If that happens, we can say that the Order has expired, and the transaction is rejected.
One-time execution of signed message
To prevent a signed message from being processed multiple times, we store which messages have already been processed in a mapping in the contract. After validating the signature, we include this check (line 71).
On-chain cancelation of signed message
After a message is signed, simply removing the message and signature from off-chain database won’t work because they might have been exposed earlier (before deletion) and so they can be passed to the contract for execution. So there is an on-chain function to cancel an Order, which can only be called by the account which originally signed the Order.
Domain information
In addition to the Order data (value, setter, validPeriod), the message to be signed also contains the domain information which includes these fields:
- name
- version
- chain id
- verifying contract address
This way, it’s made sure that the signature would work only with the intended contract which is deployed to the intended chain. Name and version add to the security.
For more information about EIP712 signatures from a mathematical point of view, visit EIP-712: Typed structured data hashing and signing.
External links
- My previous article on signature based workflow (theory): https://medium.com/@dharmveerbharti/off-chain-transactions-on-blockchain-introducing-signature-based-workflow-4bb3b5a7e72f
- Lazy minting tutorial by NFT School: https://nftschool.dev/tutorial/lazy-minting/
- EIP-712: Typed structured data hashing and signing
- Github repository of associated project: https://github.com/d-Bharti001/signature-flow-tutorial
Comment down if you need any help about message signing and verification. If required, I may write another article on how these messages are structured for signing, what is domain separator and order hash, etc.