Skip to content

Commit

Permalink
feat: WC Names integration (#2122)
Browse files Browse the repository at this point in the history
@svenvoskamp merging as you are OOO. Please let me know if there's anything that wasn't addressed
  • Loading branch information
tomiir authored May 24, 2024
1 parent 99db024 commit 6476885
Show file tree
Hide file tree
Showing 48 changed files with 1,264 additions and 110 deletions.
22 changes: 22 additions & 0 deletions apps/gallery/stories/composites/wui-ens-input.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Meta } from '@storybook/web-components'
import '@web3modal/ui/src/composites/wui-ens-input'
import type { WuiEnsInput } from '@web3modal/ui'
import { html } from 'lit'
import '../../components/gallery-container'

type Component = Meta<WuiEnsInput>

export default {
title: 'Composites/wui-ens-input',
args: {
errorMessage: '',
disabled: false
}
} as Component

export const Default: Component = {
render: args =>
html`<gallery-container width="336"
><wui-ens-input .errorMessage=${args.errorMessage} .disabled=${args.disabled}></wui-ens-input
></gallery-container>`
}
2 changes: 1 addition & 1 deletion apps/gallery/stories/composites/wui-icon-box.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default {
argTypes: {
size: {
defaultValue: 'md',
options: ['sm', 'md', 'lg'],
options: ['sm', 'md', 'lg', 'xl'],
control: { type: 'select' }
},
backgroundColor: {
Expand Down
4 changes: 4 additions & 0 deletions apps/laboratory/tests/shared/utils/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,8 @@ export class Email {

return `w3m-w${index}${randIndex}@${domain}`
}

getSmartAccountEnabledEmail(): string {
return 'web3modal-smart-account@mailsac.com'
}
}
2 changes: 2 additions & 0 deletions packages/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ export { DateUtil } from './src/utils/DateUtil.js'
export { NetworkUtil } from './src/utils/NetworkUtil.js'
export { NumberUtil } from './src/utils/NumberUtil.js'
export { erc20ABI } from './src/contracts/erc20.js'
export { NavigationUtil } from './src/utils/NavigationUtil.js'
export { ConstantsUtil } from './src/utils/ConstantsUtil.js'
export * from './src/utils/ThemeUtil.js'
export type * from './src/utils/TypeUtil.js'
3 changes: 3 additions & 0 deletions packages/common/src/utils/ConstantsUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ConstantsUtil = {
WC_NAME_SUFFIX: '.wcn.id'
}
5 changes: 5 additions & 0 deletions packages/common/src/utils/NavigationUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const NavigationUtil = {
URLS: {
FAQ: 'https://walletconnect.com/faq'
}
}
3 changes: 3 additions & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export type { SendControllerState } from './src/controllers/SendController.js'
export { TooltipController } from './src/controllers/TooltipController.js'
export type { TooltipControllerState } from './src/controllers/TooltipController.js'

export { EnsController } from './src/controllers/EnsController.js'
export type { EnsControllerState } from './src/controllers/EnsController.js'

// -- Utils -------------------------------------------------------------------
export { AssetUtil } from './src/utils/AssetUtil.js'
export { ConstantsUtil } from './src/utils/ConstantsUtil.js'
Expand Down
39 changes: 38 additions & 1 deletion packages/core/src/controllers/BlockchainApiController.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ConstantsUtil } from '../utils/ConstantsUtil.js'
import { CoreHelperUtil } from '../utils/CoreHelperUtil.js'
import { FetchUtil } from '../utils/FetchUtil.js'
import { ConstantsUtil as CommonConstantsUtil } from '@web3modal/common'
import type {
BlockchainApiTransactionsRequest,
BlockchainApiTransactionsResponse,
Expand All @@ -23,7 +24,10 @@ import type {
OnrampQuote,
PaymentCurrency,
PurchaseCurrency,
BlockchainApiBalanceResponse
BlockchainApiBalanceResponse,
BlockchainApiLookupEnsName,
BlockchainApiSuggestionResponse,
BlockchainApiRegisterNameParams
} from '../utils/TypeUtil.js'
import { OptionsController } from './OptionsController.js'

Expand Down Expand Up @@ -242,6 +246,39 @@ export const BlockchainApiController = {
})
},

async lookupEnsName(name: string) {
return api.get<BlockchainApiLookupEnsName>({
path: `/v1/profile/account/${name}${CommonConstantsUtil.WC_NAME_SUFFIX}?projectId=${OptionsController.state.projectId}`
})
},

async reverseLookupEnsName({ address }: { address: string }) {
return api.get<BlockchainApiLookupEnsName[]>({
path: `/v1/profile/reverse/${address}?projectId=${OptionsController.state.projectId}`
})
},

async getEnsNameSuggestions(name: string) {
return api.get<BlockchainApiSuggestionResponse>({
path: `/v1/profile/suggestions/${name}?projectId=${OptionsController.state.projectId}`
})
},

async registerEnsName({
coinType,
address,
message,
signature
}: BlockchainApiRegisterNameParams) {
return api.post({
path: `/v1/profile/account`,
body: { coin_type: coinType, address, message, signature },
headers: {
'Content-Type': 'application/json'
}
})
},

