Exploring the intersection between TypeScript, Ethereum, GraphQL, and other exciting technologies.

Making a SuperStruct EthereumAddress type

18th Apr 2022

SuperStruct is a great library for validating data in Javascript. It provides proper TypeScript types, which has made me favor it to some of the competitors like Joi.

I want it to work well and be as strict as possible for Ethereum addresses (0x...).

Let’s say I’m receiving this token object from some kind of user input and I want to validate it.

const token = {
  address: "0x728713b41fcEc5C071359Ecf2802Fa0B62bec5a8"
}

With superstruct, we can make a simple schema and validate this.

import { is, object, string } from 'superstruct'

const TokenSchema = object({
  address: string(),
})

is(token, TokenSchema) // true

But string isn’t very specific in this regard. This shouldn’t be possible:

const invalidToken = {
  address: "asdfg"
}

is(invalidToken, TokenSchema) // true ❌ SHOULD BE FALSE

Let’s fix this!

Making a custom EthereumAddress SuperStruct type

Let’s make a custom type in SuperStruct.

import { define } from "superstruct";

const EthereumAddress = define("EthereumAddress", (value) => {
  // validation logic here
});

How do we validate an Ethereum address? One easy way is by using the Ethers library.

import { utils } from "ethers";

utils.isAddress("0x728713b41fcEc5C071359Ecf2802Fa0B62bec5a8") // true
utils.isAddress("asdf") // false

Let’s put this into our new type.

// Yes, I am using isString from lodash. Too lazy.
import { isString } from "lodash";

const EthereumAddress = define("EthereumAddress", (value) => {
	// first check if it is a string
	if(!isString(value)){
		return false
	}

	// then check if it is a valid address
  return utils.isAddress(value);
});

Now we can supercharghe our TokenSchema with our new type.

const TokenSchema = object({
  address: EthereumAddress
});

const token = {
  address: "0x728713b41fcEc5C071359Ecf2802Fa0B62bec5a8"
}

is(token, TokenSchema) // true

const invalidToken = {
  address: "asdfg"
}

is(invalidToken, TokenSchema) // false

Give the right types to typescript

One of my favourite things with SuperStruct is that you can infer types to use in Typescript. But our new EthereumAddress type gives us an unknown 😲.

type Token = Infer<typeof TokenSchema>
// type Token = {
//   address: unknown
//}

We can fix this by explicitly telling SuperStruct that the closest TypeScript type is string when defining the type.

We do this by using the generic define<string>.

const EthereumAddress = define<string>("EthereumAddress", (value) => {
  ...
})

Now it gives us the proper TypeScript type.

type Token = Infer<typeof TokenSchema>
// type Token = {
//   address: string
//}

If you want it to be even prettier, you could add an actual EthereumAddress TypeScript type.

type EthereumAddress = string
const EthereumAddress = define<EthereumAddress>("EthereumAddress", (value) => {
  ...
})

type Token = Infer<typeof TokenSchema>
// type Token = {
//   address: EthereumAddress
//}

Conclusion

SuperStruct is great by itself, but can be even greater by adding some custom types.

Check out the CodeSandbox.


Tools