From ef4b2d158dc8edc64f5927b817d92e47685559ab Mon Sep 17 00:00:00 2001 From: Vojtech Simetka Date: Tue, 31 Jan 2023 10:34:40 +0700 Subject: [PATCH 1/3] feat: add network check --- packages/ui/.env | 1 + packages/ui/src/lib/components/modal.svelte | 27 +++++++++ packages/ui/src/lib/constants.ts | 2 + packages/ui/src/lib/services/blockchain.ts | 59 +++++++++++++++++++ .../ui/src/lib/services/{index.ts => zk.ts} | 17 +----- packages/ui/src/lib/stores/post.ts | 2 +- packages/ui/src/routes/+page.svelte | 3 + packages/ui/src/routes/post/new/+page.svelte | 18 +++--- packages/ui/src/routes/profile/+page.svelte | 5 +- 9 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 packages/ui/src/lib/components/modal.svelte create mode 100644 packages/ui/src/lib/services/blockchain.ts rename packages/ui/src/lib/services/{index.ts => zk.ts} (82%) diff --git a/packages/ui/.env b/packages/ui/.env index b968c62d..40ffd983 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -1,3 +1,4 @@ PUBLIC_PROVIDER=https://goerli.infura.io/v3/6d4ffa9d4df447ebb73468f4efcb8e8e PUBLIC_GROUP_ID=38219357914 PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=0x7A5fe8899b9e189483Db0545c71D6cad92df93F0 +PUBLIC_TARGET_CHAIN_ID=0x5 \ No newline at end of file diff --git a/packages/ui/src/lib/components/modal.svelte b/packages/ui/src/lib/components/modal.svelte new file mode 100644 index 00000000..ce74b65c --- /dev/null +++ b/packages/ui/src/lib/components/modal.svelte @@ -0,0 +1,27 @@ + + +
+
+ + diff --git a/packages/ui/src/lib/constants.ts b/packages/ui/src/lib/constants.ts index d08536a1..fa888f5b 100644 --- a/packages/ui/src/lib/constants.ts +++ b/packages/ui/src/lib/constants.ts @@ -2,8 +2,10 @@ import { PUBLIC_PROVIDER, PUBLIC_GROUP_ID, PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS, + PUBLIC_TARGET_CHAIN_ID, } from '$env/static/public' export const GLOBAL_ANONYMOUS_FEED_ADDRESS = PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS export const GROUP_ID = PUBLIC_GROUP_ID export const PROVIDER = PUBLIC_PROVIDER +export const TARGET_CHAIN_ID = PUBLIC_TARGET_CHAIN_ID diff --git a/packages/ui/src/lib/services/blockchain.ts b/packages/ui/src/lib/services/blockchain.ts new file mode 100644 index 00000000..aea55cb3 --- /dev/null +++ b/packages/ui/src/lib/services/blockchain.ts @@ -0,0 +1,59 @@ +import { providers, Signer } from 'ethers' +import { TARGET_CHAIN_ID } from '$lib/constants' +import { browser } from '$app/environment' + +type WindowWithEthereum = Window & typeof globalThis & { ethereum: providers.ExternalProvider } + +export async function connectWallet(network?: providers.Networkish): Promise { + const provider = new providers.Web3Provider(getEthereum(), network) + await provider.send('eth_requestAccounts', []) + return provider.getSigner() +} + +export function canConnectWallet() { + return Boolean((window as WindowWithEthereum)?.ethereum) +} + +export function hasWallet(w: Window & typeof globalThis = window): w is WindowWithEthereum { + return Boolean((w as WindowWithEthereum)?.ethereum) +} + +export function getEthereum(w: Window & typeof globalThis = window): providers.ExternalProvider { + if (hasWallet()) { + return (w as WindowWithEthereum)?.ethereum + } + + throw new Error('No web3 wallet found') +} + +export async function checkNetwork() { + if (!browser) return + + const ethereum = (window as WindowWithEthereum)?.ethereum + if (ethereum && ethereum.request) { + const currentChainId = await ethereum.request({ + method: 'eth_chainId', + }) + + if (Number.parseInt(currentChainId, 16) === Number.parseInt(TARGET_CHAIN_ID, 16)) { + return true + } + } + + return false +} + +export async function switchNetwork(targetChainId = TARGET_CHAIN_ID) { + if (!browser) return + + const ethereum = (window as WindowWithEthereum)?.ethereum + if (ethereum && ethereum.request) { + await ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: targetChainId }], + }) + + // refresh + window.location.reload() + } +} diff --git a/packages/ui/src/lib/services/index.ts b/packages/ui/src/lib/services/zk.ts similarity index 82% rename from packages/ui/src/lib/services/index.ts rename to packages/ui/src/lib/services/zk.ts index 130dced4..19dc83f7 100644 --- a/packages/ui/src/lib/services/index.ts +++ b/packages/ui/src/lib/services/zk.ts @@ -1,6 +1,6 @@ -import { ethers, providers, Signer, type BigNumberish, type ContractTransaction } from 'ethers' -import { Identity } from '@semaphore-protocol/identity' +import { ethers, Signer, type BigNumberish, type ContractTransaction } from 'ethers' import { Group, type Member } from '@semaphore-protocol/group' +import { Identity } from '@semaphore-protocol/identity' import { generateProof, packToSolidityProof, @@ -16,19 +16,6 @@ import { GLOBAL_ANONYMOUS_FEED_ADDRESS, GROUP_ID } from '$lib/constants' import { solidityKeccak256, type BytesLike, type Hexable } from 'ethers/lib/utils' import type { PromiseOrValue } from '$lib/assets/typechain/common' -type WindowWithEthereum = Window & - typeof globalThis & { ethereum: providers.ExternalProvider | providers.JsonRpcFetchFunc } - -export async function connectWallet(network?: providers.Networkish): Promise { - const provider = new providers.Web3Provider((window as WindowWithEthereum).ethereum, network) - await provider.send('eth_requestAccounts', []) - return provider.getSigner() -} - -export function canConnectWallet() { - return Boolean((window as WindowWithEthereum)?.ethereum) -} - export async function createIdentity(signer: Signer, secret: string) { const identitySeed = await signer.signMessage(secret) return new Identity(identitySeed) diff --git a/packages/ui/src/lib/stores/post.ts b/packages/ui/src/lib/stores/post.ts index cc009db7..d74162dc 100644 --- a/packages/ui/src/lib/stores/post.ts +++ b/packages/ui/src/lib/stores/post.ts @@ -1,6 +1,6 @@ import { writable, type Writable } from 'svelte/store' import { browser } from '$app/environment' -import { getGlobalAnonymousFeed } from '$lib/services' +import { getGlobalAnonymousFeed } from '$lib/services/zk' import { providers } from 'ethers' import { PROVIDER } from '$lib/constants' diff --git a/packages/ui/src/routes/+page.svelte b/packages/ui/src/routes/+page.svelte index e9e04fe8..9601a0af 100644 --- a/packages/ui/src/routes/+page.svelte +++ b/packages/ui/src/routes/+page.svelte @@ -11,6 +11,9 @@ import { profile } from '$lib/stores/profile' import { goto } from '$app/navigation' import Masonry from '$lib/masonry.svelte' + import { checkNetwork } from '$lib/services/blockchain' + + checkNetwork() let y: number diff --git a/packages/ui/src/routes/post/new/+page.svelte b/packages/ui/src/routes/post/new/+page.svelte index f34543a5..69e84b3c 100644 --- a/packages/ui/src/routes/post/new/+page.svelte +++ b/packages/ui/src/routes/post/new/+page.svelte @@ -12,7 +12,7 @@ getGlobalAnonymousFeed, getRandomExternalNullifier, validateProofOnChain, - } from '$lib/services/index' + } from '$lib/services/zk' import { posts } from '$lib/stores/post' let cls: string | undefined = undefined @@ -34,15 +34,17 @@ const externalNullifier = getRandomExternalNullifier() const proof = await generateGroupProof(group, identity, postText, externalNullifier) - const tx = await validateProofOnChain(globalAnonymousFeed, proof, postText, externalNullifier) - const res = await tx.wait() + console.log(proof) + // const tx = await validateProofOnChain(globalAnonymousFeed, proof, postText, externalNullifier) - posts.add({ - timestamp: Date.now(), - text: postText, - tx: res.transactionHash, - }) + // const res = await tx.wait() + + // posts.add({ + // timestamp: Date.now(), + // text: postText, + // tx: res.transactionHash, + // }) goto(ROUTES.HOME) } catch (error) { console.error(error) diff --git a/packages/ui/src/routes/profile/+page.svelte b/packages/ui/src/routes/profile/+page.svelte index 7fb72259..f999d90c 100644 --- a/packages/ui/src/routes/profile/+page.svelte +++ b/packages/ui/src/routes/profile/+page.svelte @@ -6,15 +6,14 @@ import Wallet from '$lib/components/icons/wallet.svelte' import WalletInfo from '$lib/components/wallet-info.svelte' import { formatAddress } from '$lib/utils' + import { connectWallet, canConnectWallet } from '$lib/services/blockchain' import { - connectWallet, - canConnectWallet, createIdentity, getGlobalAnonymousFeed, getContractGroup, joinGroupOffChain, joinGroupOnChain, - } from '$lib/services' + } from '$lib/services/zk' import { profile } from '$lib/stores/profile' let y: number From 9f1dff766809d6129d8beae7d533739e62d11e36 Mon Sep 17 00:00:00 2001 From: Vojtech Simetka Date: Tue, 31 Jan 2023 17:34:55 +0700 Subject: [PATCH 2/3] feat: add ethereum network check --- packages/ui/src/lib/components/modal.svelte | 14 +++++- packages/ui/src/lib/stores/errors.ts | 19 +++++++ packages/ui/src/lib/stores/post.ts | 10 +++- packages/ui/src/lib/types.ts | 4 ++ packages/ui/src/routes/+layout.svelte | 53 ++++++++++++++++++++ packages/ui/src/routes/+page.svelte | 3 -- packages/ui/src/routes/post/new/+page.svelte | 39 +++++++++----- 7 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 packages/ui/src/lib/stores/errors.ts diff --git a/packages/ui/src/lib/components/modal.svelte b/packages/ui/src/lib/components/modal.svelte index ce74b65c..99d72451 100644 --- a/packages/ui/src/lib/components/modal.svelte +++ b/packages/ui/src/lib/components/modal.svelte @@ -3,10 +3,17 @@ let cls: string | undefined = undefined export { cls as class } + export let message: string + export let action: (() => void) | undefined = undefined
-
diff --git a/packages/ui/src/lib/stores/errors.ts b/packages/ui/src/lib/stores/errors.ts new file mode 100644 index 00000000..2b872cff --- /dev/null +++ b/packages/ui/src/lib/stores/errors.ts @@ -0,0 +1,19 @@ +import { onDestroy } from 'svelte' +import { writable, type Writable } from 'svelte/store' + +export interface UserStore extends Writable { + add: (error: Error) => void +} + +function createErrorStore(): UserStore { + const store = writable([]) + + return { + ...store, + add: (error: Error) => { + store.update((errors) => [...errors, error]) + }, + } +} + +export const errors = createErrorStore() diff --git a/packages/ui/src/lib/stores/post.ts b/packages/ui/src/lib/stores/post.ts index d74162dc..ab746f22 100644 --- a/packages/ui/src/lib/stores/post.ts +++ b/packages/ui/src/lib/stores/post.ts @@ -3,6 +3,8 @@ import { browser } from '$app/environment' import { getGlobalAnonymousFeed } from '$lib/services/zk' import { providers } from 'ethers' import { PROVIDER } from '$lib/constants' +import { errors } from '$lib/stores/errors' +import type { ErrorWithCode } from '$lib/types' export interface Post { timestamp: number @@ -36,8 +38,12 @@ async function pullFeed() { localStorage.setItem('messages', JSON.stringify(messages)) } } - } catch (e) { - console.error(e) + } catch (error) { + if ((error as ErrorWithCode).code === 'NETWORK_ERROR') { + errors.add(new Error('Could not connect to the ethereum blockchain.')) + } else { + errors.add(error as Error) + } } } diff --git a/packages/ui/src/lib/types.ts b/packages/ui/src/lib/types.ts index 158b3443..ef151cd7 100644 --- a/packages/ui/src/lib/types.ts +++ b/packages/ui/src/lib/types.ts @@ -10,3 +10,7 @@ export interface IconProps { size?: number class?: string } + +export interface ErrorWithCode extends Error { + code?: string +} diff --git a/packages/ui/src/routes/+layout.svelte b/packages/ui/src/routes/+layout.svelte index 5475a434..fe7d2f60 100644 --- a/packages/ui/src/routes/+layout.svelte +++ b/packages/ui/src/routes/+layout.svelte @@ -3,6 +3,59 @@ import '@fontsource/source-sans-pro' import '@fontsource/source-serif-pro' import './styles.css' + + import Modal from '$lib/components/modal.svelte' + + import { errors } from '$lib/stores/errors' + import { onDestroy, onMount } from 'svelte' + import { getEthereum, switchNetwork } from '$lib/services/blockchain' + import type { providers } from 'ethers' + import { TARGET_CHAIN_ID } from '$lib/constants' + import type { ErrorWithCode } from '$lib/types' + + onMount(() => { + const ethereum = getEthereum() as providers.Provider + ethereum.on('chainChanged', (chainId: string) => { + if (chainId !== TARGET_CHAIN_ID) { + $errors = [ + new Error('You are connected to a wrong ethereum network. Please switch to Goerli'), + ] + switchNetwork(TARGET_CHAIN_ID).catch( + () => + ($errors = [ + new Error('Network change rejected, please change the network manually.'), + ...$errors, + ]), + ) + } else { + window.location.reload() + } + }) + + ethereum.on('disconnect', (error: ErrorWithCode) => { + errors.add( + new Error( + 'Disconnected from the ethereum network. Please check your internet connection and refresh the page', + ), + ) + console.error(error) + }) + }) + + onDestroy(() => { + console.log('hello') + }) +{#if $errors.length > 0} + { + const [err, ...rest] = $errors + $errors = rest + console.error(`Resolved error ${err.message}`) + }} + /> +{/if} + diff --git a/packages/ui/src/routes/+page.svelte b/packages/ui/src/routes/+page.svelte index 9601a0af..a50a9328 100644 --- a/packages/ui/src/routes/+page.svelte +++ b/packages/ui/src/routes/+page.svelte @@ -1,5 +1,4 @@ From bd9fb3217b1da52f9205872bac755045ad15db24 Mon Sep 17 00:00:00 2001 From: Vojtech Simetka Date: Tue, 31 Jan 2023 22:30:47 +0700 Subject: [PATCH 3/3] feat: handle chain change and disconnect --- packages/ui/src/lib/services/blockchain.ts | 14 +++---- packages/ui/src/lib/stores/errors.ts | 1 - packages/ui/src/routes/+layout.svelte | 44 +++++++++++++++------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/packages/ui/src/lib/services/blockchain.ts b/packages/ui/src/lib/services/blockchain.ts index aea55cb3..6415389a 100644 --- a/packages/ui/src/lib/services/blockchain.ts +++ b/packages/ui/src/lib/services/blockchain.ts @@ -11,22 +11,22 @@ export async function connectWallet(network?: providers.Networkish): Promise { diff --git a/packages/ui/src/routes/+layout.svelte b/packages/ui/src/routes/+layout.svelte index fe7d2f60..b0236cd3 100644 --- a/packages/ui/src/routes/+layout.svelte +++ b/packages/ui/src/routes/+layout.svelte @@ -8,42 +8,60 @@ import { errors } from '$lib/stores/errors' import { onDestroy, onMount } from 'svelte' - import { getEthereum, switchNetwork } from '$lib/services/blockchain' + import { checkNetwork, getEthereum, switchNetwork } from '$lib/services/blockchain' import type { providers } from 'ethers' import { TARGET_CHAIN_ID } from '$lib/constants' import type { ErrorWithCode } from '$lib/types' + import { browser } from '$app/environment' + + function switchToTargetNetwork() { + $errors = [new Error('You are connected to a wrong ethereum network. Please switch to Goerli')] + switchNetwork(TARGET_CHAIN_ID).catch( + () => + ($errors = [ + new Error('Network change rejected, please change the network manually.'), + ...$errors, + ]), + ) + } onMount(() => { + if (!browser) return const ethereum = getEthereum() as providers.Provider + + checkNetwork(TARGET_CHAIN_ID) + .then((networkMatches) => { + if (!networkMatches) switchToTargetNetwork() + }) + .catch(errors.add) + ethereum.on('chainChanged', (chainId: string) => { if (chainId !== TARGET_CHAIN_ID) { - $errors = [ - new Error('You are connected to a wrong ethereum network. Please switch to Goerli'), - ] - switchNetwork(TARGET_CHAIN_ID).catch( - () => - ($errors = [ - new Error('Network change rejected, please change the network manually.'), - ...$errors, - ]), - ) + switchToTargetNetwork() } else { window.location.reload() } }) ethereum.on('disconnect', (error: ErrorWithCode) => { + console.log(error) errors.add( new Error( 'Disconnected from the ethereum network. Please check your internet connection and refresh the page', ), ) - console.error(error) }) }) onDestroy(() => { - console.log('hello') + try { + const ethereum = getEthereum() as providers.Provider + + ethereum.off('chainChanged') + ethereum.off('disconnect') + } catch (error) { + console.error(error) + } })