Unlocking Ethereum for the masses Everyone talks about “gas-less” Ethereum transactions because no one likes paying for gas. But the Ethereum network runs precisely because transactions are paid for. Then, how can you have “gas-less” anything? What is this sorcery? In this article I’m going to show how to use the patterns behind “gas-less” transactions. You will discover that although there is no such thing as a free lunch in Ethereum, you can shift gas costs in interesting ways. By applying the knowledge from this article, your users will save on gas, will enjoy a better UX, and even build novel delegation patterns into your smart contracts. But wait! There is more! For your convenience I’ve put all the tools needed in . So now the barrier for you to implement “gas-less” tokens is suddenly much lower. this repository Let’s get nerdy. Background I have to confess that even if I know how to implement “gas-less” transactions into smart contracts, I know very little about the cryptography that makes them possible. That wasn’t a major obstacle for me, so it shouldn’t be for you either. As far as I know, my private key is used to sign the transactions I send to Ethereum, and some cryptography magic is used to identify me as msg.sender. That underpins all access control in Ethereum. The sorcery behind “gas-less” transactions is that I can produce a signature with my private key and the smart contract transaction that I want executed. The signature would be produced off-chain, without spending anything on gas. Then I could give this signature to someone else to execute the transaction on my behalf, with their gas. The function that the signature is for will usually be a regular function, but extended with additional signature parameters. For example in we have the approve function: dai.sol ( ) ( ) function approve address usr, uint wad external returns bool We also have the function, which does the same as but takes a signature as a parameter. permit approve ( ) function permit address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s external Don’t worry about all those extra parameters, we’ll get to them. What you need to pay attention to is what both functions do with the mapping: allowance { allowance[msg.sender][usr] = wad; … } { … allowance[holder][spender] = wad; … } ( ) ( ) function approve address usr, uint wad external returns bool ( ) function permit address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s external If you use , you allow to use up to of your tokens. approve spender wad If you give a valid signature to someone, that someone can call to allow using your tokens. permit spender So basically, the pattern behind “gas-less” transactions is to craft a signature that you can give to someone, so that they can safely execute a special transaction. It’s like giving permission to someone to execute a function. It is a delegation pattern. The standards If you are like me, the first thing you will do is to dive into the code. I immediately noticed this comment: // — — EIP712 niceties — - With that, I , and got hopelessly lost. Now that I understand it, I can explain it in plain terms. went down the rabbit hole describes how to build signatures for functions, in a generic way. Other EIPs describe how to apply to specific use cases. For example describes how to use signatures for a function called which should have the same functionality as in an ERC20 token. EIP712 EIP712 EIP2612 EIP712 permit approve If you just want to implement a signature function that has been done before, like adding signature approves to your own MetaCoin, then you can read and you will be well on your way. You can even inherit from a contract implementing it and limit the stress in your life. EIP2612 In this article we will investigate an implementation of “gas-less” transactions in , which will make things clear. The implementation happened before and is slightly different. That will not be a problem. dai.sol dai.sol EIP2612 Signature composition An early implementation of signatures can be found in . It allows dai holders to approve transfer transactions by calculating an off-chain signature and giving it to the spender, instead of calling approve themselves. EIP712 dai.sol It includes four elements: A . DOMAIN_SEPARATOR A . PERMIT_TYPEHASH A variable. nonces A function. permit This is the , with related variables: DOMAIN_SEPARATOR string public constant name = ; string public constant version = ; bytes32 public DOMAIN_SEPARATOR; (uint256 chainId_) public { ... DOMAIN_SEPARATOR = keccak256(abi.encode( keccak256( + ), keccak256(bytes(name)), keccak256(bytes(version)), chainId_, address( ) )); } "Dai Stablecoin" "1" constructor "EIP712Domain(string name,string version," "uint256 chainId,address verifyingContract)" this The is nothing more than a hash that uniquely identifies a smart contract. It is built from a string denoting it as an EIP712 Domain, the name of the token contract, the version, the chainId in case it changes, and the address that the contract is deployed at. DOMAIN_SEPARATOR All that information is hashed on the constructor into the variable, which will have to be used by the holder when creating the signature, and will need to match when executing permit. That ensures that a signature is valid for one contract only. DOMAIN_SEPARATOR This is the : PERMIT_TYPEHASH The is the hash of the function name (capitalized) and all the parameters including type and name. It’s purpose is to clearly identify which function is the signature for. PERMIT_TYPEHASH The signature will be processed in the permit function, and if the used was not for this specific function, it will revert. This makes sure that a signature is only used for the intended function. PERMIT_TYPEHASH Then there is the mapping: nonces mapping ( uint) public nonces; => address This mapping registers how many signatures have been used for a particular holder. When creating the signature, a value needs to be included. When executing , the nonce included must exactly match the number of signatures that have been used so far for that holder. This ensures that each signature is used only once. nonces permit All these three conditions together, the , the , and the , make sure that each signature is used only for the intended contract, the intended function, and only once. PERMIT_TYPEHASH DOMAIN_SEPARATOR nonce Now let’s see how the signature would be processed in the smart contract. The permit function is the function that allows using signatures to modify the of towards . permit dai.sol allowance holder spender // --- Approve by signature --- ( ) ; function permit address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s external As you can see, there are a lot of parameters there. They are all the parameters needed to compute the signature, plus , and which are the signature itself. v r s It seems silly that you need the parameters that were used to create the signature, but you do. The only thing that you can recover from the signature is the address that created it, nothing more. We will use all the parameters and the recovered address to ensure the signature is valid. First we calculate a using all the parameters that we will need to ensure safety. The will need to calculate the exact same digest off-chain, as part of the signature creation: digest holder bytes32 digest = keccak256(abi.encodePacked( , DOMAIN_SEPARATOR, keccak256(abi.encode( PERMIT_TYPEHASH, holder, spender, nonce, expiry, allowed )) )); "\x19\x01" Using and the signature we can recover an address. If it is the address of the , we know that all the parameters match ( , , , , , , and . If anything is off, the signature is rejected: ecrecover v,r,s holder DOMAIN_SEPARATOR PERMIT_TYPEHASH nonce holder spender expiry allowed (holder == ecrecover(digest, v, r, s), ); require "Dai/invalid-permit" A word of caution here. There are many parameters that go into a signature, some of them obscure like the (part of the ). Any of them being off will cause the signature being rejected with the , which guarantees that debugging off-chain signatures will be difficult. You have been warned. chainId DOMAIN_SEPARATOR exact same error Now we know that the approved this function call. Next we will certify that the signature is not being abused. We check that the current time is before the , this allows permits to be held only for a specific period. holder expiry (expiry == || now <= expiry, ); require 0 "Dai/permit-expired" We also check that a signature with that hasn’t been used yet, so that each signature can be used only once. nonce (nonce == nonces[holder]++, ); require "Dai/invalid-nonce" And we are through! maxes out the of towards , emits an event, and that’s it. dai.sol allowance holder spender uint wad = allowed ? uint( ) : ; allowance[holder][spender] = wad; emit Approval(holder, spender, wad); -1 0 The contract has a binary approach towards , in the provided you'll find a more traditional behavior. dai.sol allowance repository Creating the signature off-chain Creating the signature is not for the faint of heart, but with a bit of practice and persistence it can be mastered. We will replicate what the smart contract does in in three steps: permit Generate the DOMAIN_SEPARATOR Generate the digest Create the transaction signature The following function will create the . It is the same code as in the constructor, but in JavaScript and using , and from . It needs the token name and deployment address, along with the . It assumes the token version to be “1”. DOMAIN_SEPARATOR dai.sol keccak256 defaultAbiCoder toUtfBytes ethers.js chainId The following function will create a for a specific call. Note that the , , and are passed on as arguments. It also passes an argument for clarity, although you could just set it always to , otherwise the signature will be rejected and what would be the point? The we just copied it from . digest permit holder spender nonce expiry approve.allowed true PERMIT_TYPEHASH dai.sol Once we have a , signing it is relatively easy. We just use from after removing the 0x prefix from the . Note that we need the user private key to do this. digest ecsign ethereumjs-util digest In the code, we would call these functions as follows: Note how the call to reuses all the parameters that were used to create the , before it was signed. Only in that case the signature would be valid. permit digest Note as well that the only two transactions in this snippet are being called by . is the , and is the one that created the and signed it. However, didn’t spend any gas doing so. user2 user1 holder digest user1 gave the signature to , which used it to execute both the and the that allowed. user1 user2 permit transferFrom user1 From the point of view of , it was a “gas-less” transaction. He didn’t spend a wei. user1 Conclusion This article shows how to use “gas-less” transactions, clarifying that “gas-less” actually means passing the gas cost to someone else. To do that we need a function in a smart contract that is ready to deal with pre-signed transactions, and a good deal of data manipulation to make everything safe. However, there are significant gains from using this pattern, and for that reason it is widely used. Signatures allow passing the transaction gas cost from the user to the service provider, eliminating a considerable barrier in many cases. It also allows for the implementation of more advanced delegation patterns, often with considerable UX improvements. A has been provided for you to get started. Please use it, and please . repository continue the conversation Very special thanks to Georgios Konstantinopoulos, who taught me all I know about this pattern.