Heya! In this post we're going to go a quick look into using EthersJS API for the EIP712 standard.
Also, before we dive in, I would like to point out that if your use case is simple as a single message, you likely do not need to complicate it with EIP712. You can simply use signer.signMessage("hello")
API (EIP191). While if you have multiple values (like complex structs) to sign, then EIP712 can be useful.
Setting up signer
Consider a use case where a user want to sign a complex struct. They also need to include a signature to prove that they own the private keys of an account containing some funds.
You can use Metamask. But if you're just trying for demo, you can just generate a random wallet.
const metamaskProvider = new ethers.providers.Web3Provider(window.ethereum);
const signer = metamaskProvider.getSigner();
// or
const signer = ethers.Wallet.createRandom()
Defining Domain Separator
Now we define our domain separator. This is useful for the case when there is a fork/clone of your app on your chain or even a different one, and some user using both the apps. The domain separator ensures different signatures for same messages by same user.
const domain = {
name: 'My App',
version: '1',
chainId: 1,
verifyingContract: '0x1111111111111111111111111111111111111111'
};
Defining Struct Types
const types = {
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'content', type: 'string' }
],
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' }
]
};
Signing struct objects
const mail = {
from: {
name: 'Alice',
wallet: '0x2111111111111111111111111111111111111111'
},
to: {
name: 'Bob',
wallet: '0x3111111111111111111111111111111111111111'
},
content: 'Hello!'
};
To sign the above struct, we use _signTypedData
method on the signer.
const signature = await signer._signTypedData(domain, types, mail);
// 0x74ff2b1850dfa49f825a29760cf7f8194465190481afb49dd81f0ef7783d7b9638180110bdbf5d85ab8d9f39d2d4f4bc63f017c5b53e90bf915cf22c2b7125901b
This contains an underscore prepend because of the fact that it's recently introduced and is is in public beta. Once enough confidence in it, it will be moved out of beta by renaming to signTypedData
(don't worry the underscore alias will also exist for backwards compatibility).
Verifying signatures
You can use the verifyTypedData
util to verify if a given signature is valid.
const expectedSignerAddress = signer.address;
const recoveredAddress = ethers.utils.verifyTypedData(domain, types, mail, signature);
console.log(recoveredAddress === expectedSignerAddress);
// true
Summary
That's pretty much it for this post. To summarise, we saw
- how to define a domain separator
- struct types
- signing our struct object
- verifying a given signature
Top comments (3)
What if your struct contains an array of another struct?
eg. In your "mail", what if "to" contains multiple Persons like Bob and Charlie.
Can you show an example?
I've been trying this all day and can't get it to work.
Try this:
const types = {
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person[]' },
{ name: 'content', type: 'string' }
],
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' }
]
};
I am using ethersv6 ( with Hardhat ^2.19.4 )
signer._signTypedData didn't worked for me , signer.signTypedData() is working .
ethers.utils.verifyTypedData is now ethers.verifyTypedData .