+
+
@@ -108,7 +79,7 @@ const UserMenu = ({ onConnect, onDisconnect }: UserMenuProps): JSX.Element => {
>
diff --git a/helpers/env.ts b/helpers/env.ts
index 705a01d1..391bdea1 100644
--- a/helpers/env.ts
+++ b/helpers/env.ts
@@ -9,6 +9,10 @@ export const getEnv = (): 'dev' | 'production' | 'local' => {
return 'dev'
}
+export const isAppEnvDemo = (): boolean => {
+ return process.env.NEXT_PUBLIC_APP_ENVIRONMENT === 'demo'
+}
+
export const tagStr = (): string | null => {
return getEnv() === 'production' ? null : getEnv().toLocaleUpperCase()
}
diff --git a/hooks/useDisconnect.ts b/hooks/useDisconnect.ts
new file mode 100644
index 00000000..cee66f4b
--- /dev/null
+++ b/hooks/useDisconnect.ts
@@ -0,0 +1,25 @@
+import { useRouter } from 'next/router'
+import { useCallback } from 'react'
+import { useAppStore } from '../store/app'
+
+const useDisconnect = () => {
+ const router = useRouter()
+ const reset = useAppStore((state) => state.reset)
+
+ const disconnect = useCallback(() => {
+ Object.keys(localStorage).forEach((key) => {
+ if (key.startsWith('xmtp')) {
+ localStorage.removeItem(key)
+ }
+ })
+ reset()
+ router.push('/')
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [router])
+
+ return {
+ disconnect,
+ }
+}
+
+export default useDisconnect
diff --git a/hooks/useEns.ts b/hooks/useEns.ts
deleted file mode 100644
index d93be157..00000000
--- a/hooks/useEns.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useState, useEffect } from 'react'
-import useWalletProvider from './useWalletProvider'
-
-const useEns = (addressOrName: string | undefined) => {
- const { resolveName, lookupAddress, getAvatarUrl } = useWalletProvider()
- const [address, setAddress] = useState
('')
- const [name, setName] = useState('')
- const [avatarUrl, setAvatarUrl] = useState('')
- const [loading, setLoading] = useState(true)
- const probableAddress =
- addressOrName?.startsWith('0x') && addressOrName?.length === 42
- ? addressOrName
- : undefined
- const probableName = addressOrName?.endsWith('.eth')
- ? addressOrName
- : undefined
-
- useEffect(() => {
- if (!resolveName || !lookupAddress || !getAvatarUrl) {
- return
- }
- const initAvatarUrl = async (name: string) => {
- setAvatarUrl((await getAvatarUrl(name)) as string)
- }
- const initName = async (probableAddress: string) => {
- setLoading(true)
- setName((await lookupAddress(probableAddress)) as string)
- if (name) {
- await initAvatarUrl(name)
- }
- setLoading(false)
- }
- const initAddress = async (probableName: string) => {
- setLoading(true)
- setAddress((await resolveName(probableName)) as string)
- await initAvatarUrl(probableName)
- setLoading(false)
- }
- if (probableAddress) {
- initName(probableAddress)
- }
- if (probableName) {
- initAddress(probableName)
- }
- }, [
- probableName,
- probableAddress,
- name,
- resolveName,
- lookupAddress,
- getAvatarUrl,
- ])
-
- return {
- address: probableAddress || (address as string | undefined),
- name: probableName || (name as string | undefined),
- avatarUrl: avatarUrl as string | undefined,
- loading,
- }
-}
-
-export default useEns
diff --git a/hooks/useEnsHooks.ts b/hooks/useEnsHooks.ts
new file mode 100644
index 00000000..ab9b5c3b
--- /dev/null
+++ b/hooks/useEnsHooks.ts
@@ -0,0 +1,80 @@
+import { useCallback, useState } from 'react'
+import { useAppStore } from '../store/app'
+
+// Ethereum mainnet
+const ETH_CHAIN_ID = 1
+
+const cachedLookupAddress = new Map()
+const cachedResolveName = new Map()
+const cachedGetAvatarUrl = new Map()
+
+const useEnsHooks = () => {
+ const [loading, setLoading] = useState(false)
+ const provider = useAppStore((state) => state.provider)
+
+ const resolveName = useCallback(
+ async (name: string) => {
+ if (cachedResolveName.has(name)) {
+ return cachedResolveName.get(name)
+ }
+
+ setLoading(true)
+ const { chainId } = (await provider?.getNetwork()) || {}
+
+ if (Number(chainId) !== ETH_CHAIN_ID) {
+ return undefined
+ }
+ const address = (await provider?.resolveName(name)) || undefined
+ cachedResolveName.set(name, address)
+ setLoading(false)
+ return address
+ },
+ [provider]
+ )
+
+ const lookupAddress = useCallback(
+ async (address: string) => {
+ if (cachedLookupAddress.has(address)) {
+ return cachedLookupAddress.get(address)
+ }
+ setLoading(true)
+ const { chainId } = (await provider?.getNetwork()) || {}
+
+ if (Number(chainId) !== ETH_CHAIN_ID) {
+ return undefined
+ }
+
+ const name = (await provider?.lookupAddress(address)) || undefined
+ cachedLookupAddress.set(address, name)
+ setLoading(false)
+ return name
+ },
+ [provider]
+ )
+
+ const getAvatarUrl = useCallback(
+ async (address: string) => {
+ const name = await lookupAddress(address)
+ if (name) {
+ if (cachedGetAvatarUrl.has(name)) {
+ return cachedGetAvatarUrl.get(name)
+ }
+ setLoading(true)
+ const avatarUrl = (await provider?.getAvatar(name)) || undefined
+ cachedGetAvatarUrl.set(name, avatarUrl)
+ setLoading(false)
+ return avatarUrl
+ }
+ },
+ [provider]
+ )
+
+ return {
+ resolveName,
+ lookupAddress,
+ getAvatarUrl,
+ loading,
+ }
+}
+
+export default useEnsHooks
diff --git a/hooks/useListConversations.tsx b/hooks/useListConversations.tsx
index c1cc7226..c309a025 100644
--- a/hooks/useListConversations.tsx
+++ b/hooks/useListConversations.tsx
@@ -7,13 +7,13 @@ import {
import { useEffect, useState } from 'react'
import { getConversationKey, shortAddress, truncate } from '../helpers'
import { useAppStore } from '../store/app'
-import useWalletProvider from './useWalletProvider'
+import useEnsHooks from './useEnsHooks'
let latestMsgId: string
export const useListConversations = () => {
const walletAddress = useAppStore((state) => state.address)
- const { lookupAddress } = useWalletProvider()
+ const { lookupAddress } = useEnsHooks()
const convoMessages = useAppStore((state) => state.convoMessages)
const client = useAppStore((state) => state.client)
const conversations = useAppStore((state) => state.conversations)
diff --git a/hooks/useWalletProvider.tsx b/hooks/useWalletProvider.tsx
index 63476093..f751e133 100644
--- a/hooks/useWalletProvider.tsx
+++ b/hooks/useWalletProvider.tsx
@@ -3,102 +3,45 @@ import { ethers } from 'ethers'
import Web3Modal, { IProviderOptions, providers } from 'web3modal'
import WalletConnectProvider from '@walletconnect/web3-provider'
import WalletLink from 'walletlink'
-import { useRouter } from 'next/router'
import { useAppStore } from '../store/app'
-
-// Ethereum mainnet
-const ETH_CHAIN_ID = 1
-
-const cachedLookupAddress = new Map()
-const cachedResolveName = new Map()
-const cachedGetAvatarUrl = new Map()
-
-// This variables are not added in state on purpose.
-// It saves few re-renders which then trigger the children to re-render
-// Consider the above while moving it to state variables
-let provider: ethers.providers.Web3Provider
+import { isAppEnvDemo } from '../helpers'
+import useDisconnect from './useDisconnect'
const useWalletProvider = () => {
const [web3Modal, setWeb3Modal] = useState()
const setAddress = useAppStore((state) => state.setAddress)
const setSigner = useAppStore((state) => state.setSigner)
- const reset = useAppStore((state) => state.reset)
- const router = useRouter()
-
- const resolveName = useCallback(async (name: string) => {
- if (cachedResolveName.has(name)) {
- return cachedResolveName.get(name)
- }
-
- const { chainId } = (await provider?.getNetwork()) || {}
-
- if (Number(chainId) !== ETH_CHAIN_ID) {
- return undefined
- }
- const address = (await provider?.resolveName(name)) || undefined
- cachedResolveName.set(name, address)
- return address
- }, [])
-
- const lookupAddress = useCallback(async (address: string) => {
- if (cachedLookupAddress.has(address)) {
- return cachedLookupAddress.get(address)
- }
- const { chainId } = (await provider?.getNetwork()) || {}
-
- if (Number(chainId) !== ETH_CHAIN_ID) {
- return undefined
- }
-
- const name = (await provider?.lookupAddress(address)) || undefined
- cachedLookupAddress.set(address, name)
- return name
- }, [])
-
- const getAvatarUrl = useCallback(async (name: string) => {
- if (cachedGetAvatarUrl.has(name)) {
- return cachedGetAvatarUrl.get(name)
- }
- const avatarUrl = (await provider?.getAvatar(name)) || undefined
- cachedGetAvatarUrl.set(name, avatarUrl)
- return avatarUrl
- }, [])
-
- // Note, this triggers a re-render on acccount change and on diconnect.
- const disconnect = useCallback(() => {
- Object.keys(localStorage).forEach((key) => {
- if (key.startsWith('xmtp')) {
- localStorage.removeItem(key)
- }
- })
- reset()
- router.push('/')
- }, [router, web3Modal])
+ const setProvider = useAppStore((state) => state.setProvider)
+ const { disconnect } = useDisconnect()
const handleAccountsChanged = useCallback(() => {
disconnect()
}, [disconnect])
const connect = useCallback(async () => {
- if (!web3Modal) {
- throw new Error('web3Modal not initialized')
- }
- try {
- const instance = await web3Modal.connect()
- if (!instance) {
- return
+ const isDemoEnv = isAppEnvDemo()
+ if (!isDemoEnv) {
+ if (!web3Modal) {
+ throw new Error('web3Modal not initialized')
+ }
+ try {
+ const instance = await web3Modal.connect()
+ if (!instance) {
+ return
+ }
+ instance.on('accountsChanged', handleAccountsChanged)
+ const newProvider = new ethers.providers.Web3Provider(instance, 'any')
+ const newSigner = newProvider.getSigner()
+ setProvider(newProvider)
+ setSigner(newSigner)
+ setAddress(await newSigner.getAddress())
+ return newSigner
+ } catch (e) {
+ // TODO: better error handling/surfacing here.
+ // Note that web3Modal.connect throws an error when the user closes the
+ // modal, as "User closed modal"
+ console.log('error', e)
}
- instance.on('accountsChanged', handleAccountsChanged)
- provider = new ethers.providers.Web3Provider(instance, 'any')
- const newSigner = provider.getSigner()
- setSigner(newSigner)
- setAddress(await newSigner.getAddress())
- return newSigner
- } catch (e) {
- // TODO: better error handling/surfacing here.
- // Note that web3Modal.connect throws an error when the user closes the
- // modal, as "User closed modal"
- console.log('error', e)
}
}, [handleAccountsChanged, web3Modal])
@@ -161,8 +104,9 @@ const useWalletProvider = () => {
return
}
instance.on('accountsChanged', handleAccountsChanged)
- provider = new ethers.providers.Web3Provider(instance, 'any')
- const newSigner = provider.getSigner()
+ const newProvider = new ethers.providers.Web3Provider(instance, 'any')
+ const newSigner = newProvider.getSigner()
+ setProvider(newProvider)
setSigner(newSigner)
setAddress(await newSigner.getAddress())
} catch (e) {
@@ -173,11 +117,7 @@ const useWalletProvider = () => {
}, [web3Modal])
return {
- resolveName,
- lookupAddress,
- getAvatarUrl,
connect,
- disconnect,
}
}
diff --git a/hooks/useWalletProviderDemo.tsx b/hooks/useWalletProviderDemo.tsx
new file mode 100644
index 00000000..5ea7bff6
--- /dev/null
+++ b/hooks/useWalletProviderDemo.tsx
@@ -0,0 +1,45 @@
+import { useCallback, useEffect } from 'react'
+import { ethers, Wallet } from 'ethers'
+import { useAppStore } from '../store/app'
+import { isAppEnvDemo } from '../helpers'
+
+function getInfuraId() {
+ return process.env.NEXT_PUBLIC_INFURA_ID || 'c518355f44bd45709cf0d42567d7bdb4'
+}
+
+const useWalletProviderDemo = () => {
+ const address = useAppStore((state) => state.address)
+ const provider = useAppStore((state) => state.provider)
+ const setProvider = useAppStore((state) => state.setProvider)
+ const setAddress = useAppStore((state) => state.setAddress)
+ const setSigner = useAppStore((state) => state.setSigner)
+
+ const connect = useCallback(async () => {
+ const isDemoEnv = isAppEnvDemo()
+ if (isDemoEnv) {
+ try {
+ if (!address) {
+ const newSigner = Wallet.createRandom()
+ setSigner(newSigner)
+ setAddress(newSigner.address)
+ return newSigner
+ }
+ } catch (e) {
+ console.log('error', e)
+ }
+ }
+ }, [address])
+
+ useEffect(() => {
+ if (!provider) {
+ setProvider(new ethers.providers.InfuraProvider('mainnet', getInfuraId()))
+ connect()
+ }
+ }, [provider])
+
+ return {
+ connect,
+ }
+}
+
+export default useWalletProviderDemo
diff --git a/hooks/useWindowSize.ts b/hooks/useWindowSize.ts
new file mode 100644
index 00000000..f411aeda
--- /dev/null
+++ b/hooks/useWindowSize.ts
@@ -0,0 +1,17 @@
+import { useLayoutEffect, useState } from 'react'
+
+const useWindowSize = () => {
+ const [size, setSize] = useState<[number, number]>([0, 0])
+
+ useLayoutEffect(() => {
+ function updateSize() {
+ setSize([window.innerWidth, window.innerHeight])
+ }
+ window.addEventListener('resize', updateSize)
+ updateSize()
+ return () => window.removeEventListener('resize', updateSize)
+ }, [])
+ return size
+}
+
+export default useWindowSize
diff --git a/pages/dm/[...recipientWalletAddr].tsx b/pages/dm/[...recipientWalletAddr].tsx
index b1d87406..3b5f0a5a 100644
--- a/pages/dm/[...recipientWalletAddr].tsx
+++ b/pages/dm/[...recipientWalletAddr].tsx
@@ -2,11 +2,11 @@ import React, { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import { useRouter } from 'next/router'
import { Conversation } from '../../components/Conversation'
-import useWalletProvider from '../../hooks/useWalletProvider'
+import useEnsHooks from '../../hooks/useEnsHooks'
const ConversationPage: NextPage = () => {
const router = useRouter()
- const { resolveName } = useWalletProvider()
+ const { resolveName } = useEnsHooks()
const [recipientWalletAddr, setRecipientWalletAddr] = useState()
useEffect(() => {
diff --git a/store/app.tsx b/store/app.tsx
index 015c5631..485cf239 100644
--- a/store/app.tsx
+++ b/store/app.tsx
@@ -1,13 +1,20 @@
import { Client, Conversation, DecodedMessage } from '@xmtp/xmtp-js'
-import { Signer } from 'ethers'
+import { ethers, Signer } from 'ethers'
import create from 'zustand'
import getUniqueMessages from '../helpers/getUniqueMessages'
+type ProviderType =
+ | ethers.providers.Web3Provider
+ | ethers.providers.InfuraProvider
+ | undefined
+
interface AppState {
signer: Signer | undefined
setSigner: (signer: Signer | undefined) => void
address: string | undefined
setAddress: (address: string | undefined) => void
+ provider: ProviderType
+ setProvider: (provider: ProviderType) => void
client: Client | undefined | null
setClient: (client: Client | undefined | null) => void
conversations: Map
@@ -27,6 +34,8 @@ export const useAppStore = create((set) => ({
setSigner: (signer: Signer | undefined) => set(() => ({ signer })),
address: undefined,
setAddress: (address: string | undefined) => set(() => ({ address })),
+ provider: undefined,
+ setProvider: (provider: ProviderType) => set(() => ({ provider })),
client: undefined,
setClient: (client: Client | undefined | null) => set(() => ({ client })),
conversations: new Map(),