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

Resolve local names for each network #2555

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions background/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
deleteAccount,
loadAccount,
updateAccountBalance,
updateAccountLocalName,
updateAccountName,
updateENSAvatar,
} from "./redux-slices/accounts"
Expand Down Expand Up @@ -907,6 +908,19 @@ export default class Main extends BaseService<never> {
this.store.dispatch(updateAccountName({ ...addressOnNetwork, name }))
}
)
this.nameService.emitter.on(
"resolvedLocalName",
async ({
from: {
addressOnNetwork: { address }, // ignore network
},
resolved: {
nameOnNetwork: { name },
},
}) => {
this.store.dispatch(updateAccountLocalName({ address, name }))
}
)
this.nameService.emitter.on(
"resolvedAvatar",
async ({ from: { addressOnNetwork }, resolved: { avatar } }) => {
Expand Down
36 changes: 33 additions & 3 deletions background/redux-slices/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import { DomainName, HexString, URI } from "../types"
import { normalizeEVMAddress } from "../lib/utils"
import { AccountSigner } from "../services/signing"
import { TEST_NETWORK_BY_CHAIN_ID } from "../constants"
import { NETWORK_BY_CHAIN_ID, TEST_NETWORK_BY_CHAIN_ID } from "../constants"
import { convertFixedPoint } from "../lib/fixed-point"

/**
Expand Down Expand Up @@ -347,8 +347,6 @@ const accountSlice = createSlice({
immerState.accountsData.evm[network.chainID] ??= {}

const baseAccountData = getOrCreateAccountData(
// TODO Figure out the best way to handle default name assignment
// TODO across networks.
immerState,
normalizedAddress,
network
Expand All @@ -359,6 +357,37 @@ const accountSlice = createSlice({
ens: { ...baseAccountData.ens, name },
}
},
updateAccountLocalName: (
immerState,
{
payload: { address, name },
}: { payload: { address: string; name: DomainName } }
) => {
const normalizedAddress = normalizeEVMAddress(address)

Object.keys(immerState.accountsData.evm).forEach((chainID) => {
if (
immerState.accountsData.evm[chainID]?.[normalizedAddress] ===
undefined
) {
return
}

immerState.accountsData.evm[chainID] ??= {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can omit this because of the previous check right?


const network = NETWORK_BY_CHAIN_ID[chainID]
const baseAccountData = getOrCreateAccountData(
immerState,
normalizedAddress,
network
)

immerState.accountsData.evm[chainID][normalizedAddress] = {
...baseAccountData,
ens: { ...baseAccountData.ens, name },
}
})
},
updateENSAvatar: (
immerState,
{
Expand Down Expand Up @@ -399,6 +428,7 @@ export const {
loadAccount,
updateAccountBalance,
updateAccountName,
updateAccountLocalName,
updateENSAvatar,
} = accountSlice.actions

Expand Down
89 changes: 53 additions & 36 deletions background/services/name/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type ResolvedAvatarRecord = {
type Events = ServiceLifecycleEvents & {
resolvedAddress: ResolvedAddressRecord
resolvedName: ResolvedNameRecord
resolvedLocalName: ResolvedNameRecord
resolvedAvatar: ResolvedAvatarRecord
}

Expand Down Expand Up @@ -130,7 +131,6 @@ export default class NameService extends BaseService<Events> {
preferenceService.emitter.on(
"addressBookEntryModified",
async ({ network, address }) => {
this.clearNameCacheEntry(network.chainID, address)
await this.lookUpName({ network, address })
}
)
Expand Down Expand Up @@ -207,6 +207,52 @@ export default class NameService extends BaseService<Events> {
): Promise<ResolvedNameRecord | undefined> {
const { address, network } = normalizeAddressOnNetwork(addressOnNetwork)

const workingResolvers = this.resolvers.filter((resolver) =>
resolver.canAttemptNameResolution({ address, network })
)

// check local address book
const localResolvers = [...workingResolvers].filter(
(resolver) =>
resolver.type === "tally-address-book" ||
resolver.type === "tally-known-contracts"
)

const localResolution = (
await Promise.allSettled(
localResolvers.map(async (resolver) => ({
type: resolver.type,
resolved: await resolver.lookUpNameForAddress({ address, network }),
}))
)
)
.filter(isFulfilledPromise)
.find(({ value: { resolved } }) => resolved !== undefined)?.value

if (
typeof localResolution !== "undefined" &&
typeof localResolution.resolved !== "undefined"
) {
const { type: system, resolved: nameOnNetwork } = localResolution

const nameRecord = {
from: { addressOnNetwork: { address, network } },
resolved: {
nameOnNetwork,
// TODO Read this from the name service; for now, this avoids infinite
// TODO resolution loops.
expiresAt: Date.now() + MINIMUM_RECORD_EXPIRY,
},
system,
} as const

// emmit local names without a network and update all address networks paird in the redux?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// emmit local names without a network and update all address networks paird in the redux?
// Emit local names without a network and update all address network pairs in Redux

this.emitter.emit("resolvedLocalName", nameRecord)

return nameRecord
}

// if there is no loacal name then look deeper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// if there is no loacal name then look deeper
// If there is no local name then look deeper

if (!this.cachedResolvedNames[network.family][network.chainID]) {
this.cachedResolvedNames[network.family][network.chainID] = {}
}
Expand All @@ -224,24 +270,15 @@ export default class NameService extends BaseService<Events> {
}
}

const workingResolvers = this.resolvers.filter((resolver) =>
resolver.canAttemptNameResolution({ address, network })
)

const localResolvers = [...workingResolvers].filter(
(resolver) =>
resolver.type === "tally-address-book" ||
resolver.type === "tally-known-contracts"
)
const remoteResolvers = [...workingResolvers].filter(
(resolver) =>
resolver.type !== "tally-address-book" &&
resolver.type !== "tally-known-contracts"
)

let firstMatchingResolution = (
const remoteResolution = (
await Promise.allSettled(
localResolvers.map(async (resolver) => ({
remoteResolvers.map(async (resolver) => ({
type: resolver.type,
resolved: await resolver.lookUpNameForAddress({ address, network }),
}))
Expand All @@ -250,28 +287,14 @@ export default class NameService extends BaseService<Events> {
.filter(isFulfilledPromise)
.find(({ value: { resolved } }) => resolved !== undefined)?.value

if (!firstMatchingResolution) {
firstMatchingResolution = (
await Promise.allSettled(
remoteResolvers.map(async (resolver) => ({
type: resolver.type,
resolved: await resolver.lookUpNameForAddress({ address, network }),
}))
)
)
.filter(isFulfilledPromise)
.find(({ value: { resolved } }) => resolved !== undefined)?.value
}

if (
firstMatchingResolution === undefined ||
firstMatchingResolution.resolved === undefined
remoteResolution === undefined ||
remoteResolution.resolved === undefined
) {
return undefined
}

const { type: resolverType, resolved: nameOnNetwork } =
firstMatchingResolution
const { type: system, resolved: nameOnNetwork } = remoteResolution

const nameRecord = {
from: { addressOnNetwork: { address, network } },
Expand All @@ -281,7 +304,7 @@ export default class NameService extends BaseService<Events> {
// TODO resolution loops.
expiresAt: Date.now() + MINIMUM_RECORD_EXPIRY,
},
system: resolverType,
system,
} as const

const cachedNameOnNetwork = cachedResolvedNameRecord?.resolved.nameOnNetwork
Expand All @@ -297,12 +320,6 @@ export default class NameService extends BaseService<Events> {
return nameRecord
}

clearNameCacheEntry(chainId: string, address: HexString): void {
if (this.cachedResolvedNames.EVM[chainId]?.[address] !== undefined) {
delete this.cachedResolvedNames.EVM[chainId][address]
}
}

async lookUpAvatar(
addressOnNetwork: AddressOnNetwork
): Promise<ResolvedAvatarRecord | undefined> {
Expand Down
65 changes: 39 additions & 26 deletions background/services/preferences/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ import { HexString } from "../../types"
import { AccountSignerSettings } from "../../ui"
import { AccountSignerWithId } from "../../signing"

type AddressBookEntry = {
type ContractAddressBookEntry = {
network: EVMNetwork
address: HexString
name: string
}

type InMemoryAddressBook = AddressBookEntry[]

const sameAddressBookEntry = (a: AddressOnNetwork, b: AddressOnNetwork) =>
normalizeEVMAddress(a.address) === normalizeEVMAddress(b.address) &&
sameNetwork(a.network, b.network)
type CustomAddressBookEntry = {
// no network to make custom names global
address: HexString
name: string
}

const BUILT_IN_CONTRACTS = [
{
Expand Down Expand Up @@ -96,8 +96,8 @@ interface Events extends ServiceLifecycleEvents {
preferencesChanges: Preferences
initializeDefaultWallet: boolean
initializeSelectedAccount: AddressOnNetwork
addressBookEntryModified: AddressOnNetwork & { name: string }
updateAnalyticsPreferences: AnalyticsPreferences
addressBookEntryModified: AddressBookEntry
updatedSignerSettings: AccountSignerSettings[]
}

Expand All @@ -106,9 +106,9 @@ interface Events extends ServiceLifecycleEvents {
* event when preferences change.
*/
export default class PreferenceService extends BaseService<Events> {
private knownContracts: InMemoryAddressBook = BUILT_IN_CONTRACTS
private knownContracts: ContractAddressBookEntry[] = BUILT_IN_CONTRACTS

private addressBook: InMemoryAddressBook = []
private addressBook: CustomAddressBookEntry[] = []

/*
* Create a new PreferenceService. The service isn't initialized until
Expand Down Expand Up @@ -148,38 +148,51 @@ export default class PreferenceService extends BaseService<Events> {
// TODO Implement the following 6 methods as something stored in the database and user-manageable.
// TODO Track account names in the UI in the address book.

addOrEditNameInAddressBook(newEntry: AddressBookEntry): void {
const correspondingEntryIndex = this.addressBook.findIndex((entry) =>
sameAddressBookEntry(newEntry, entry)
addOrEditNameInAddressBook({
address,
network,
name,
}: {
network: EVMNetwork
address: HexString
name: string
}): void {
const newEntry = {
address: normalizeEVMAddress(address),
name,
}
const correspondingEntryIndex = this.addressBook.findIndex(
(entry) =>
normalizeEVMAddress(newEntry.address) ===
normalizeEVMAddress(entry.address)
)
if (correspondingEntryIndex !== -1) {
this.addressBook[correspondingEntryIndex] = newEntry
} else {
this.addressBook.push({
network: newEntry.network,
name: newEntry.name,
address: normalizeEVMAddress(newEntry.address),
})
this.addressBook.push(newEntry)
}
this.emitter.emit("addressBookEntryModified", newEntry)
this.emitter.emit("addressBookEntryModified", { ...newEntry, network })
}

lookUpAddressForName({
name,
network,
}: NameOnNetwork): AddressOnNetwork | undefined {
return this.addressBook.find(
({ name: entryName, network: entryNetwork }) =>
sameNetwork(network, entryNetwork) && name === entryName
const entry = this.addressBook.find(
({ name: entryName }) => name === entryName
)
return entry ? { address: entry.address, network } : undefined
}

lookUpNameForAddress(
addressOnNetwork: AddressOnNetwork
): NameOnNetwork | undefined {
return this.addressBook.find((addressBookEntry) =>
sameAddressBookEntry(addressBookEntry, addressOnNetwork)
lookUpNameForAddress({
address,
network,
}: AddressOnNetwork): NameOnNetwork | undefined {
const entry = this.addressBook.find(
({ address: entryAddress }) =>
normalizeEVMAddress(entryAddress) === normalizeEVMAddress(address)
)
return entry ? { name: entry.name, network } : undefined
}

async lookUpAddressForContractName({
Expand Down
3 changes: 1 addition & 2 deletions ui/components/AccountItem/AccountItemEditName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default function AccountItemEditName({
<SharedInput
label=""
placeholder={t("typeNewName")}
autoSelect
errorMessage={error}
onChange={(value) => {
if (!touched) {
Expand Down Expand Up @@ -125,8 +126,6 @@ export default function AccountItemEditName({
margin-top: 0px;
}
.details {
display: flex;
flex-direction: column;
line-height: 24px;
font-size: 16px;
margin-top: 21px;
Expand Down