Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

Commit

Permalink
feat: Add ENS Reverse Lookup to Create/Load Safe process (#3262)
Browse files Browse the repository at this point in the history
* feat: Add ENS Reverse Lookup to Load Safe process

* build: Add ens domain to provider info

* feat: Add ens reverse lookup function

* fix: ENS Lookup only on account change

* chore: Cleanup code + add types

* fix: Tweak logic + add return type

* chore: Remove empty string constant

* fix: Compare against ChainId

* feat: Add ENS Reverse Lookup to Load Safe process

* chore: Replace reverse ens lookup function from core sdk

* feat: Add reverse ENS Lookup to create safe process

* chore: Add typing, extract getOwnerName

* refactor: use getOwnerName for load safe review step

* chore: jest spy on ens getResolver to fix failing tests

* fix: Don't use typed value as placeholder for owner name

* fix: try catch ens resolver

* build: Use ENS domain part for safe creation and loading

* chore: Rename getDomainPart util function

* fix: Move initial ENS fetch, fetch ENS when adding new owners

* refactor: use absolute import paths

* refactor: Use absolute paths for imports

* fix: Get ENS if new owner is imported with qr code

Co-authored-by: iamacook <aaron.cook@gnosis.pm>
  • Loading branch information
usame-algan and iamacook authored Jan 21, 2022
1 parent e12d4fd commit dd8724e
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 86 deletions.
54 changes: 1 addition & 53 deletions src/logic/safe/utils/mocks/remoteConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,59 +64,6 @@
"SPENDING_LIMIT"
]
},
{
"transactionService": "https://safe-transaction.xdai.gnosis.io",
"chainId": "100",
"chainName": "Gnosis Chain",
"shortName": "gno",
"l2": true,
"description": "Gnosis Chain",
"rpcUri": {
"authentication": "NO_AUTHENTICATION",
"value": "https://rpc.xdaichain.com/oe-only/"
},
"safeAppsRpcUri": {
"authentication": "NO_AUTHENTICATION",
"value": "https://rpc.xdaichain.com/oe-only/"
},
"publicRpcUri": {
"authentication": "NO_AUTHENTICATION",
"value": "https://rpc.xdaichain.com/oe-only/"
},
"blockExplorerUriTemplate": {
"address": "https://blockscout.com/xdai/mainnet/address/{{address}}/transactions",
"txHash": "https://blockscout.com/xdai/mainnet/tx/{{txHash}}/",
"api": "https://blockscout.com/poa/xdai/api?module={{module}}&action={{action}}&address={{address}}&apiKey={{apiKey}}"
},
"nativeCurrency": {
"name": "xDai",
"symbol": "XDAI",
"decimals": 18,
"logoUri": "https://safe-transaction-assets.staging.gnosisdev.com/chains/100/currency_logo.png"
},
"theme": {
"textColor": "#ffffff",
"backgroundColor": "#48A9A6"
},
"gasPrice": [
{
"type": "FIXED",
"weiValue": "4000000000"
}
],
"disabledWallets": [
"metamask",
"walletConnect"
],
"features": [
"CONTRACT_INTERACTION",
"EIP1559",
"ERC721",
"SAFE_APPS",
"SAFE_TX_GAS_OPTIONAL",
"SPENDING_LIMIT"
]
},
{
"transactionService": "https://safe-transaction-polygon.staging.gnosisdev.com",
"chainId": "137",
Expand Down Expand Up @@ -174,6 +121,7 @@
],
"features": [
"CONTRACT_INTERACTION",
"EIP1559",
"ERC721",
"SAFE_APPS",
"SAFE_TX_GAS_OPTIONAL"
Expand Down
2 changes: 2 additions & 0 deletions src/logic/wallets/getWeb3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export const reverseENSLookup = async (address: string): Promise<string> => {
return verifiedAddress === address ? name : ''
}

export const removeTld = (name: string): string => name.replace(/\.[^.]+$/, '')

export const getContentFromENS = (name: string): Promise<ContentHash> => web3.eth.ens.getContenthash(name)

export const isTxPendingError = (err: Error): boolean => {
Expand Down
1 change: 1 addition & 0 deletions src/routes/CreateSafePage/CreateSafePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const estimateGasForDeployingSafeSpy = jest.spyOn(safeContracts, 'estimateGasFor
const calculateGasPriceSpy = jest.spyOn(ethTransactions, 'calculateGasPrice')

const getENSAddressSpy = jest.spyOn(getWeb3ReadOnly().eth.ens, 'getAddress')
jest.spyOn(getWeb3ReadOnly().eth.ens, 'getResolver')

jest.mock('src/logic/contracts/safeContracts', () => {
// Require the original module to not be mocked...
Expand Down
8 changes: 5 additions & 3 deletions src/routes/CreateSafePage/CreateSafePage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactElement, useState, useEffect } from 'react'
import { ReactElement, useEffect, useState } from 'react'
import IconButton from '@material-ui/core/IconButton'
import ChevronLeft from '@material-ui/icons/ChevronLeft'
import styled from 'styled-components'
Expand All @@ -12,7 +12,7 @@ import Block from 'src/components/layout/Block'
import Row from 'src/components/layout/Row'
import Heading from 'src/components/layout/Heading'
import { history } from 'src/routes/routes'
import { sm, secondary } from 'src/theme/variables'
import { secondary, sm } from 'src/theme/variables'
import StepperForm, { StepFormElement } from 'src/components/StepperForm/StepperForm'
import NameNewSafeStep, { nameNewSafeStepLabel } from './steps/NameNewSafeStep'
import {
Expand All @@ -22,6 +22,7 @@ import {
FIELD_MAX_OWNER_NUMBER,
FIELD_NEW_SAFE_PROXY_SALT,
FIELD_NEW_SAFE_THRESHOLD,
FIELD_SAFE_OWNER_ENS_LIST,
FIELD_SAFE_OWNERS_LIST,
SAFE_PENDING_CREATION_STORAGE_KEY,
} from './fields/createSafeFields'
Expand Down Expand Up @@ -143,7 +144,7 @@ function getInitialValues(userAddress, addressBook, location, suggestedSafeName)

// we set the owner names
const ownersNamesFromUrl = Array.isArray(ownernames) ? ownernames : [ownernames]
const userAddressName = [addressBook[userAddress]?.name || 'My Wallet']
const userAddressName = [addressBook[userAddress]?.name || '']
const ownerNames = isOwnersPresentInTheUrl ? ownersNamesFromUrl : userAddressName

const thresholdFromURl = Number(threshold)
Expand All @@ -158,6 +159,7 @@ function getInitialValues(userAddress, addressBook, location, suggestedSafeName)
nameFieldName: `owner-name-${index}`,
addressFieldName: `owner-address-${index}`,
})),
[FIELD_SAFE_OWNER_ENS_LIST]: {},
// we set owners address values as owner-address-${index} format in the form state
...owners.reduce(
(ownerAddressFields, ownerAddress, index) => ({
Expand Down
12 changes: 7 additions & 5 deletions src/routes/CreateSafePage/components/SafeCreationProcess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
FIELD_CREATE_CUSTOM_SAFE_NAME,
FIELD_NEW_SAFE_PROXY_SALT,
FIELD_NEW_SAFE_GAS_PRICE,
FIELD_SAFE_OWNER_ENS_LIST,
} from '../fields/createSafeFields'
import { getSafeInfo } from 'src/logic/safe/utils/safeInformation'
import { buildSafe } from 'src/logic/safe/store/actions/fetchSafe'
Expand Down Expand Up @@ -172,13 +173,14 @@ function SafeCreationProcess(): ReactElement {
const owners = createSafeFormValues[FIELD_SAFE_OWNERS_LIST]

// we update the address book with the owners and the new safe
const ownersAddressBookEntry = owners.map(({ nameFieldName, addressFieldName }) =>
makeAddressBookEntry({
const ownersAddressBookEntry = owners.map(({ nameFieldName, addressFieldName }) => {
const ownerAddress = createSafeFormValues[addressFieldName]
return makeAddressBookEntry({
address: createSafeFormValues[addressFieldName],
name: createSafeFormValues[nameFieldName],
name: createSafeFormValues[nameFieldName] || createSafeFormValues[FIELD_SAFE_OWNER_ENS_LIST][ownerAddress],
chainId,
}),
)
})
})
const safeAddressBookEntry = makeAddressBookEntry({ address: newSafeAddress, name: safeName, chainId })
await dispatch(addressBookSafeLoad([...ownersAddressBookEntry, safeAddressBookEntry]))

Expand Down
2 changes: 2 additions & 0 deletions src/routes/CreateSafePage/fields/createSafeFields.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const FIELD_CREATE_CUSTOM_SAFE_NAME = 'customSafeName'
export const FIELD_CREATE_SUGGESTED_SAFE_NAME = 'suggestedSafeName'
export const FIELD_SAFE_OWNERS_LIST = 'owners'
export const FIELD_SAFE_OWNER_ENS_LIST = 'safeOwnerENSList'
export const FIELD_NEW_SAFE_THRESHOLD = 'newSafeThreshold'
export const FIELD_MAX_OWNER_NUMBER = 'maxOwnerNumber'
export const FIELD_NEW_SAFE_PROXY_SALT = 'safeCreationSalt'
Expand All @@ -18,6 +19,7 @@ export type CreateSafeFormValues = {
[FIELD_CREATE_CUSTOM_SAFE_NAME]?: string
[FIELD_NEW_SAFE_THRESHOLD]: number
[FIELD_SAFE_OWNERS_LIST]: Array<OwnerFieldItem>
[FIELD_SAFE_OWNER_ENS_LIST]: Record<string, string>
[FIELD_MAX_OWNER_NUMBER]: number
[FIELD_NEW_SAFE_PROXY_SALT]: number
[FIELD_NEW_SAFE_GAS_LIMIT]: number
Expand Down
46 changes: 43 additions & 3 deletions src/routes/CreateSafePage/steps/NameNewSafeStep.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactElement, useEffect } from 'react'
import { ReactElement, useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { useForm } from 'react-final-form'
import styled from 'styled-components'
Expand All @@ -10,13 +10,20 @@ import Paragraph from 'src/components/layout/Paragraph'
import Field from 'src/components/forms/Field'
import TextField from 'src/components/forms/TextField'
import { providerNameSelector } from 'src/logic/wallets/store/selectors'
import { FIELD_CREATE_CUSTOM_SAFE_NAME, FIELD_CREATE_SUGGESTED_SAFE_NAME } from '../fields/createSafeFields'
import {
FIELD_CREATE_CUSTOM_SAFE_NAME,
FIELD_CREATE_SUGGESTED_SAFE_NAME,
FIELD_SAFE_OWNER_ENS_LIST,
FIELD_SAFE_OWNERS_LIST,
} from '../fields/createSafeFields'
import { useStepper } from 'src/components/Stepper/stepperContext'
import NetworkLabel from 'src/components/NetworkLabel/NetworkLabel'
import { removeTld, reverseENSLookup } from 'src/logic/wallets/getWeb3'

export const nameNewSafeStepLabel = 'Name'

function NameNewSafeStep(): ReactElement {
const [ownersWithENSName, setOwnersWithENSName] = useState<Record<string, string>>({})
const provider = useSelector(providerNameSelector)

const { setCurrentStep } = useStepper()
Expand All @@ -28,9 +35,42 @@ function NameNewSafeStep(): ReactElement {
}, [provider, setCurrentStep])

const createNewSafeForm = useForm()

const formValues = createNewSafeForm.getState().values

useEffect(() => {
const getInitialOwnerENSNames = async () => {
const formValues = createNewSafeForm.getState().values
const owners = formValues[FIELD_SAFE_OWNERS_LIST]
const ownersWithENSName = await Promise.all(
owners.map(async ({ addressFieldName }) => {
const address = formValues[addressFieldName]
const ensName = await reverseENSLookup(address)
const ensDomain = removeTld(ensName)
return {
address,
name: ensDomain,
}
}),
)

const ownersWithENSNameRecord = ownersWithENSName.reduce<Record<string, string>>((acc, { address, name }) => {
return {
...acc,
[address]: name,
}
}, {})

setOwnersWithENSName(ownersWithENSNameRecord)
}
getInitialOwnerENSNames()
}, [createNewSafeForm])

useEffect(() => {
if (ownersWithENSName) {
createNewSafeForm.change(FIELD_SAFE_OWNER_ENS_LIST, ownersWithENSName)
}
}, [ownersWithENSName, createNewSafeForm])

return (
<BlockWithPadding data-testid={'create-safe-name-step'}>
<Block margin="md">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ import {
required,
THRESHOLD_ERROR,
} from 'src/components/forms/validator'
import { FIELD_MAX_OWNER_NUMBER, FIELD_NEW_SAFE_THRESHOLD, FIELD_SAFE_OWNERS_LIST } from '../fields/createSafeFields'
import {
FIELD_MAX_OWNER_NUMBER,
FIELD_NEW_SAFE_THRESHOLD,
FIELD_SAFE_OWNER_ENS_LIST,
FIELD_SAFE_OWNERS_LIST,
} from '../fields/createSafeFields'
import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper'
import { currentNetworkAddressBookAsMap } from 'src/logic/addressBook/store/selectors'
import NetworkLabel from 'src/components/NetworkLabel/NetworkLabel'
import { removeTld, reverseENSLookup } from 'src/logic/wallets/getWeb3'

export const ownersAndConfirmationsNewSafeStepLabel = 'Owners and Confirmations'

Expand All @@ -53,6 +59,7 @@ function OwnersAndConfirmationsNewSafeStep(): ReactElement {
const formErrors = createSafeForm.getState().errors || {}

const owners = createSafeFormValues[FIELD_SAFE_OWNERS_LIST]
const ownersWithENSName = createSafeFormValues[FIELD_SAFE_OWNER_ENS_LIST]
const threshold = createSafeFormValues[FIELD_NEW_SAFE_THRESHOLD]
const maxOwnerNumber = createSafeFormValues[FIELD_MAX_OWNER_NUMBER]

Expand All @@ -68,16 +75,27 @@ function OwnersAndConfirmationsNewSafeStep(): ReactElement {

function onClickRemoveOwner({ addressFieldName }) {
const ownersUpdated = owners.filter((owner) => owner.addressFieldName !== addressFieldName)

createSafeForm.change(FIELD_SAFE_OWNERS_LIST, ownersUpdated)
createSafeForm.change(addressFieldName, undefined)

const hasToUpdateThreshold = threshold > ownersUpdated.length
const updatedMaxOwnerNumbers = maxOwnerNumber - 1
createSafeForm.change(FIELD_MAX_OWNER_NUMBER, updatedMaxOwnerNumbers)

const hasToUpdateThreshold = threshold > ownersUpdated.length
if (hasToUpdateThreshold) {
createSafeForm.change(FIELD_NEW_SAFE_THRESHOLD, threshold - 1)
}
}

const getENSName = async (address: string): Promise<void> => {
const ensName = await reverseENSLookup(address)
const ensDomain = removeTld(ensName)
const newOwnersWithENSName: Record<string, string> = Object.assign(ownersWithENSName, {
[address]: ensDomain,
})
createSafeForm.change(FIELD_SAFE_OWNER_ENS_LIST, newOwnersWithENSName)
}

return (
<>
<BlockWithPadding data-testid={'create-safe-owners-confirmation-step'}>
Expand Down Expand Up @@ -113,9 +131,12 @@ function OwnersAndConfirmationsNewSafeStep(): ReactElement {
<RowHeader>
{owners.map(({ nameFieldName, addressFieldName }) => {
const hasOwnerAddressError = formErrors[addressFieldName]
const ownerAddress = createSafeFormValues[addressFieldName]
const showDeleteIcon = addressFieldName !== 'owner-address-0' // we hide de delete icon for the first owner
const ownerName = ownersWithENSName[ownerAddress] || 'Owner Name'

const handleScan = (address: string, closeQrModal: () => void): void => {
const handleScan = async (address: string, closeQrModal: () => void): Promise<void> => {
await getENSName(address)
createSafeForm.change(addressFieldName, address)
closeQrModal()
}
Expand All @@ -126,7 +147,7 @@ function OwnersAndConfirmationsNewSafeStep(): ReactElement {
<OwnerNameField
component={TextField}
name={nameFieldName}
placeholder="Owner Name"
placeholder={ownerName}
text="Owner Name"
type="text"
validate={minMaxLength(0, 50)}
Expand All @@ -135,7 +156,8 @@ function OwnersAndConfirmationsNewSafeStep(): ReactElement {
</Col>
<Col xs={7}>
<AddressInput
fieldMutator={(address) => {
fieldMutator={async (address) => {
await getENSName(address)
createSafeForm.change(addressFieldName, address)
const addressName = addressBook[address]?.name
if (addressName) {
Expand Down
4 changes: 3 additions & 1 deletion src/routes/CreateSafePage/steps/ReviewNewSafeStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
FIELD_NEW_SAFE_GAS_PRICE,
FIELD_NEW_SAFE_PROXY_SALT,
FIELD_NEW_SAFE_THRESHOLD,
FIELD_SAFE_OWNER_ENS_LIST,
FIELD_SAFE_OWNERS_LIST,
} from '../fields/createSafeFields'
import { getExplorerInfo, getNativeCurrency } from 'src/config'
Expand Down Expand Up @@ -46,6 +47,7 @@ function ReviewNewSafeStep(): ReactElement | null {
const safeName = createSafeFormValues[FIELD_CREATE_CUSTOM_SAFE_NAME] || defaultSafeValue
const threshold = createSafeFormValues[FIELD_NEW_SAFE_THRESHOLD]
const owners = createSafeFormValues[FIELD_SAFE_OWNERS_LIST]
const ownersWithENSName = createSafeFormValues[FIELD_SAFE_OWNER_ENS_LIST]
const numberOfOwners = owners.length
const safeCreationSalt = createSafeFormValues[FIELD_NEW_SAFE_PROXY_SALT]
const ownerAddresses = owners.map(({ addressFieldName }) => createSafeFormValues[addressFieldName])
Expand Down Expand Up @@ -110,8 +112,8 @@ function ReviewNewSafeStep(): ReactElement | null {
</TitleContainer>
<Hairline />
{owners.map(({ nameFieldName, addressFieldName }) => {
const ownerName = createSafeFormValues[nameFieldName]
const ownerAddress = createSafeFormValues[addressFieldName]
const ownerName = createSafeFormValues[nameFieldName] || ownersWithENSName[ownerAddress]
return (
<React.Fragment key={`owner-${addressFieldName}`}>
<OwnersAddressesContainer>
Expand Down
1 change: 1 addition & 0 deletions src/routes/LoadSafePage/LoadSafePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as safeVersion from 'src/logic/safe/utils/safeVersion'
import { GATEWAY_URL } from 'src/utils/constants'

const getENSAddressSpy = jest.spyOn(getWeb3ReadOnly().eth.ens, 'getAddress')
jest.spyOn(getWeb3ReadOnly().eth.ens, 'getResolver')

jest.spyOn(safeVersion, 'getSafeVersionInfo').mockImplementation(async () => ({
current: '1.3.0',
Expand Down
Loading

0 comments on commit dd8724e

Please sign in to comment.