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

Update/wallet connect #493

Merged
merged 6 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
10 changes: 8 additions & 2 deletions apps/mvp/src/components/ConnectWalletsFlow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const { getEthersLedgerAddresses } = useLedger()
const { getEthersTrezorAddresses } = useTrezor()
const { user } = useUser()
const { detectActiveNetwork, switchEthersNetwork } = useWallets()
const { connectWalletConnectV2 } = useWalletConnect()
const { connectWalletConnectV2, walletConnectSelectedAccount } = useWalletConnect()
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally curious, would it ever make sense to do something like:

const walletConnect = useWalletConnect()
...
watch(walletConnect.selectedAccount, () => {
...
}

composableName.someVariableOrMethodName might be nice for organization (and less redundancy).

Not suggesting this for the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cool pattern.

// const { installedWallets, detectInstalledWalletProviders } = useWallets()

// eslint-disable-next-line no-undef
Expand Down Expand Up @@ -226,6 +226,12 @@ onUnmounted(() => {
flowState.value = "select_provider"
}
})

watch(walletConnectSelectedAccount, () => {
if (selectedProvider.value === "WalletConnect") {
walletProviderAddresses.value = walletConnectSelectedAccount.value as CryptoAddress[]
}
})
</script>

<template>
Expand Down Expand Up @@ -374,7 +380,7 @@ onUnmounted(() => {
@click="
selectedProvider === 'Ledger' || selectedProvider === 'Trezor' ?
selectAddress(trimAndLowercaseAddress(act.address), pathIndex) :
selectAddress(trimAndLowercaseAddress(act.address), undefined)
selectAddress(act.address, undefined)
"
>
<div>
Expand Down
15 changes: 5 additions & 10 deletions apps/mvp/src/composables/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const { browserProvidersList, loginWithEthers } = useEthers()
const { loginWithLedger } = useLedger()
const { loginWithTrezor } = useTrezor()
const { setUser, user } = useUser()
const { disconnectWalletConnect, loginWithWalletConnectV2, initializeWalletConnect, uninitializeWalletConnect } = useWalletConnect()
const { disconnectWalletConnect, loginWithWalletConnectV2, initializeWalletConnect } = useWalletConnect()
const { detectActiveWalletAddress } = useWallets()

const initializedAuthComposable = ref(false)
Expand Down Expand Up @@ -132,9 +132,8 @@ export default function useAuth() {
if (addressExistsOnUser) return "Address already exists on this account"

// Check if it exists as a primary address of a different user
const {
data: { sameAddress, walletProvider },
} = await checkIfPrimaryUserExists(provider as ProviderString, address)
const { data: { sameAddress } } =
await checkIfPrimaryUserExists(provider as ProviderString, address)
// If yes, ask user if they want to add it as a secondary to this account or if they want to log in with that account
if (sameAddress) {
return "Address already exists as a primary address on another account"
Expand Down Expand Up @@ -174,6 +173,7 @@ export default function useAuth() {
const browserWallet = browserProvidersList.includes(provider as ProviderString)

if (provider === "WalletConnect") {
console.log("got to wallet connect login")
await loginWithProvider(loginCredentials as LoginCredentials)
await getUser()
return "Successfully logged in"
Expand Down Expand Up @@ -308,12 +308,7 @@ export default function useAuth() {
}
}
})

onUnmounted(() => {
initializedAuthComposable.value = false
uninitializeWalletConnect()
})


// TODO: Re-enable once we have a way to remove accounts in UI
// async function removeConnectedAccount() {
// if (!user?.value?.address) {
Expand Down
3 changes: 3 additions & 0 deletions apps/mvp/src/composables/staking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ export default function useStaking() {
async function deposit({ amount, walletProvider, type, pathIndex }: { amount: string, walletProvider: ProviderString, type: "default" | "eigen", pathIndex: number | undefined}) {
stakeWithdrawError.value = ""
try {
// This is currently handling the connectWalletConnectV2 if no current session
const activeNetwork = await detectActiveNetwork(walletProvider)
console.log("activeNetwork :>> ", activeNetwork)
if (activeNetwork !== 5) {
await switchEthersNetwork(walletProvider, "0x5")
return window.location.reload()
Expand Down Expand Up @@ -184,6 +186,7 @@ export default function useStaking() {
async function withdraw({ amount, walletProvider, type }: { amount: string, walletProvider: ProviderString, type: "default" | "eigen" }) {
try {
stakeWithdrawError.value = ""
// This is currently handling the connectWalletConnectV2 if no current session
const activeNetwork = await detectActiveNetwork(walletProvider)
if (activeNetwork !== 5) {
await switchEthersNetwork(walletProvider, "0x5")
Expand Down
146 changes: 77 additions & 69 deletions apps/mvp/src/composables/walletConnectV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let cleanupFunctions: Array<any> = [] // TODO: Potentially fix type here.
const accounts = ref<Array<string>>([])
const ethereumProvider = ref()
const pairings = ref([])
const session = ref()
const session = ref<SessionTypes.Struct>()
const web3Modal = ref()
const web3Provider = shallowRef()

Expand All @@ -28,6 +28,8 @@ const subscribedToProviderEvents = ref(false)
const componentIsMounted = ref(false)
const isInitializing = ref(false)

const walletConnectSelectedAccount = ref([] as Array<CryptoAddress>)

export default function useWalletConnectV2() {
ccali11 marked this conversation as resolved.
Show resolved Hide resolved
async function connectWalletConnectV2(chainId: "1" | "5" | "1337", pairing?: PairingTypes.Struct): Promise<CryptoAddress[]> {
try {
Expand All @@ -40,13 +42,19 @@ export default function useWalletConnectV2() {
}
const rpcUrl = customRpcs[chainId]
if (!rpcUrl) throw new Error(`RPC URL not found for chainId: ${chainId}`)

if (ethereumProvider.value.accounts.length > 0) throw new Error("Prompt user to simply change accounts to add it to their account")
await ethereumProvider.value.connect()
createEthersProvider()
const _accounts = await ethereumProvider.value.enable()
// const _accounts = await ethereumProvider.value.enable()
const _accounts = ethereumProvider.value.accounts
accounts.value = _accounts
session.value = session
session.value = ethereumProvider.value.session
web3Modal.value?.closeModal()
walletConnectSelectedAccount.value = [{
address: accounts.value[0],
balance: (await getEthersBalance(accounts.value[0])).toString()
}]

const connectedAddress = _accounts[0].toLowerCase().trim() as string
const connectedAddressBalance = (await getEthersBalance(connectedAddress)).toString()
return [{ address: connectedAddress as string, balance: connectedAddressBalance }]
Expand Down Expand Up @@ -99,93 +107,64 @@ export default function useWalletConnectV2() {
}

function getWalletConnectSignerV2() {
if (!web3Provider.value) {
throw new Error("Web3Provider is not initialized")
}
if (!web3Provider.value) throw new Error("Web3Provider is not initialized")
return web3Provider.value.getSigner()
}

async function initializeWalletConnect() {
if (componentIsMounted.value) return
if (import.meta.env.MODE === "development") console.log("initializing wallet connect")
componentIsMounted.value = true

// Check for persisted sessions & Subscribe to provider events
if (ethereumProvider.value && user.value) {
_subscribeToProviderEvents()
subscribedToProviderEvents.value = true
await _checkForPersistedSession()
checkedForPersistedSession.value = true
} else {
await createClient()
}
await createClient()
}

async function loginWithWalletConnectV2(loginCredentials: LoginCredentials) {
const { provider, address, currency } = loginCredentials
try {
const message = await createSiweMessage(address, "Sign in with Ethereum to the Casimir.")
const signedMessage = await signWalletConnectMessage(message)
await signInWithEthereum({
address,
currency: currency || "ETH",
provider,
message,
signedMessage
})
} catch (err) {
console.log("error in loginWithWalletConnect :>> ", err)
}
const message = await createSiweMessage(address, "Sign in with Ethereum to the Casimir.")
const signedMessage = await signWalletConnectMessage(message)
await signInWithEthereum({
address,
currency: currency || "ETH",
provider,
message,
signedMessage
})
}

async function signWalletConnectMessage(message: string) : Promise<string> {
try {
const signer = await web3Provider.value?.getSigner()
return await signer?.signMessage(message) as string
} catch (err) {
console.error("error in signWalletConnectMessage :>> ", err)
throw err
return ""
}
}

function uninitializeWalletConnect() {
cleanupFunctions.forEach((cleanup) => cleanup())
cleanupFunctions = [] // Reset the array
componentIsMounted.value = false
const signer = await web3Provider.value?.getSigner()
return await signer?.signMessage(message) as string
}

async function _checkForPersistedSession() {
if (ethereumProvider.value?.session) {
const _session = ethereumProvider.value?.session
console.log("RESTORED SESSION:", _session)
session.value = ethereumProvider.value?.session
console.log("RESTORED SESSION:", session.value)
if (!user.value) {
await disconnectWalletConnect()
return
} else {
if (!ethereumProvider.value) {
throw new ReferenceError("EthereumProvider is not initialized.")
}
walletConnectSelectedAccount.value = [{
address: ethereumProvider.value.accounts[0],
balance: (await getEthersBalance(ethereumProvider.value.accounts[0])).toString()
}]
createEthersProvider()
}
await _onSessionConnected(_session)
return _session
}
}

async function _onSessionConnected(_session: SessionTypes.Struct) {
if (!ethereumProvider.value) {
throw new ReferenceError("EthereumProvider is not initialized.")
}
const allNamespaceAccounts = Object.values(_session.namespaces).map(namespace => namespace.accounts).flat()
const allNamespaceChains = Object.keys(_session.namespaces)

session.value = _session
accounts.value = allNamespaceAccounts.map(account => account.split(":")[2])
console.log("RESTORED", allNamespaceChains, allNamespaceAccounts)
createEthersProvider()
}

function _resetApp() {
console.log("resetting app")
cleanupFunctions.forEach((cleanup) => cleanup())
cleanupFunctions = [] // Reset the array
pairings.value = []
session.value = undefined
accounts.value = []
web3Provider.value = undefined
walletConnectSelectedAccount.value = []
checkedForPersistedSession.value = false
subscribedToProviderEvents.value = false
}

function _subscribeToProviderEvents() {
Expand All @@ -195,10 +174,37 @@ export default function useWalletConnectV2() {

// Event handler for display_uri
const handleDisplayUri = (uri: string) => {
console.log("DISPLAY URI EVENT", "QR Code Modal open")
console.log("QR Code Modal open")
web3Modal.value?.openModal({ uri })
}

// Event handler for accountsChanged
const handleAccountsChanged = async (walletConnectAccounts: Array<string>) => {
console.log("ACCOUNTS CHANGED EVENT", walletConnectAccounts)
if (!user.value && accounts.value.length > 0) return window.location.reload()

// Check which accounts are new
const userAccounts = user.value?.accounts.map((account) => account.address.toLowerCase().trim())
const newAccounts = walletConnectAccounts.filter((account) => !userAccounts?.includes(account.toLowerCase().trim()))

// Add new accounts to user
if (newAccounts.length > 0) {
// await addAccountToUser(newAccounts) // TODO: Enable this (review with @DemogorGod)
const newAccountsWithBalances = await Promise.all(newAccounts.map(async (account) => {
return {
address: account,
balance: (await getEthersBalance(account)).toString()
}
}))
walletConnectSelectedAccount.value = [...walletConnectSelectedAccount.value, ...newAccountsWithBalances]
}
}

// Event handler for chainChanged
const handleChainChanged = (chainId: number) => {
// console.log("CHAIN CHANGED EVENT", chainId)
}

// Event handler for session_ping
const handleSessionPing = ({ id, topic }: { id: number; topic: string }) => {
console.log("SESSION PING EVENT", "session_ping")
Expand All @@ -207,8 +213,7 @@ export default function useWalletConnectV2() {

// Event handler for session_event
const handleSessionEvent = ({ event, chainId }: { event: any; chainId: string }) => {
console.log("SESSION EVENT", "session_event")
console.log(event, chainId)
console.log("SESSION EVENT", event)
}

// Event handler for session_update
Expand All @@ -219,33 +224,36 @@ export default function useWalletConnectV2() {

// Event handler for session_delete
const handleSessionDelete = ({ id, topic }: { id: number; topic: string }) => {
console.log("DELETE SESSION EVENT", "session_deleted")
console.log(id, topic)
console.log("DELETE SESSION EVENT", "session_deleted", id, topic)
_resetApp()
}

// Attaching the event listeners
ethereumProvider.value.on("display_uri", handleDisplayUri)
ethereumProvider.value.on("accountsChanged", handleAccountsChanged)
ethereumProvider.value.on("chainChanged", handleChainChanged)
ethereumProvider.value.on("session_ping", handleSessionPing)
ethereumProvider.value.on("session_event", handleSessionEvent)
ethereumProvider.value.on("session_update", handleSessionUpdate)
ethereumProvider.value.on("session_delete", handleSessionDelete)

// Storing cleanup functions
cleanupFunctions.push(() => ethereumProvider.value.off("display_uri", handleDisplayUri))
cleanupFunctions.push(() => ethereumProvider.value.off("accountsChanged", handleAccountsChanged))
cleanupFunctions.push(() => ethereumProvider.value.off("chainChanged", handleChainChanged))
cleanupFunctions.push(() => ethereumProvider.value.off("session_ping", handleSessionPing))
cleanupFunctions.push(() => ethereumProvider.value.off("session_event", handleSessionEvent))
cleanupFunctions.push(() => ethereumProvider.value.off("session_update", handleSessionUpdate))
cleanupFunctions.push(() => ethereumProvider.value.off("session_delete", handleSessionDelete))
}

return {
walletConnectSelectedAccount,
web3Provider,
connectWalletConnectV2,
disconnectWalletConnect,
getWalletConnectSignerV2,
initializeWalletConnect,
loginWithWalletConnectV2,
uninitializeWalletConnect
}
}
5 changes: 4 additions & 1 deletion apps/mvp/src/composables/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useWalletConnectV2 from "@/composables/walletConnectV2"

const installedWallets = ref([] as ProviderString[])
const { browserProvidersList, detectActiveEthersWalletAddress, getBrowserProvider } = useEthers()
const { web3Provider } = useWalletConnectV2()
const { connectWalletConnectV2, web3Provider } = useWalletConnectV2()

export default function useWallets() {
async function detectActiveNetwork(providerString: ProviderString): Promise<number> {
Expand All @@ -15,6 +15,9 @@ export default function useWallets() {
const chainId = parseInt(await provider.request({ method: "eth_chainId" }), 16)
return chainId
} else if (providerString === "WalletConnect") {
if (!web3Provider.value) {
await connectWalletConnectV2("5")
}
return web3Provider.value.provider.chainId
} else if (providerString === "Ledger") {
// TODO: Determine if there is a way to implement with Ledger or if have to rely on selected network on device
Expand Down
Loading