Skip to content

Commit

Permalink
feat: getEnsAddress
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm committed Feb 10, 2023
1 parent cff2581 commit 203ad6a
Show file tree
Hide file tree
Showing 15 changed files with 239 additions and 129 deletions.
3 changes: 2 additions & 1 deletion src/_test/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ export const accounts = [
] as const

export const address = {
vitalik: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
burn: '0x0000000000000000000000000000000000000000',
usdcHolder: '0x5414d89a8bf7e99d732bc52f3e6a3ef461c0c078',
vitalik: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
} as const

export const initialBlockNumber = BigInt(
Expand Down
37 changes: 37 additions & 0 deletions src/actions/ens/getEnsAddress.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { expect, test } from 'vitest'

import { publicClient } from '../../_test'
import { getEnsAddress } from './getEnsAddress'

test('gets address for name', async () => {
await expect(
getEnsAddress(publicClient, {
name: 'awkweb.eth',
universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
}),
).resolves.toMatchInlineSnapshot(
'"0xA0Cf798816D4b9b9866b5330EEa46a18382f251e"',
)
})

test('gets address for name', async () => {
await expect(
getEnsAddress(publicClient, {
name: 'awkweb.eth',
universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
}),
).resolves.toMatchInlineSnapshot(
'"0xA0Cf798816D4b9b9866b5330EEa46a18382f251e"',
)
})

test('name without address', async () => {
await expect(
getEnsAddress(publicClient, {
name: 'unregistered-name.eth',
universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
}),
).resolves.toMatchInlineSnapshot(
'"0x0000000000000000000000000000000000000000"',
)
})
127 changes: 72 additions & 55 deletions src/actions/ens/getEnsAddress.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,82 @@
import { PublicClient } from '../../clients'
import type { Address } from '../../types'
import { readContract } from '../public'
import type { Address, Hex, Prettify } from '../../types'
import { decodeFunctionResult, encodeFunctionData } from '../../utils'
import { namehash, packetToBuffer } from '../../utils/ens'
import { readContract, ReadContractArgs } from '../public'

export type GetEnsNameArgs = {
/** Address to get ENS name for. */
address: Address
// TODO: Add block number, etc.
}
export type GetEnsAddressArgs = Prettify<
Pick<ReadContractArgs, 'blockNumber' | 'blockTag'> & {
/** ENS name to get address. */
name: string
/** Address of ENS Universal Resolver Contract */
universalResolverAddress: Address
}
>

/**
* @description Gets primary name for specified address.
* @description Gets address for ENS name.
*
* - Calls `resolve(bytes, bytes)` on ENS Universal Resolver Contract.
*
* @example
* const ensAddress = await getEnsAddress(publicClient, {
* name: 'wagmi-dev.eth',
* universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
* })
* console.log(ensAddress) // '0xd2135CfB216b74109775236E36d4b433F1DF507B'
*/
export async function getEnsName(
export async function getEnsAddress(
client: PublicClient,
{ address }: GetEnsNameArgs,
{ blockNumber, blockTag, name, universalResolverAddress }: GetEnsAddressArgs,
) {
const abi = [
{
name: 'reverse',
type: 'function',
stateMutability: 'view',
inputs: [{ type: 'bytes', name: 'reverseName' }],
outputs: [
{ type: 'string', name: 'resolvedName' },
{ type: 'address', name: 'resolvedAddress' },
{ type: 'address', name: 'reverseResolver' },
{ type: 'address', name: 'resolver' },
],
},
] as const
const reverseNode = `${address.toLowerCase().substring(2)}.addr.reverse`
const res = await readContract(client, {
abi,
address: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
functionName: 'reverse',
args: [`0x${encode(reverseNode).toString('hex')}`],
address: universalResolverAddress,
abi: [
{
name: 'resolve',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'name', type: 'bytes' },
{ name: 'data', type: 'bytes' },
],
outputs: [
{ name: '', type: 'bytes' },
{ name: 'address', type: 'address' },
],
},
],
functionName: 'resolve',
args: [
`0x${packetToBuffer(name).toString('hex')}`,
encodeFunctionData({
abi: [
{
name: 'addr',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'name', type: 'bytes32' }],
outputs: [],
},
],
functionName: 'addr',
args: [namehash(name)],
}),
],
blockNumber,
blockTag,
})
return decodeFunctionResult({
abi: [
{
name: 'addr',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: 'name', type: 'address' }],
},
],
functionName: 'addr',
data: res[0],
})
return res[0]
}

// adapted from https://github.com/mafintosh/dns-packet
function encode(str: string) {
function encodingLength(n: string) {
if (n === '.' || n === '..') return 1
return Buffer.byteLength(n.replace(/^\.|\.$/gm, '')) + 2
}

const buf = Buffer.alloc(encodingLength(str))
let offset = 0

// strip leading and trailing .
const n = str.replace(/^\.|\.$/gm, '')
if (n.length) {
const list = n.split('.')

for (let i = 0; i < list.length; i++) {
const len = buf.write(list[i], offset + 1)
buf[offset] = len
offset += len + 1
}
}

return buf
}
34 changes: 19 additions & 15 deletions src/actions/ens/getEnsName.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
import { expect, test } from 'vitest'

import { publicClient } from '../../_test'
import { address, publicClient } from '../../_test'

import { getEnsName } from './getEnsName'

