Skip to content

Instantly share code, notes, and snippets.

@jefflau
Last active September 20, 2020 23:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jefflau/38d41fe732870892cb7abf950fb76b7a to your computer and use it in GitHub Desktop.
Save jefflau/38d41fe732870892cb7abf950fb76b7a to your computer and use it in GitHub Desktop.
ENS Javascript API design

API design for ENS

The ENS library is to be used in dapps and possibly other tools that need to connect to the set of ENS contracts on the Ethereum blockchain. It should give them all the tools they need to resolve and set records for their ENS names, as well as abstracting implementation details including, but not limited to:

  • namehashes (could be provided as an advanced option)
  • resolvers (unless it's absolutely required for custom resolvers)
  • using the graph for getting textRecord or non-eth address keys
  • decoders/encoders of addresses

Instantiation

Takes either a string, for connecting directly to infura, alternatively, can take a web3 provider object

import ENS from ‘ensLib’

const ens = new ENS({ 
  provider: web3.currentProvider, 
  registryAddress: registry  // optional, if it recognises the network, it will use a hardcoded list of registry addresses otherwise will error and ask user to provider a registry address
}) 

High level overview of API

  • ens.getName() - Gets the name object using a namehash or ens name
  • ens.getResolver() - Gets the resolver object at certain address, which can be then be asked what name you want from that resolver.
  • ens.setReverseRecord() - Sets the reverse record for the current account

Name object

const name = ens.name(‘jefflau.eth’)

Getters

const owner = await name.getOwner()
const resolver = await name.getResolver()
const address = await name.getAddr(‘eth’)
const contenthash = await name.getContenthash()
const parent = await name.getParent() //eth

TextRecords

const textRecord = await name.getTextRecord(‘url’)
// ‘http://awesome.com'
const textRecords = await name.getAllTextRecords() 

// [{ key: ‘url’, value: ‘http://awesome.com'}]

Coins

const coin = await name.getAddress(‘eth’)
// { key: ‘ETH’, coinId: 50, value: ‘0x000….’}

const coin = await name.getAddress(50)
// { key: ‘ETH’, coinId: 50, value: ‘0x000….’}

const coins = await name.getAllAddresses()
// [{ key: ‘ETH’, value: ‘0x000….’}, {key: "BTC", value: '14abc...']

Setters

const tx = await name.setResolver() // by default sets it to the public resolver with no arguments
const tx = await name.setResolver(customResolverAddr)
const tx = await name.setOwner(addr)
const tx = await name.setSubnodeOwner(label, addr)
const tx = await name.createSubdomain(label) //sets the owner to yourself

Record Setter

const tx = await name.setRecords({
  contentHash: 'ipfs://...',
  addresses: [{key: 'ETH', value: "0x123..." }],
  textRecords: [{ key: "url", value: "http://..."}]
})

//TODO possible get all records convenience function

Registrar object

const registrarDetails = name.getRegistrarDetails() //errors if isn't .eth
{
   expiryDate: DateObject,
   gracePeriodEnds: DateObject,
   registrant: "0x123..."
}

Resolver Object

const resolver = ens.getResolver('0x123')

const name = await resolver.name('vitalik.eth') // same as ens.getName, but only returns names that are using this resolver
const resolverAddress = resolver.address // 0x123... (not async)

name.getAddr('eth') // 0x123...
name.getContenthash() // ipfs://

Reverse record

ens.getReverseRecord()
ens.setReverseRecord('vitalik.eth')
@makoto
Copy link

makoto commented Aug 24, 2020

const resolver = await name.getOwner() is probably const owner

@makoto
Copy link

makoto commented Aug 24, 2020

const ens = new ENS(web3.currentProvider) How do you pass test registry address for ganache/private chain usage?

@makoto
Copy link

makoto commented Aug 24, 2020

const textRecord = await name.getTextRecord(‘url’)
// { key: ‘url’, value: ‘http://awesome.com'}

Do you need to return key give it's already passed in input argument.

@makoto
Copy link

makoto commented Aug 24, 2020

It's a bit inconsistent naming to call one as getTextRecords and another for getAllAddr

@makoto
Copy link

makoto commented Aug 24, 2020

setRecords. Does this override existing records including the ones not specified, or more of merge? Also I wonder if we need API to express 'deleteRecord/deleteAddr/deleteResolver/deleteOwner'.

@Arachnid
Copy link

const ens = new ENS(‘mainnet’) // use infura and read only

I'm not sure we should encourage this, with INFURA moving to API-key-only access.

const address = await name.getAddr(‘eth’)

This should probably also take coin ID as an alternative.

const tx = await name.setResolver() // by default sets it to the public resolver with no arguments

I think rather than having this hardcoded, we should just document that they can use resolver.ens.eth to get the public resolver.

A couple of thoughts about the object model:

  • It may make sense to have a "resolver" object, that references a specific name on a specific resolver address. That allows a couple of benefits:
    • It makes it more obvious that changing the resolver for a name will result in any records under it going away.
    • We can provide advanced methods to get the resolver object for a name that doesn't point at that resolver (eg, get the object for 'foo.eth' on the new public resolver, even though 'foo.eth' specifies a different resolver in the registry). That would make it possible to implement things like the manager's resolver migration system.
  • Likewise, it may make sense to have a 'registrar' object - or maybe to have the getRegistrarDetails method be off the top level instead of off the name, since it only applies to some names.

@jefflau
Copy link
Author

jefflau commented Sep 7, 2020

setRecords. Does this override existing records including the ones not specified, or more of merge? Also I wonder if we need API to express 'deleteRecord/deleteAddr/deleteResolver/deleteOwner'.

I was thinking this was more of a merge. Thinking about delete, that might make sense, however we would still like to support multiple records deletes/updates. I'm not sure the best API to have create/update and then do delete. The only thing I can think of is a keyword like 'delete', but that seems kind of lame

@jefflau
Copy link
Author

jefflau commented Sep 7, 2020

const ens = new ENS(web3.currentProvider) How do you pass test registry address for ganache/private chain usage?

I'll change this to take an options object

@makoto
Copy link

makoto commented Sep 14, 2020

Would be nice if we have some sort of introspection function such as resolver.address returning resolver address.

@makoto
Copy link

makoto commented Sep 14, 2020

Do we not support setReverseRecord and creating subdomain? Also it would be nice if we can provide name.getParent() which returns parent name.

@makoto
Copy link

makoto commented Sep 14, 2020

name.getAddress(‘eth’) is it supposed to be case insensitive? If not, it should be name.getAddress(‘ETH’). Also name.getAddress() should default to return ETH address.

@jefflau
Copy link
Author

jefflau commented Sep 18, 2020

name.getAddress(‘eth’) is it supposed to be case insensitive? If not, it should be name.getAddress(‘ETH’). Also name.getAddress() should default to return ETH address.

I think case insensitivity is fine here, especially if we're going to do things like add defaults like getAddress()

@Arachnid
Copy link

I was thinking this was more of a merge. Thinking about delete, that might make sense, however we would still like to support multiple records deletes/updates. I'm not sure the best API to have create/update and then do delete. The only thing I can think of is a keyword like 'delete', but that seems kind of lame

What I'd suggest is this:

  • For single values, if the field is present it overrides the current value. Specifying null unsets/deletes it.
  • For lists such as addresses and textRecords, it replaces the values of the supplied elements but does not touch others. Setting the value to null deletes an entry.

So if nick.eth has a contentHash of a and text records url: example.com and avatar: foo, then after this operation:

const tx = await name.setRecords({
  contentHash: 'b',
  textRecords: [{ key: "url", value: null}, { key: "test", value: "bar"}]
})

The name will then have a contentHash of b, and text records avatar: foo and test: bar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment