From 2b8bbee92b231573c9b249c131e22513f001d323 Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 10:24:21 +0800 Subject: [PATCH 01/10] docs: update --- app/content/eip-6963.md | 5 ++--- app/content/eips.md | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/content/eip-6963.md b/app/content/eip-6963.md index 0b61b36..ac794b6 100644 --- a/app/content/eip-6963.md +++ b/app/content/eip-6963.md @@ -6,7 +6,6 @@ head: content: '' --- -# EIP-6963 - -Decentralize injected wallet providers +# EIP-6963 Multi Injected Provider Discovery +Using `window.dispatchEvent` to emit a EIP-1193 provider instead of `window.ethereum`. diff --git a/app/content/eips.md b/app/content/eips.md index 5ae10e6..099bcf5 100644 --- a/app/content/eips.md +++ b/app/content/eips.md @@ -8,7 +8,8 @@ head: # EIPs -Collect the EIPs related to Vue Dapp +Collect the EIPs related to DApp development, especially in frontend. + ## EIP-1193 - [EIP-1193: Ethereum Provider JavaScript API](https://eips.ethereum.org/EIPS/eip-1193){:target="_blank"} From 9a52a9509efceacb623b7b9184be102d43db643c Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 10:37:59 +0800 Subject: [PATCH 02/10] chore: update Contract no event text --- app/components/content/Contract.vue | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/components/content/Contract.vue b/app/components/content/Contract.vue index eaca87d..730f231 100644 --- a/app/components/content/Contract.vue +++ b/app/components/content/Contract.vue @@ -172,7 +172,16 @@ const isReady = computed(() => isConnected.value && !showSwitchButton.value)

Events

-
No event found.
+
+
No event found in the last 40,000 blocks.
+ + More on explorer + +
From de434d15b0d6e6f6f6f2ac9cae955f8ed83dbadd Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 10:40:13 +0800 Subject: [PATCH 03/10] chore: add two new sidebar item --- app/core/sidebar.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/core/sidebar.ts b/app/core/sidebar.ts index 6998c22..9354fc1 100644 --- a/app/core/sidebar.ts +++ b/app/core/sidebar.ts @@ -60,6 +60,28 @@ export const sidebarMenu = [ ), key: '/examples/multicall', }, + { + label: () => + h( + NuxtLink, + { + to: '/examples/meta-transactions', + }, + { default: () => 'Meta Transactions' }, + ), + key: '/examples/meta-transactions', + }, + { + label: () => + h( + NuxtLink, + { + to: '/examples/siwe', + }, + { default: () => 'Sign-In with Ethereum' }, + ), + key: '/examples/siwe', + }, ], }, { From d804bbffa0aa994280828429a40d6866c4ce373b Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 10:42:38 +0800 Subject: [PATCH 04/10] docs: update --- app/content/overview.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/content/overview.md b/app/content/overview.md index 55ca028..5d94e1f 100644 --- a/app/content/overview.md +++ b/app/content/overview.md @@ -13,7 +13,7 @@ head: ## Wallet & isConnected -These two states will be frequently used in dapp development. +These two states will be frequently used in development. The `isConnected` is a [computed](https://vuejs.org/api/reactivity-core.html#computed){:target="_blank"}, and the `wallet` is a [readonly](https://vuejs.org/api/reactivity-core.html#readonly) [reactive](https://vuejs.org/api/reactivity-core.html#reactive){:target="_blank"}. @@ -34,7 +34,7 @@ if(isConnected.value) { } ``` -The wallet comprises 8 properties, each of which can be obtained from the `useVueDapp` as a [computed](https://vuejs.org/api/reactivity-core.html#ref){:target="_blank"}. +The wallet comprises 8 properties, each of which can be obtained from the `useVueDapp` as a [computed](https://vuejs.org/api/reactivity-core.html#computed){:target="_blank"}. ```ts const { error, chainId } = useVueDapp() From 9101d5e68654dc2f47006985b5bf615048055b62 Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 10:49:25 +0800 Subject: [PATCH 05/10] fix: markRaw provider in useEthers.setWallet --- app/stores/useEthers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/stores/useEthers.ts b/app/stores/useEthers.ts index 26e3228..b41ebf5 100644 --- a/app/stores/useEthers.ts +++ b/app/stores/useEthers.ts @@ -7,7 +7,7 @@ export const useEthers = defineStore('useEthers', () => { const signer = ref(null) async function setWallet(p: EIP1193Provider) { - provider.value = new ethers.BrowserProvider(p) + provider.value = markRaw(new ethers.BrowserProvider(p)) signer.value = markRaw(await provider.value.getSigner()) } From 9e70c58efe914cf7d287404144266ec37c37d4be Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 10:53:00 +0800 Subject: [PATCH 06/10] fix: remove error message in ConnectButton --- app/components/button/ConnectButton.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/components/button/ConnectButton.vue b/app/components/button/ConnectButton.vue index 384cec8..63a314d 100644 --- a/app/components/button/ConnectButton.vue +++ b/app/components/button/ConnectButton.vue @@ -2,7 +2,7 @@ import { useVueDapp, shortenAddress } from '@vue-dapp/core' import { useVueDappModal } from '@vue-dapp/modal' -const { address, status, error, disconnect } = useVueDapp() +const { address, status, disconnect } = useVueDapp() function onClickConnectButton() { if (status.value === 'connected') { @@ -27,7 +27,6 @@ function onClickConnectButton() {
Disconnect
{{ shortenAddress(address) }}
-
{{ error }}
From f2bfe5dbfdf960dfc6acb1e0743ff6a99a16b431 Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 14:40:12 +0800 Subject: [PATCH 07/10] feat(core): eip6963 service add removeListener --- app/components/content/Eip6963.client.vue | 50 +++++++++++++++++++++++ app/content/eip-6963.md | 3 ++ packages/core/src/services/eip6963.ts | 28 +++++++++---- packages/core/src/store.ts | 10 ++++- 4 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 app/components/content/Eip6963.client.vue diff --git a/app/components/content/Eip6963.client.vue b/app/components/content/Eip6963.client.vue new file mode 100644 index 0000000..9bd829c --- /dev/null +++ b/app/components/content/Eip6963.client.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/app/content/eip-6963.md b/app/content/eip-6963.md index ac794b6..d4cb901 100644 --- a/app/content/eip-6963.md +++ b/app/content/eip-6963.md @@ -9,3 +9,6 @@ head: # EIP-6963 Multi Injected Provider Discovery Using `window.dispatchEvent` to emit a EIP-1193 provider instead of `window.ethereum`. + +::Eip6963 +:: diff --git a/packages/core/src/services/eip6963.ts b/packages/core/src/services/eip6963.ts index 4141f59..0e347ab 100644 --- a/packages/core/src/services/eip6963.ts +++ b/packages/core/src/services/eip6963.ts @@ -3,35 +3,47 @@ import { EIP6963AnnounceProviderEvent, EIP6963ProviderDetail, RDNS } from '../ty import { useStore } from '../store' export function useEIP6963(pinia?: any) { - const walletStore = useStore(pinia) + const store = useStore(pinia) + + let listener: (event: EIP6963AnnounceProviderEvent) => void function subscribe() { - window.addEventListener('eip6963:announceProvider', (event: EIP6963AnnounceProviderEvent) => { + const listener = (event: EIP6963AnnounceProviderEvent) => { // console.log('eip6963:announceProvider -> detail', event.detail) _addProviderDetail(event.detail) - }) + } + + window.addEventListener('eip6963:announceProvider', listener) window.dispatchEvent(new CustomEvent('eip6963:requestProvider')) + + return () => window.removeEventListener('eip6963:announceProvider', listener) + } + + function removeListener() { + if (!listener) return + window.removeEventListener('eip6963:announceProvider', listener) } function _addProviderDetail(detail: EIP6963ProviderDetail) { - if (walletStore.providerDetails.some(({ info }) => info.uuid === detail.info.uuid)) return - walletStore.providerDetails.push(detail) // why detail cannot be markRaw()? it will lead to "TypeError: Cannot define property __v_skip, object is not extensible" + if (store.providerDetails.some(({ info }) => info.uuid === detail.info.uuid)) return + store.providerDetails.push(detail) // why detail cannot be markRaw()? it will lead to "TypeError: Cannot define property __v_skip, object is not extensible" } function getProviderDetail(rdns: string | RDNS): EIP6963ProviderDetail | undefined { - return walletStore.providerDetails.find(({ info }) => info.rdns === rdns) + return store.providerDetails.find(({ info }) => info.rdns === rdns) } return { // state - providerDetails: computed(() => walletStore.providerDetails), + providerDetails: computed(() => store.providerDetails), // getters hasInjectedProvider: computed(() => typeof window !== 'undefined' && !!window.ethereum), - isProviderAnnounced: computed(() => walletStore.providerDetails.length > 0), + isProviderAnnounced: computed(() => store.providerDetails.length > 0), subscribe, getProviderDetail, + removeListener, } } diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index 56b99ae..6ee491e 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -1,7 +1,13 @@ import { reactive, ref, toRefs } from 'vue' import { defineStore } from 'pinia' -import { Connector, EIP6963ProviderDetail, OnDisconnectCallback } from './types' -import { Wallet, OnAccountsChangedCallback, OnChainChangedCallback } from './types' +import { + Wallet, + Connector, + EIP6963ProviderDetail, + OnDisconnectCallback, + OnAccountsChangedCallback, + OnChainChangedCallback, +} from './types' /** * Pinia Setup Store From f17dab3f1b27174a6845fd495533d19979691c4e Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 14:44:04 +0800 Subject: [PATCH 08/10] feat(modal): add hideConnectingModal prop --- packages/modal/src/VueDappModal.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/modal/src/VueDappModal.vue b/packages/modal/src/VueDappModal.vue index cd961d6..dadaa58 100644 --- a/packages/modal/src/VueDappModal.vue +++ b/packages/modal/src/VueDappModal.vue @@ -11,12 +11,14 @@ const props = withDefaults( dark?: boolean autoConnect?: boolean autoConnectBrowserWalletIfSolo?: boolean + hideConnectingModal?: boolean }>(), { modelValue: undefined, dark: false, autoConnect: false, autoConnectBrowserWalletIfSolo: false, + hideConnectingModal: false, }, ) @@ -154,7 +156,7 @@ const vClickOutside = { - +

Connecting...

From aa4eaa2e81037a90dc57896ecfe973001205edc9 Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 15:08:27 +0800 Subject: [PATCH 09/10] chore: add lightThemeOverrides --- app/app.vue | 19 ++++++++++++++++--- app/components/content/Eip6963.client.vue | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/app.vue b/app/app.vue index 78592f9..c26fc93 100644 --- a/app/app.vue +++ b/app/app.vue @@ -5,7 +5,14 @@ import '@vue-dapp/modal/dist/style.css' // import { WalletConnectConnector } from '@vue-dapp/walletconnect' // import { CoinbaseWalletConnector } from '@vue-dapp/coinbase' -import { lightTheme } from 'naive-ui' +import { darkTheme, lightTheme, type GlobalThemeOverrides } from 'naive-ui' + +const lightThemeOverrides: GlobalThemeOverrides = { + common: { + primaryColor: darkTheme.common.primaryColor, + successColor: darkTheme.common.successColor, + }, +} useHead({ titleTemplate: title => { @@ -49,16 +56,22 @@ onWalletUpdated(async (wallet: ConnWallet) => { onDisconnected(() => { resetWallet() }) + +const hideConnectingModal = computed(() => { + const route = useRoute() + if (route.path === '/eip-6963') return true + return false +}) diff --git a/app/components/content/Eip6963.client.vue b/app/components/content/Eip6963.client.vue index 9bd829c..38e7d6b 100644 --- a/app/components/content/Eip6963.client.vue +++ b/app/components/content/Eip6963.client.vue @@ -29,7 +29,7 @@ async function onClickWallet(connName: ConnectorName, rdns?: RDNS | string) { :key="detail.info.uuid" @click="onClickWallet('BrowserWallet', detail.info.rdns)" size="medium" - :loading="status === 'connecting'" + :disabled="status === 'connecting' || wallet.providerInfo?.rdns === detail.info.rdns" >
{{ detail.info.name }}
From ce4b42ec9d0ce06870440e460b040466cafdef82 Mon Sep 17 00:00:00 2001 From: Johnson Chen Date: Mon, 22 Apr 2024 17:32:30 +0800 Subject: [PATCH 10/10] fix: autoConnect attempt is based on the rdns of the last connected wallet --- app/components/content/Eip6963.client.vue | 31 ++++++-- app/content/eip-6963.md | 84 +++++++++++++++++++++- packages/core/src/services/connect.ts | 65 ++++++++--------- packages/core/src/services/localStorage.ts | 41 +++++++++++ packages/modal/src/VueDappModal.vue | 3 +- 5 files changed, 182 insertions(+), 42 deletions(-) create mode 100644 packages/core/src/services/localStorage.ts diff --git a/app/components/content/Eip6963.client.vue b/app/components/content/Eip6963.client.vue index 38e7d6b..c16f2ec 100644 --- a/app/components/content/Eip6963.client.vue +++ b/app/components/content/Eip6963.client.vue @@ -3,7 +3,6 @@ import { useVueDapp, shortenAddress, type ConnectorName, RDNS } from '@vue-dapp/ import { useVueDappModal } from '@vue-dapp/modal' const { providerDetails, wallet, address, status, connectTo, disconnect, error, isConnected } = useVueDapp() -const { close } = useVueDappModal() const providerList = computed(() => { return providerDetails.value.slice().sort((a, b) => { @@ -16,7 +15,7 @@ const providerList = computed(() => { }) async function onClickWallet(connName: ConnectorName, rdns?: RDNS | string) { - close() + useVueDappModal().close() await connectTo(connName, { rdns }) } @@ -33,13 +32,35 @@ async function onClickWallet(connName: ConnectorName, rdns?: RDNS | string) { >
{{ detail.info.name }}
+

No provider was found in this browser.

-
+
Connecting...
-
{{ wallet.providerInfo?.name }} is connected.
-
{{ shortenAddress(address || '') }}
+
+
uuid: {{ wallet.providerInfo?.uuid }}
+
name: {{ wallet.providerInfo?.name }}
+ +
+ icon: + +
+
rdns: {{ wallet.providerInfo?.rdns }}
+ +
+ Disconnect +
{{ shortenAddress(address || '') }}
+
+

{{ error }}

diff --git a/app/content/eip-6963.md b/app/content/eip-6963.md index d4cb901..5b675d1 100644 --- a/app/content/eip-6963.md +++ b/app/content/eip-6963.md @@ -8,7 +8,89 @@ head: # EIP-6963 Multi Injected Provider Discovery -Using `window.dispatchEvent` to emit a EIP-1193 provider instead of `window.ethereum`. +Using window events to announce [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193){:target="_blank"} providers instead of `window.ethereum`. + + +- [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963){:target="_blank"} + + +## Provider details + ::Eip6963 :: + + +## Source code + +```ts [setup script] +import { useVueDapp, shortenAddress, type ConnectorName, RDNS } from '@vue-dapp/core' +import { useVueDappModal } from '@vue-dapp/modal' + +const { providerDetails, wallet, address, status, connectTo, disconnect, error, isConnected } = useVueDapp() + +const providerList = computed(() => { + return providerDetails.value.slice().sort((a, b) => { + if (a.info.rdns === RDNS.rabby) return -1 + if (b.info.rdns === RDNS.rabby) return 1 + if (a.info.rdns === RDNS.metamask) return -1 + if (b.info.rdns === RDNS.metamask) return 1 + return 0 + }) +}) + +async function onClickWallet(connName: ConnectorName, rdns?: RDNS | string) { + useVueDappModal().close() + await connectTo(connName, { rdns }) +} +``` + +```vue [template] + +``` diff --git a/packages/core/src/services/connect.ts b/packages/core/src/services/connect.ts index 214c6f6..914333f 100644 --- a/packages/core/src/services/connect.ts +++ b/packages/core/src/services/connect.ts @@ -4,6 +4,11 @@ import { ConnectOptions, ConnectorName, RDNS } from '../types' import { AutoConnectError, ConnectError, ConnectorNotFoundError } from '../errors' import { normalizeChainId } from '../utils' import { BrowserWalletConnector } from '../browserWalletConnector' +import { + getLastConnectedBrowserWallet, + removeLastConnectedBrowserWallet, + setLastConnectedBrowserWallet, +} from './localStorage' export function useConnect(pinia?: any) { const walletStore = useStore(pinia) @@ -41,15 +46,15 @@ export function useConnect(pinia?: any) { walletStore.wallet.provider = provider walletStore.wallet.address = account walletStore.wallet.chainId = normalizeChainId(chainId) + + walletStore.wallet.status = 'connected' + if (info?.rdns) setLastConnectedBrowserWallet(info.rdns) } catch (err: any) { await disconnect() // will resetWallet() walletStore.wallet.error = err.message throw new ConnectError(err) } - walletStore.wallet.status = 'connected' - localStorage.removeItem('VUE_DAPP__disconnected') - // ============================= listen EIP-1193 events ============================= // Events: disconnect, chainChanged, and accountsChanged @@ -82,46 +87,38 @@ export function useConnect(pinia?: any) { } async function disconnect() { - // console.log('useConnect.disconnect') - if (walletStore.wallet.connector) { - try { + try { + if (walletStore.wallet.connector) { await walletStore.wallet.connector.disconnect() - } catch (err: any) { - resetWallet() - throw new Error(err) } + } catch (err: any) { + walletStore.error = `Failed to disconnect, wallet reset: ${err.message}` + throw new Error(`Failed to disconnect, wallet reset: ${err.message}`) + } finally { + resetWallet() + removeLastConnectedBrowserWallet() } - resetWallet() - - localStorage.setItem('VUE_DAPP__disconnected', 'true') } - async function autoConnect(rdns: RDNS | string) { - if (localStorage.getItem('VUE_DAPP__disconnected')) { - // console.warn('No auto-connect: has disconnected') - return - } + async function autoConnect(rdns?: RDNS | string) { + const lastRdns = getLastConnectedBrowserWallet() + if (!lastRdns) return - const browserWalletConn = walletStore.connectors.find(conn => conn.name === 'BrowserWallet') - - if (browserWalletConn) { - try { - const isConnected = await BrowserWalletConnector.checkConnection(rdns) - if (isConnected) { - await connectTo(browserWalletConn.name, { - rdns, - }) - } else { - // console.warn('No auto-connect to MetaMask: not connected') - } - } catch (err: any) { - throw new AutoConnectError(err) + rdns = rdns || lastRdns + + const bwConnector = walletStore.connectors.find(conn => conn.name === 'BrowserWallet') + + if (!bwConnector || !rdns) return + + try { + const isConnected = await BrowserWalletConnector.checkConnection(rdns) + if (isConnected) { + await connectTo(bwConnector.name, { rdns }) } - } else { - // console.warn('No auto-connect to MetaMask: connector not found') + } catch (err: any) { + throw new AutoConnectError(err) } } - return { wallet: readonly(walletStore.wallet), diff --git a/packages/core/src/services/localStorage.ts b/packages/core/src/services/localStorage.ts new file mode 100644 index 0000000..f799ed1 --- /dev/null +++ b/packages/core/src/services/localStorage.ts @@ -0,0 +1,41 @@ +import { RDNS } from '../types' + +const LS_KEY = 'VUE_DAPP' + +export function setLastConnectedBrowserWallet(rdns: RDNS | string) { + window.localStorage.setItem( + LS_KEY, + JSON.stringify({ + lastConnectedWalletRdns: rdns, + }), + ) +} + +export function removeLastConnectedBrowserWallet() { + const data = window.localStorage.getItem(LS_KEY) + if (!data) return + try { + const { lastConnectedWalletRdns } = JSON.parse(data) + if (lastConnectedWalletRdns) { + window.localStorage.setItem( + LS_KEY, + JSON.stringify({ + lastConnectedWalletRdns: undefined, + }), + ) + } + } catch { + return + } +} + +export function getLastConnectedBrowserWallet(): RDNS | undefined { + const data = window.localStorage.getItem(LS_KEY) + if (!data) return + try { + const { lastConnectedWalletRdns } = JSON.parse(data) + return lastConnectedWalletRdns + } catch { + return + } +} diff --git a/packages/modal/src/VueDappModal.vue b/packages/modal/src/VueDappModal.vue index dadaa58..8e61cdd 100644 --- a/packages/modal/src/VueDappModal.vue +++ b/packages/modal/src/VueDappModal.vue @@ -46,8 +46,7 @@ async function handleAutoConnect() { if (props.autoConnect) { try { isAutoConnecting.value = true - // throw new Error('test autoConnect error') - await autoConnect(RDNS.metamask) + await autoConnect() } catch (err: any) { emit('autoConnectError', err) } finally {