test('default', async () => {
test('gets primary name for address', async () => {
await expect(
getEnsName(publicClient, {
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
}),
).resolves.toMatchInlineSnapshot('"awkweb.eth"')
})

// await expect(
// getEnsName(publicClient, {
// address: '0x5FE6C3F8d12D5Ad1480F6DC01D8c7864Aa58C523',
// }),
// ).rejects.toThrowErrorMatchingInlineSnapshot(`
// "execution reverted

// Contract: 0x0000000000000000000000000000000000000000
// Function: reverse(bytes address)
// Arguments: (0x28356665366333663864313264356164313438306636646330316438633738363461613538633532330461646472077265766572736500)
test('address with no primary name', async () => {
await expect(
getEnsName(publicClient, {
address: address.burn,
universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
}),
).resolves.toMatchInlineSnapshot('null')
})

// Details: execution reverted
// Version: [email protected]"
// `)
test('invalid universal resolver address', async () => {
await expect(
getEnsName(publicClient, {
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
universalResolverAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
}),
).resolves.toMatchInlineSnapshot('null')
})
99 changes: 46 additions & 53 deletions src/actions/ens/getEnsName.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,58 @@
import { PublicClient } from '../../clients'
import type { Address } from '../../types'
import { readContract } from '../public'
import type { Address, Prettify } from '../../types'
import { packetToBuffer } from '../../utils/ens'
import { readContract, ReadContractArgs } from '../public'

export type GetEnsNameArgs = {
/** Address to get ENS name for. */
address: Address
/** Universal Resolver address */
universalResolverAddress: Address
// TODO: Add block number, etc.
}
export type GetEnsNameArgs = Prettify<
Pick<ReadContractArgs, 'blockNumber' | 'blockTag'> & {
/** Address to get ENS name for. */
address: Address
/** Address of ENS Universal Resolver Contract */
universalResolverAddress: Address
}
>

/**
* @description Gets primary name for specified address.
*
* - Calls `reverse(bytes)` on ENS Universal Resolver Contract.
*
* @example
* const ensName = await getEnsName(publicClient, {
* address: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
* universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
* })
* console.log(ensName) // 'wagmi-dev.eth'
*/
export async function getEnsName(
client: PublicClient,
{ address }: GetEnsNameArgs,
{ address, blockNumber, blockTag, universalResolverAddress }: GetEnsNameArgs,
) {
const abi = [
{
name: 'reverse',
type: 'function',
stateMutability: 'view',
inputs: [{ type: 'bytes', name: 'reverseName' }],
outputs: [
{ type: 'string', name: 'resolvedName' },
{ type: 'address', name: 'resolvedAddress' },
{ type: 'address', name: 'reverseResolver' },
{ type: 'address', name: 'resolver' },
],
},
] as const
const reverseNode = `${address.toLowerCase().substring(2)}.addr.reverse`
const res = await readContract(client, {
abi,
address: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
functionName: 'reverse',
args: [`0x${encode(reverseNode).toString('hex')}`],
})
return res[0]
}

// Adapted from https://github.com/mafintosh/dns-packet
function encode(packet: string) {
function length(value: string) {
if (value === '.' || value === '..') return 1
return Buffer.byteLength(value.replace(/^\.|\.$/gm, '')) + 2
}

const buffer = Buffer.alloc(length(packet))
// strip leading and trailing `.`
const value = packet.replace(/^\.|\.$/gm, '')
if (!value.length) return buffer

let offset = 0
const list = value.split('.')
for (let i = 0; i < list.length; i++) {
const len = buffer.write(list[i], offset + 1)
buffer[offset] = len
offset += len + 1
try {
const res = await readContract(client, {
address: universalResolverAddress,
abi: [
{
name: 'reverse',
type: 'function',
stateMutability: 'view',
inputs: [{ type: 'bytes', name: 'reverseName' }],
outputs: [
{ type: 'string', name: 'resolvedName' },
{ type: 'address', name: 'resolvedAddress' },
{ type: 'address', name: 'reverseResolver' },
{ type: 'address', name: 'resolver' },
],
},
],
functionName: 'reverse',
args: [`0x${packetToBuffer(reverseNode).toString('hex')}`],
blockNumber,
blockTag,
})
return res[0]
} catch (error) {
return null
}

return buffer
}
12 changes: 12 additions & 0 deletions src/actions/ens/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, test } from 'vitest'

import * as actions from './index'

test('exports actions', () => {
expect(actions).toMatchInlineSnapshot(`
{
"getEnsAddress": [Function],
"getEnsName": [Function],
}
`)
})
2 changes: 2 additions & 0 deletions src/actions/ens/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { getEnsAddress } from './getEnsAddress'

export { getEnsName } from './getEnsName'
15 changes: 15 additions & 0 deletions src/ens.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect, test } from 'vitest'

import * as ens from './ens'

test('exports ens', () => {
expect(ens).toMatchInlineSnapshot(`
{
"getEnsAddress": [Function],
"getEnsName": [Function],
"labelhash": [Function],
"namehash": [Function],
"normalize": [Function],
}
`)
})
2 changes: 1 addition & 1 deletion src/ens.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { getEnsName } from './actions/ens'
export { getEnsAddress, getEnsName } from './actions/ens'

export {
labelhash,
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export type {

export type {
PartialBy,
Prettify,
MergeIntersectionProperties,
OptionalNullable,
} from './utils'
Loading

0 comments on commit 203ad6a

Please sign in to comment.