npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

eth-typed-data

v0.1.0-beta.0

Published

A library to simplifiy interacting with and signing EIP712 typed data

Downloads

1,725

Readme

Typed Structured Data, On-Chain and Off

js-standard-style Twitter Follow

Thanks for checking out EthTypedData! This library is currently in beta, and we don't yet recommend using it in production applications -- please file an issue if you encounter any bugs or would like to request a feature!

With the new EIP712 specification poised to be the new standard for representing data structures in the ethereum world, the uPort team has developed a convenient library to interact with types and domains as defined by the spec. In particular, we've made it easy to manage domains with multiple different types, and provide convenient methods for encoding, hashing, and signing EIP712 typed data structures, as well as for converting objects to signature requests for use with eth_signTypedData.

Creating a Domain

To use eth-typed-data, the first step is to create a domain. Domains are special types that encode a particular application or use-case, and are used to distinguish between objects with the same structure but created for different applications. In particular, this protects users by avoiding the possibility that a signature on one object in one application can be reused in a different application.

We create a domain as follows:

import { EIP712Domain } from 'eth-typed-data'

const myDomain = new EIP712Domain({
  name: 'Ether Mail',               // Name of the domain
  version: '1',                     // Version identifier for this domain
  chainId: 1,                       // EIP-155 Chain id associated with this domain (1 for mainnet)
  verifyingContract: '0xdeadbeef',  // Address of smart contract associated with this domain
  salt: 'rAnD0mstr1ng'              // Random string to differentiate domain, just in case
})

The EIP712 Spec requires that a domain define at least one of the above properties, though to best protect against domain conflicts, we recommend that you define all of them.

Defining Structure Types

The domain has the ability to define new struct types (modeled after Solidity structs) that can be used within it using the createType() method. Structure types contain properties, each with their own name and type. The type of each property can be one of the EIP712 primitive types or another structure type already defined in the current domain. Note that createType will throw an error if a referenced structure type is not yet defined in the current domain.

To create a new Structure type in a domain, call createType with a list of objects {name, type}, giving the string name and string type name for each property of the new Structure type.

const Person = myDomain.createType('Person', [
  { name: 'name', type: 'string' },
  { name: 'wallet', type: 'address'}
])
const Mail = myDomain.createType('Mail', [
  {name: 'to', type: 'Person'},
  {name: 'from', type: 'Person'},
  {name: 'contents', type: 'string'}
])

Alternatively, and object mapping string names to string types can be used in the same way.

const Person = myDomain.createType('Person', {
  name: 'string',
  wallet: 'address'
})
const Mail = myDomain.createType('Mail', {
  to: 'Person',
  from: 'Person',
  contents: 'string'
})

Creating Type Instances

The value returned from myDomain.createType is a constructor, which may be instantiated arbitrarily many times. You can create an instance of a structure type in the same way you create a domain, by passing an object with a value for each property of the type. In contrast to a domain, Structure types require a value for every property in their definition, and will raise an error if any property is undefined.

// Create two new `Person`s, alice and bob
let alice = new Person({
  name: 'Alice',
  wallet: '0xCcccCcCCCCccccccCCCcCccCcCCCccCCcCcCCCCc',
})
let bob = new Person({
  name: 'Bob',
  wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'
})
// Create a piece of mail between alice and bob
let letter = new Mail({
  from: alice,
  to: bob,
  contents: 'Woah, a well-formed piece of structured data that I can sign and verify on-chain!'
})

In addition to validating that each property is given a value, the type constructors also validate that the provided value is allowable for that property's type. Each primitive type has its own validation function, and each structure type has a recursive static validate method, which checks the validity of each of its properties. With this in mind, we can also define a piece of Mail with a single object:

// Create a piece of mail from alice to bob, without explicitly creating alice or bob
let explicitLetter = new Mail({
  from: {
    name: 'Alice',
    wallet: '0xaaAAaaaAaaAAAAaaaaaAaaAAAaAAAAAAAaaAaaAA'
  },
  to: {
    name: 'Bob',
    wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'
  },
  contents: 'Look! Another message!'
})

If you attempt to construct a type with an invalid value for any property, you will get an error.

// ERROR
let badletter = new Mail({
  from: alice,
  to: 'bob', // Invalid value for type `Person`!
  contents: 'A malformed message'
})
// ERROR
let badperson = new Person({
  name: 'Bad',
  wallet: 25 // Invalid value for type `address`
})

Encoding, Hashing, and Signing

Once you've created a type, there are a number of methods available to encode, hash, and sign your data. Each type class has static methods for generating the abi encoding according to the EIP712 spec, and returning the typeHash, which is simply the keccak256 hash of the abi encoding.

> Person.encodeType()
'Person(string name,address wallet)'
> Person.typeHash()
'b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500'
> Mail.encodeType()
'Mail(Person from,Person to,string contents)Person(string name,address wallet)'
> Mail.typeHash()
'a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2'

Instances of a type represent actual data that can be signed along with the hash of the type. To convert a type instance to a signature request for use with eth_signTypedData, call the toSignatureRequest() method. This will encode the domain, types, primaryType, and message, in preparation to be signed.

> letter.toSignatureRequest()
{
  types: {
    EIP712Domain: [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
      { name: 'verifyingContract', type: 'address' },
      { name: 'salt', type: 'string' }
    ],
    Person: [
      { name: 'name', type: 'string' },
      { name: 'wallet', type: 'address' }
    ],
    Mail: [
      { name: 'from', type: 'Person' },
      { name: 'to', type: 'Person' },
      { name: 'contents', type: 'string' }
    ],
  },
  primaryType: 'Mail',
  domain: {
    name: 'Ether Mail',
    version: '1',
    chainId: 1,
    verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
    salt: 'rAnD0mstr1ng'
  },
  message: {
    from: {
      name: 'Alice',
      wallet: '0xaaAAaaaAaaAAAAaaaaaAaaAAAaAAAAAAAaaAaaAA'
    },
    to: {
      name: 'Bob',
      wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'
    },
    contents: 'Woah, a well-formed piece of structured data that I can sign and verify on-chain!',
  },
}

To do the signing off chain in javascript, you can encode a structures type and data using the hashStruct instance method. This will return the keccak256 hash of the concatenation of the typeHash and the abi encoded data according to the EIP712 spec, which we will not repeat here. The abi encoding of the data alone can be calculated with the encodeData() instance method. Finally, the encode() method prefixes the hashStruct with \x19\x01 and the domainSeparator, equivalent to the hashStruct of the current domain. This can be be used to properly encode the data for signing elsewhere, or you can simply pass a signer to the sign() method, for example the SimpleSigner from did-jwt

import { SimpleSigner } from 'did-jwt'

const signer = new SimpleSigner(process.env.PRIVATE_KEY)
const signature = letter.sign(signer)

Primitive Types

The primitive types in the EIP712 spec are divided into two categories:

  1. Atomic types, with a fixed size in bytes, and well-defined encoding
  • bytes1, bytes2, bytes4, bytes8, bytes16, bytes32, uint8, uint16, uint32, uint64, uint128, uint256, int8, int16, int32, int64, int128, int256, address, bool
  1. Dynamic types, with variable length, and a hash-based encoding
  • bytes, string

TODO: Table defining each type and equivalent/compatible javascript type and validation