async generateOnRampURL({
destinationWallets,
partnerUserId,
Expand Down
162 changes: 162 additions & 0 deletions packages/core/src/controllers/EnsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { subscribeKey as subKey } from 'valtio/utils'
import { proxy, subscribe as sub } from 'valtio/vanilla'
import { BlockchainApiController } from './BlockchainApiController.js'
import type { BlockchainApiEnsError } from '../utils/TypeUtil.js'
import { AccountController } from './AccountController.js'
import { ConnectorController } from './ConnectorController.js'
import { RouterController } from './RouterController.js'
import { ConnectionController } from './ConnectionController.js'
import { NetworkController } from './NetworkController.js'
import { NetworkUtil } from '@web3modal/common'
import { EnsUtil } from '../utils/EnsUtil.js'
import { ConstantsUtil } from '@web3modal/common'

// -- Types --------------------------------------------- //
type Suggestion = {
name: string
registered: boolean
}

export interface EnsControllerState {
suggestions: Suggestion[]
loading: boolean
}

type StateKey = keyof EnsControllerState

// -- State --------------------------------------------- //
const state = proxy<EnsControllerState>({
suggestions: [],
loading: false
})

// -- Controller ---------------------------------------- //
export const EnsController = {
state,

subscribe(callback: (newState: EnsControllerState) => void) {
return sub(state, () => callback(state))
},

subscribeKey<K extends StateKey>(key: K, callback: (value: EnsControllerState[K]) => void) {
return subKey(state, key, callback)
},

async resolveName(name: string) {
try {
return await BlockchainApiController.lookupEnsName(name)
} catch (e) {
const error = e as BlockchainApiEnsError
throw new Error(error?.reasons?.[0]?.description || 'Error resolving name')
}
},

async isNameRegistered(name: string) {
try {
await BlockchainApiController.lookupEnsName(name)

return true
} catch {
return false
}
},

async getSuggestions(name: string) {
try {
state.loading = true
state.suggestions = []
const response = await BlockchainApiController.getEnsNameSuggestions(name)
state.suggestions =
response.suggestions.map(suggestion => ({
...suggestion,
name: suggestion.name.replace(ConstantsUtil.WC_NAME_SUFFIX, '')
})) || []

return state.suggestions
} catch (e) {
const errorMessage = this.parseEnsApiError(e, 'Error fetching name suggestions')
throw new Error(errorMessage)
} finally {
state.loading = false
}
},

async getNamesForAddress(address: string) {
try {
const network = NetworkController.state.caipNetwork
if (!network) {
return []
}

const response = await BlockchainApiController.reverseLookupEnsName({ address })

return response
} catch (e) {
const errorMessage = this.parseEnsApiError(e, 'Error fetching names for address')
throw new Error(errorMessage)
}
},

async registerName(name: string) {
const network = NetworkController.state.caipNetwork
if (!network) {
throw new Error('Network not found')
}
const address = AccountController.state.address
const emailConnector = ConnectorController.getAuthConnector()
if (!address || !emailConnector) {
throw new Error('Address or auth connector not found')
}

state.loading = true

try {
const message = JSON.stringify({
name: `${name}${ConstantsUtil.WC_NAME_SUFFIX}`,
attributes: {},
timestamp: Math.floor(Date.now() / 1000)
})

RouterController.pushTransactionStack({
view: 'RegisterAccountNameSuccess',
goBack: false,
replace: true,
onCancel() {
state.loading = false
}
})

const signature = await ConnectionController.signMessage(message)
const networkId = NetworkUtil.caipNetworkIdToNumber(network.id)

if (!networkId) {
throw new Error('Network not found')
}

const coinType = EnsUtil.convertEVMChainIdToCoinType(networkId)
await BlockchainApiController.registerEnsName({
coinType,
address: address as `0x${string}`,
signature,
message
})

AccountController.setProfileName(`${name}${ConstantsUtil.WC_NAME_SUFFIX}`)
RouterController.replace('RegisterAccountNameSuccess')
} catch (e) {
const errorMessage = this.parseEnsApiError(e, `Error registering name ${name}`)
RouterController.replace('RegisterAccountName')
throw new Error(errorMessage)
} finally {
state.loading = false
}
},
validateName(name: string) {
return /^[a-zA-Z0-9-]{4,}$/u.test(name)
},
parseEnsApiError(error: unknown, defaultError: string) {
const ensError = error as BlockchainApiEnsError

return ensError?.reasons?.[0]?.description || defaultError
}
}
3 changes: 3 additions & 0 deletions packages/core/src/controllers/RouterController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface RouterControllerState {
| 'ApproveTransaction'
| 'BuyInProgress'
| 'WalletCompatibleNetworks'
| 'ChooseAccountName'
| 'Connect'
| 'ConnectingExternal'
| 'ConnectingWalletConnect'
Expand All @@ -36,6 +37,8 @@ export interface RouterControllerState {
| 'OnRampFiatSelect'
| 'OnRampProviders'
| 'OnRampTokenSelect'
| 'RegisterAccountName'
| 'RegisterAccountNameSuccess'
| 'SwitchNetwork'
| 'Transactions'
| 'UnsupportedChain'
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/controllers/ThemeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const ThemeController = {
}
} catch {
// eslint-disable-next-line no-console
console.info('Unable to sync theme to email connector')
console.info('Unable to sync theme to auth connector')
}
},

Expand All @@ -63,7 +63,7 @@ export const ThemeController = {
}
} catch {
// eslint-disable-next-line no-console
console.info('Unable to sync theme to email connector')
console.info('Unable to sync theme to auth connector')
}
},

Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/utils/EnsUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const SLIP44_MSB = 0x80000000

export const EnsUtil = {
convertEVMChainIdToCoinType(chainId: number): number {
if (chainId >= SLIP44_MSB) {
throw new Error('Invalid chainId')
}

return (SLIP44_MSB | chainId) >>> 0
}
}
Loading

0 comments on commit 6476885

Please sign in to comment.