Skip to content

Commit

Permalink
Merge pull request #657 from clrfund/feat/walletconnect-v2
Browse files Browse the repository at this point in the history
Upgrade WalletConnect to v2
  • Loading branch information
yuetloo authored Jul 18, 2023
2 parents 6716b25 + 9802cf4 commit 055b83c
Show file tree
Hide file tree
Showing 18 changed files with 735 additions and 446 deletions.
1 change: 1 addition & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export VITE_IPFS_PINNING_JWT=x
export VITE_IPFS_PINNING_URL=x
export VITE_RECIPIENT_REGISTRY_TYPE=simple
export VITE_USER_REGISTRY_TYPE=simple
export VITE_WALLET_CONNECT_PROJECT_ID=1

yarn test:format && yarn test:web && yarn test:lint-i18n
3 changes: 3 additions & 0 deletions vue-app/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ VITE_EXPORT_BATCH_SIZE=
# This is only used for netlify function, sponsor.js, to avoid getting the 'fetch not found' error
AWS_LAMBDA_JS_RUNTIME=nodejs18.x

# walletconnect project id
VITE_WALLET_CONNECT_PROJECT_ID=walletconnect_project_id

# This date will be used in the verify landing page. If not set or bad date format, a generic message will be displayed
# format: yyyy-MM-dd e.g. 2023-06-26
VITE_NEXT_ROUND_START_DATE=
Expand Down
7 changes: 4 additions & 3 deletions vue-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
]
},
"dependencies": {
"@clrfund/maci-utils": "^0.0.1",
"@kleros/gtcr-encoder": "1.4.0",
"@openfonts/inter_all": "^1.0.2",
"@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0",
"@vueuse/core": "^9.6.0",
"@walletconnect/web3-provider": "^1.8.0",
"@walletconnect/ethereum-provider": "^2.9.0",
"@walletconnect/modal": "^2.6.0",
"crypto-js": "^4.1.1",
"ethereum-blockies-base64": "^1.0.2",
"ethers": "^5.7.2",
Expand All @@ -48,8 +50,7 @@
"vue-final-modal": "^4.3.0",
"vue-i18n": "9",
"vue-meta": "3.0.0-alpha.8",
"vue-router": "4",
"@clrfund/maci-utils": "^0.0.1"
"vue-router": "4"
},
"devDependencies": {
"@graphql-codegen/cli": "^3.3.0",
Expand Down
8 changes: 7 additions & 1 deletion vue-app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { useAppStore, useUserStore, useRecipientStore, useWalletStore } from '@/
import { storeToRefs } from 'pinia'
import { useRoute } from 'vue-router'
import { useMeta } from 'vue-meta'
import type { WalletUser } from '@/stores'
const route = useRoute()
const appStore = useAppStore()
Expand Down Expand Up @@ -183,9 +184,14 @@ onBeforeUnmount(() => {
watch(walletUser, async () => {
try {
if (walletUser.value) {
const user: WalletUser = {
chainId: walletUser.value.chainId,
walletAddress: walletUser.value.walletAddress,
web3Provider: walletUser.value.web3Provider,
}
// make sure factory is loaded
await appStore.loadFactoryInfo()
userStore.loginUser(walletUser.value)
userStore.loginUser(user)
await userStore.loadUserInfo()
await userStore.loadBrightID()
} else {
Expand Down
4 changes: 2 additions & 2 deletions vue-app/src/api/contributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function getContributionAmount(
}
const data = await sdk.GetContributionsAmount({
fundingRoundAddress: fundingRoundAddress.toLowerCase(),
contributorAddress,
contributorAddress: contributorAddress.toLowerCase(),
})

if (!data.contributions.length) {
Expand Down Expand Up @@ -117,7 +117,7 @@ export async function hasContributorVoted(fundingRoundAddress: string, contribut
}
const data = await sdk.GetContributorVotes({
fundingRoundAddress: fundingRoundAddress.toLowerCase(),
contributorAddress,
contributorAddress: contributorAddress.toLowerCase(),
})
return !!data.fundingRound?.contributors?.[0]?.votes?.length
}
Expand Down
8 changes: 8 additions & 0 deletions vue-app/src/api/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ if (!rpcUrl) {
throw new Error('Please provide ethereum rpc url for connecting to blockchain')
}

export const walletConnectProjectId = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID
if (!walletConnectProjectId) {
throw new Error('Please provide wallet connect project id')
}

export const mainnetProvider = new ethers.providers.StaticJsonRpcProvider(import.meta.env.VITE_ETHEREUM_MAINNET_API_URL)
export const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl)
export const chainId = Number(import.meta.env.VITE_ETHEREUM_API_CHAINID)
Expand Down Expand Up @@ -100,3 +105,6 @@ const deadline = import.meta.env.VITE_RECIPIENT_JOIN_DEADLINE
? DateTime.fromFormat(import.meta.env.VITE_RECIPIENT_JOIN_DEADLINE, 'yyyy-MM-dd', { zone: 'utc' })
: null
export const recipientJoinDeadlineConfig = deadline?.isValid ? deadline : null

// make sure walletconnect qrcode modal is not blocked by the Wallet Modal
export const walletConnectZIndex = import.meta.env.VITE_WALLET_CONNECT_Z_INDEX || '1500'
19 changes: 6 additions & 13 deletions vue-app/src/components/Cart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ import { useModal } from 'vue-final-modal'
import { useRoute } from 'vue-router'
import { getAssetsUrl } from '@/utils/url'
import { useI18n } from 'vue-i18n'
import ErrorModal from './ErrorModal.vue'
import { showError } from '@/utils/modal'
const { t } = useI18n()
Expand Down Expand Up @@ -307,18 +307,11 @@ function removeAll(): void {
appStore.toggleEditSelection(true)
}
function showError(errorMessage: string) {
const { open, close } = useModal({
component: ErrorModal,
attrs: {
errorMessage,
onClose() {
close()
},
},
})
open()
}
onMounted(() => {
if (currentUser.value && !currentUser.value.encryptionKey) {
promptSignagure()
}
})
function promptConnection(): void {
const { open, close } = useModal({
Expand Down
2 changes: 1 addition & 1 deletion vue-app/src/components/FundsNeededWarning.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
})
}}
</links>
<links v-else :to="chain.bridge">
<links v-else-if="chain.bridge" :to="chain.bridge">
{{
$t('fundsNeededWarning.link2', {
singleTokenNeeded: singleTokenNeeded,
Expand Down
1 change: 0 additions & 1 deletion vue-app/src/stores/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { getAssetsUrl } from '@/utils/url'
import { getTokenLogo } from '@/utils/tokens'
import { assert, ASSERT_MISSING_ROUND, ASSERT_MISSING_SIGNATURE, ASSERT_NOT_CONNECTED_WALLET } from '@/utils/assert'
import { Keypair } from '@clrfund/maci-utils'
import { DateTime } from 'luxon'

export type AppState = {
isAppReady: boolean
Expand Down
19 changes: 9 additions & 10 deletions vue-app/src/stores/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useAppStore } from '@/stores'
import type { WalletUser } from '@/stores'
import { getContributionAmount, hasContributorVoted } from '@/api/contributions'
import type { BigNumber, Signer } from 'ethers'
import { ensLookup } from '@/utils/accounts'
import { ensLookup, isValidSignature } from '@/utils/accounts'
import { UserRegistryType, userRegistryType } from '@/api/core'
import { getBrightId, type BrightId } from '@/api/bright-id'
import { assert, ASSERT_NOT_CONNECTED_WALLET } from '@/utils/assert'
Expand Down Expand Up @@ -44,6 +44,12 @@ export const useUserStore = defineStore('user', {
assert(this.currentUser, ASSERT_NOT_CONNECTED_WALLET)
if (!this.currentUser.encryptionKey) {
const signature = await this.signer.signMessage(LOGIN_MESSAGE)

if (!isValidSignature(signature)) {
// gnosis safe does not return signature in hex string
// show the result as error
throw new Error(signature)
}
this.currentUser.encryptionKey = sha256(signature)
}
},
Expand Down Expand Up @@ -71,15 +77,8 @@ export const useUserStore = defineStore('user', {

let contribution = appStore.contribution
if (!contribution || contribution.isZero()) {
contribution = await getContributionAmount(
appStore.currentRound.fundingRoundAddress,
this.currentUser.walletAddress,
)

const hasVoted = await hasContributorVoted(
appStore.currentRound.fundingRoundAddress,
this.currentUser.walletAddress,
)
contribution = await getContributionAmount(appStore.currentRound.fundingRoundAddress, walletAddress)
const hasVoted = await hasContributorVoted(appStore.currentRound.fundingRoundAddress, walletAddress)

appStore.contribution = contribution
appStore.hasVoted = hasVoted
Expand Down
34 changes: 12 additions & 22 deletions vue-app/src/stores/wallet/connectors/WalletConnectConnector.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
import WalletConnectProvider from '@walletconnect/web3-provider'
import { chainId as rpcChainId, rpcUrl } from '@/api/core'
import { EthereumProvider } from '@walletconnect/ethereum-provider'
import { chainId as rpcChainId, walletConnectProjectId, walletConnectZIndex } from '@/api/core'

export default {
// TODO: add better return type
connect: async (): Promise<any | undefined> => {
const provider = new WalletConnectProvider({
infuraId: import.meta.env.VITE_INFURA_ID,
rpc: {
[rpcChainId]: rpcUrl,
const provider = await EthereumProvider.init({
projectId: walletConnectProjectId,
chains: [rpcChainId],
showQrModal: true,
qrModalOptions: {
themeMode: 'light',
themeVariables: { '--wcm-z-index': walletConnectZIndex },
enableExplorer: true,
},
})

let accounts, chainId
try {
accounts = await provider.enable()
chainId = await provider.request({ method: 'eth_chainId' })
} catch (err) {
if (err.code === 4001) {
// EIP-1193 userRejectedRequest error
// If this happens, the user rejected the connection request.
/* eslint-disable-next-line no-console */
console.log('Please connect to WalletConnect.')
} else {
/* eslint-disable-next-line no-console */
console.error(err)
return
}
}
const accounts = await provider.enable()
const chainId = await provider.request({ method: 'eth_chainId' })

return {
provider,
Expand Down
5 changes: 2 additions & 3 deletions vue-app/src/stores/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import MetamaskConnector from './connectors/MetamaskConnector'
import WalletConnectConnector from './connectors/WalletConnectConnector'
import { lsGet, lsSet, lsRemove } from '@/utils/localStorage'
import { CHAIN_INFO } from '@/utils/chains'
import WalletConnectProvider from '@walletconnect/web3-provider'
import type { Provider } from './types'
import { EthereumProvider } from '@walletconnect/ethereum-provider'
import { chainId } from '@/api/core'
import { providers } from 'ethers'
import { LOGIN_MESSAGE } from '@/api/user'

const CONNECTED_PROVIDER = 'connected-provider'
const DISCONNECT_EVENT = 'disconnect'
Expand Down Expand Up @@ -80,7 +79,7 @@ export const useWalletStore = defineStore('wallet', {
// Check if user is using the supported chain id
const supportedChainId = chainId
if (conn.chainId !== supportedChainId) {
if (conn.provider instanceof WalletConnectProvider) {
if (conn.provider instanceof EthereumProvider) {
// Close walletconnect session
await conn.provider.disconnect()
}
Expand Down
10 changes: 8 additions & 2 deletions vue-app/src/utils/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ethers } from 'ethers'
import { utils } from 'ethers'
import { mainnetProvider } from '@/api/core'
import { isAddress } from '@ethersproject/address'

const SIGNATURE_LENGTH = 65

export function isSameAddress(address1: string, address2: string): boolean {
// check for empty address to avoid getAddress() from throwing
return !!address1 && !!address2 && ethers.utils.getAddress(address1) === ethers.utils.getAddress(address2)
return !!address1 && !!address2 && utils.getAddress(address1) === utils.getAddress(address2)
}

// Looks up possible ENS for given 0x address
Expand Down Expand Up @@ -36,3 +38,7 @@ export function renderAddressOrHash(address: string, digitsToShow?: number): str
}
return address
}

export function isValidSignature(signature: string): boolean {
return utils.isHexString(signature, SIGNATURE_LENGTH)
}
15 changes: 15 additions & 0 deletions vue-app/src/utils/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useModal } from 'vue-final-modal'
import ErrorModal from '@/components/ErrorModal.vue'

export function showError(errorMessage: string) {
const { open, close } = useModal({
component: ErrorModal,
attrs: {
errorMessage,
onClose() {
close()
},
},
})
open()
}
17 changes: 11 additions & 6 deletions vue-app/src/views/RecipientRegistry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ import { useUserStore, useRecipientStore } from '@/stores'
import { storeToRefs } from 'pinia'
import type { TransactionResponse } from '@ethersproject/abstract-provider'
import { useModal } from 'vue-final-modal'
import { showError } from '@/utils/modal'
const recipientStore = useRecipientStore()
const userStore = useUserStore()
Expand Down Expand Up @@ -221,12 +222,16 @@ async function waitForTransactionAndLoad(transaction: Promise<TransactionRespons
// time the subgraph to index the new state from the tx. Perhaps we could
// avoid querying the subgraph and query directly the chain to get the
// request state.
await new Promise(resolve => {
setTimeout(async () => {
await loadRequests()
resolve(true)
}, 2000)
})
try {
await new Promise(resolve => {
setTimeout(async () => {
await loadRequests()
resolve(true)
}, 2000)
})
} catch (err) {
showError((err as Error).message)
}
},
transaction,
onClose() {
Expand Down
8 changes: 5 additions & 3 deletions vue-app/src/views/VerifyLanding.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { useAppStore, useUserStore } from '@/stores'
import { useRouter } from 'vue-router'
import { useModal } from 'vue-final-modal'
import SignatureModal from '@/components/SignatureModal.vue'
import { ASSERT_NOT_CONNECTED_WALLET, assert } from '@/utils/assert'
import { formatDateWithOptions } from '@/utils/dates'
import type { DateTime } from 'luxon'
Expand Down Expand Up @@ -133,10 +134,11 @@ async function promptSignagure() {
}
function handleBrightIdButtonClicked() {
if (currentUser.value && !currentUser.value.encryptionKey) {
promptSignagure()
} else {
assert(currentUser.value, ASSERT_NOT_CONNECTED_WALLET)
if (currentUser.value.encryptionKey) {
gotoVerify()
} else {
promptSignagure()
}
}
Expand Down
4 changes: 0 additions & 4 deletions vue-app/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,13 @@ export default defineConfig({
build: {
target: 'esnext', // to enable Big integer literals
chunkSizeWarningLimit: 6300,
commonjsOptions: {
transformMixedEsModules: true, // to enable @walletconnect/web3-provider which has some code in CommonJS
},
rollupOptions: {
output: {
manualChunks: {
'google-spreadsheet': ['google-spreadsheet'],
'@kleros/gtcr-encoder': ['@kleros/gtcr-encoder'],
'@vuelidate': ['@vuelidate/core', '@vuelidate/validators'],
maci: ['@clrfund/maci-utils'],
'@walletconnect/web3-provider': ['@walletconnect/web3-provider'],
qrcode: ['qrcode'],
},
},
Expand Down
Loading

0 comments on commit 055b83c

Please sign in to comment.