Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ENS lookup methods #136

Merged
merged 11 commits into from
Jan 10, 2022
2 changes: 1 addition & 1 deletion .github/workflows/cla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ jobs:
path-to-signatures: 'signatures/version1/cla.json'
path-to-cla-document: 'https://gnosis-safe.io/cla/'
branch: 'cla-signatures'
allowlist: germartinez,mikheevm,rmeissner,Uxio0,dasanra,davidalbela,luarx,giacomolicari,lukasschor,tschubotz,gnosis-info,bot*,katspaugh
allowlist: germartinez,mikheevm,rmeissner,Uxio0,dasanra,davidalbela,luarx,giacomolicari,lukasschor,tschubotz,gnosis-info,bot*,katspaugh,usame-algan
empty-commit-flag: false
blockchain-storage-flag: false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ deployments
typechain

openapi/
.idea
2 changes: 2 additions & 0 deletions packages/safe-core-sdk/src/ethereumLibs/EthAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ interface EthAdapter {
signMessage(message: string, signerAddress: string): Promise<string>
estimateGas(transaction: EthAdapterTransaction, options?: string): Promise<number>
call(transaction: EthAdapterTransaction): Promise<string>
ensLookup(name: string): Promise<string>
ensReverseLookup(address: string): Promise<string>
}

export default EthAdapter
34 changes: 34 additions & 0 deletions packages/safe-core-sdk/src/ethereumLibs/EthersAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '../contracts/safeDeploymentContracts'
import { AbiItem } from '../types'
import EthAdapter, { EthAdapterTransaction, GetSafeContractProps } from './EthAdapter'
import ErrorCodes from './exceptions'

export interface EthersAdapterConfig {
/** ethers - Ethers v5 library */
Expand Down Expand Up @@ -151,6 +152,39 @@ class EthersAdapter implements EthAdapter {
call(transaction: EthAdapterTransaction): Promise<string> {
return this.#provider.call(transaction)
}

async ensLookup(name: string): Promise<string> {
let address: string | null

try {
address = await this.#provider.resolveName(name)
} catch (error) {
address = null
}

if (!address) {
throw new Error(ErrorCodes._100)
}

return address

}

async ensReverseLookup(address: string): Promise<string> {
let name: string | null

try {
name = await this.#provider.lookupAddress(address);
} catch (error) {
name = null
}

if (!name) {
throw new Error(ErrorCodes._101)
}

return name
}
}

export default EthersAdapter
26 changes: 24 additions & 2 deletions packages/safe-core-sdk/src/ethereumLibs/Web3Adapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BigNumber } from '@ethersproject/bignumber'
import { namehash } from '@ethersproject/hash'
import { SafeVersion } from '../contracts/config'
import {
getGnosisSafeProxyFactoryContractInstance,
Expand All @@ -15,6 +16,7 @@ import {
} from '../contracts/safeDeploymentContracts'
import { AbiItem } from '../types'
import EthAdapter, { EthAdapterTransaction, GetSafeContractProps } from './EthAdapter'
import ErrorCodes from './exceptions'

export interface Web3AdapterConfig {
/** web3 - Web3 library */
Expand Down Expand Up @@ -105,7 +107,7 @@ class Web3Adapter implements EthAdapter {
return getGnosisSafeProxyFactoryContractInstance(safeVersion, proxyFactoryContract)
}

getContract(address: string, abi: AbiItem[]): any {
getContract(address: string, abi: any): any {
return new this.#web3.eth.Contract(abi, address)
}

Expand All @@ -125,13 +127,33 @@ class Web3Adapter implements EthAdapter {
return this.#web3.eth.sign(message, this.#signerAddress)
}

estimateGas(transaction: EthAdapterTransaction, options?: string): Promise<number> {
estimateGas(transaction: EthAdapterTransaction, options?: any): Promise<number> {
return this.#web3.eth.estimateGas(transaction, options)
}

call(transaction: EthAdapterTransaction): Promise<string> {
return this.#web3.eth.call(transaction)
}

async ensLookup(name: string): Promise<string> {
try {
return await this.#web3.eth.ens.getAddress(name)
} catch (error) {
throw new Error(ErrorCodes._100)
}
}

async ensReverseLookup(address: string): Promise<string> {
const lookup = address.slice(2) + '.addr.reverse'
const node = namehash(lookup)

try {
const ResolverContract = await this.#web3.eth.ens.getResolver(lookup);
return await ResolverContract.methods.name(node).call()
} catch (error) {
throw new Error(ErrorCodes._101)
}
}
}

export default Web3Adapter
6 changes: 6 additions & 0 deletions packages/safe-core-sdk/src/ethereumLibs/exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
enum ErrorCodes {
_100 = 'Error retrieving address for ENS Name',
_101 = 'Error retrieving ENS Name for address'
}

export default ErrorCodes
87 changes: 87 additions & 0 deletions packages/safe-core-sdk/tests/ens.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import Web3 from 'web3'
import { ethers } from 'hardhat'
import { Web3Adapter } from '../src'
import { EthersAdapter } from '../dist/src'
import { ZERO_ADDRESS } from '../src/utils/constants'
import chai from 'chai'
import { getDefaultProvider } from '@ethersproject/providers'
import { Wallet } from '@ethersproject/wallet'
import ErrorCodes from '../src/ethereumLibs/exceptions'

describe('Web3Adapter', () => {
const web3 = new Web3(new Web3.providers.HttpProvider('https://rinkeby-light.eth.linkpool.io/', {}))
const web3Adapter = new Web3Adapter({web3, signerAddress: ZERO_ADDRESS})

describe('ENS direct lookup', () => {
it('returns an address for ens name', async () => {
const address = await web3Adapter.ensLookup('loremipsum.eth')
chai.expect(address).match(/0x[0-9a-z]{40}/i)
})

it('throws an error for a non existing name', async () => {
try {
await web3Adapter.ensLookup('nonexistingname')
chai.expect(true).to.be.false
} catch(error) {
chai.expect((error as Error).message).to.equal(ErrorCodes._100)
}
})
})

describe('ENS reverse lookup', () => {
it('returns a name for existing address', async () => {
const name = await web3Adapter.ensReverseLookup('0xd8bbcb76bc9aea78972ed4773a5eb67b413f26a5')
chai.expect(name).to.equal("loremipsum.eth")
})

it('throws an error if no name exists for address', async () => {
try {
await web3Adapter.ensReverseLookup(ZERO_ADDRESS)
chai.expect(true).to.be.false
} catch(error) {
chai.expect((error as Error).message).to.equal(ErrorCodes._101)
}
})
})
})

describe('EthersAdapter', () => {
const provider = getDefaultProvider(`https://rinkeby.infura.io/v3/${process.env.INFURA_KEY}`)
const signer = new Wallet(
'0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner
provider
)
const ethersAdapter = new EthersAdapter({ ethers, signer })

describe('ENS direct lookup', () => {
it('returns an address for ens name', async () => {
const address = await ethersAdapter.ensLookup('safe.eth')
chai.expect(address).match(/0x[0-9a-z]{40}/i)
})

it('throws an error for a non existing name', async () => {
try {
await ethersAdapter.ensLookup('nonexistingname')
chai.expect(true).to.be.false
} catch (error) {
chai.expect((error as Error).message).to.equal(ErrorCodes._100)
}
})
})

describe('ENS reverse lookup', () => {
it('returns a name for existing address', async () => {
const name = await ethersAdapter.ensReverseLookup('0xd8bbcb76bc9aea78972ed4773a5eb67b413f26a5')
chai.expect(name).to.equal('loremipsum.eth')
})

it('returns null if no name exists for address', async () => {
try {
await ethersAdapter.ensReverseLookup(ZERO_ADDRESS)
chai.expect(true).to.be.false
} catch (error) {
chai.expect((error as Error).message).to.equal(ErrorCodes._101)
}
})
})
})