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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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 @@ -47,6 +47,7 @@ import {
deleteAccount,
loadAccount,
updateAccountBalance,
updateAccountLocalName,
updateAccountName,
updateENSAvatar,
} from "./redux-slices/accounts"
Expand Down Expand Up @@ -959,6 +960,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
34 changes: 31 additions & 3 deletions background/redux-slices/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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 @@ -350,8 +350,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 @@ -362,6 +360,35 @@ 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
}

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 @@ -402,6 +429,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 @@ -214,6 +214,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

// 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 local name then look deeper
if (!this.cachedResolvedNames[network.family][network.chainID]) {
this.cachedResolvedNames[network.family][network.chainID] = {}
}
Expand All @@ -231,24 +277,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 @@ -257,28 +294,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 @@ -288,7 +311,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 @@ -304,12 +327,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
2 changes: 0 additions & 2 deletions ui/components/AccountItem/AccountItemEditName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,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