去中心化應用程序(DApps)的主要功能之一是連接錢包的能力,這反過來又允許用戶與DApp上的交易互動。它抽象了一些功能,如切換網(wǎng)絡,提供簽名者,以及其他為用戶提供一種認證形式的功能。連接錢包也作為一個網(wǎng)關,允許用戶通過DApp在區(qū)塊鏈上進行和讀取操作,使用他們的錢包地址作為授權身份。
WalletConnect是一個免費的開源協(xié)議,使我們的DApp與多個錢包連接成為可能,包括MetaMask、Trust Wallet、Rainbow和其他錢包。該協(xié)議通過在DApp和錢包之間建立連接來抽象這個過程,使它們在整個會話中保持同步。
在這篇文章中,我們將使用WalletConnect將我們的錢包應用與我們的DApp連接起來,在前端使用Vue.js。需要注意的是,WalletConnect可以用在任何兼容WalletConnect的DApp、鏈和錢包(托管和非托管)上。
你可以在這里找到本教程的源代碼,以及我們將建立的應用程序的演示。

開始使用Vue.js應用程序
首先,讓我們使用Vue CLI來啟動這個項目。如果你的系統(tǒng)中已經(jīng)安裝了Vue CLI,你可以直接創(chuàng)建Vue項目。
你可以用這個命令全局安裝它。
現(xiàn)在我們可以使用Vue CLI來創(chuàng)建我們的項目。用這個命令創(chuàng)建一個新項目。
vue create vue-wallet-connect
你將需要挑選一個預設。選擇手動選擇功能,然后選擇如下所示的選項。

在項目被創(chuàng)建后,導航到新的項目文件夾。
我們將在我們的Vue應用程序中使用Ethers.js,在連接我們的錢包時直接與區(qū)塊鏈互動。
在這里,我們將WalletConnect庫安裝到你的項目中。
npm install --save web3 @walletconnect/web3-provider
接下來,為了在Vue 3中直接使用WalletConnect庫,我們需要安裝node-polyfill-webpack-plugin。
npm i node-polyfill-webpack-plugin
我們安裝它是因為我們的項目使用webpack v5,其中polyfill Node核心模塊被刪除。所以,我們安裝它是為了在項目中訪問這些模塊。
現(xiàn)在,打開vue.config.js文件,用這段代碼替換它。
const { defineConfig } = require("@vue/cli-service");
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
plugins: [new NodePolyfillPlugin()],
optimization: {
splitChunks: {
chunks: "all",
},
},
},
});
一旦完成,你就可以啟動服務器了。
構建用戶界面
讓我們進入組件文件夾,創(chuàng)建一個名為StatusContainer.vue的新文件。這個組件包含我們的主頁面。
它有我們的歡迎詞,幫助我們連接的連接錢包按鈕,以及斷開我們與錢包連接的斷開按鈕。最后,當我們成功連接到一個錢包時,顯示Connected按鈕。
<template>
<div class="hello">
<h1>Welcome to Your Vue.js Dapp</h1>
<div
<button class="button">Connected</button>
<button class="disconnect__button">Disconnect</button>
</div>
<button class="button"> Connect Wallet</button>
</div>
</template>
<script>
export default {
name: 'StatusContainer'
}
</script>
一旦完成,打開App.vue文件,像這樣導入StatusContainer組件。
<template>
<status-container/>
</template>
<script>
import StatusContainer from './components/StatusContainer.vue'
export default {
name: 'App',
components: {
StatusContainer
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@100&display=swap');
#app {
font-family: 'Sora', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.button {
background-color: #1c82ff;
border: none;
color: #ffffff;
font-family: "Sora";
border-radius: 3rem;
padding: 2rem 3rem;
font-weight: 600;
font-size: 2rem;
margin: 1rem 1rem 1rem auto;
width: 40%;
}
.disconnect__button {
background-color: red;
border: none;
color: #ffffff;
font-family: "Sora";
border-radius: 3rem;
padding: 1rem 1.3rem;
font-weight: 600;
font-size: 1rem;
margin: 8rem 1rem 1rem auto;
width: 20%;
}
</style>
在我們的樣式標簽中,我們現(xiàn)在為我們先前創(chuàng)建的按鈕添加樣式:.button和.disconnect__button。另外,我們從Google Fonts導入Sora自定義字體,并將其作為我們的字體家族。
實例化WalletConnect
我們將需要一個RPC提供者來實例化我們的WalletConnect庫。在這個例子中,我們將使用Infura。打開Infura,創(chuàng)建一個新的項目,并獲取項目ID。

現(xiàn)在,在src文件夾下創(chuàng)建一個新的walletConnect文件夾:src/walletConnect。在這個文件夾中,讓我們創(chuàng)建一個provider.js文件。在這里,我們導入我們的WalletConnect庫,使用我們的Infura ID將其實例化,并導出它以便在其他文件中使用。
src/walletConnect/provider.js將看起來像這樣。
import WalletConnectProvider from "@walletconnect/web3-provider";
export const provider = new WalletConnectProvider({
infuraId: process.env.VUE_APP_INFURA_ID,
});
Infura的ID應該作為環(huán)境變量使用。所以在你的.env文件中添加以下內容。
VUE_APP_INFURA_ID={{INFURA__ID}}
使用可合成物添加功能
在創(chuàng)建我們的接口并成功實例化我們的庫之后,下一步是實現(xiàn)我們的功能。為了做到這一點,我們將使用Vue composables,因為它允許我們在應用中的任何組件中使用我們的狀態(tài)和動作,類似于我們在Pinea和Vuex中的情況。
創(chuàng)建一個可組合的
在src文件夾內,添加src/composables/connect。在connect文件夾內,讓我們創(chuàng)建一個index.js文件。
在這里,我們導入reactive和watch,我們將在這個文件中使用它們。讓我們創(chuàng)建一個叫做defaultState的狀態(tài)對象。
import { reactive, watch } from "vue";
const defaultState = {
address: "",
chainId: "",
status: false,
};
const state = defaultState
為了保持狀態(tài)的一致性,我們將狀態(tài)與本地存儲中的一個項目進行同步。讓我們把這個項目命名為 "userState",并把它分配給一個叫做STATE_NAME的變量。這樣做是為了避免在多個地方重復 "userState "時犯錯誤。
const STATE_NAME = "userState";
現(xiàn)在我們使用watch來更新我們的本地存儲,一旦我們的狀態(tài)有任何變化。
watch(
() state,
() {
localStorage.setItem(STATE_NAME, JSON.stringify(state));
},
{ deep: true }
);
接下來,我們創(chuàng)建一個getDefaultState函數(shù),檢查我們本地存儲中的STATE_NAME項是否存在,并將本地存儲項分配給狀態(tài)。如果我們的本地存儲項不存在,它就將默認狀態(tài)分配給state。
現(xiàn)在,我們可以刪除 const state = defaultState 并使用 reactive 來分配 const state = reactive(getDefaultState());。
const getDefaultState = () {
if (localStorage.getItem(STATE_NAME) !== null) {
return JSON.parse(localStorage.getItem(STATE_NAME));
}
return defaultState;
};
const state = reactive(getDefaultState());
最后,我們導出我們的狀態(tài)。我們還添加了一個if語句,檢查我們的本地存儲項是否不存在。如果不存在,它就創(chuàng)建這個項目并將狀態(tài)分配給本地存儲。
export default () => {
if (localStorage.getItem(STATE_NAME) === null) {
localStorage.setItem(STATE_NAME, JSON.stringify(state));
}
return {
state,
};
};
現(xiàn)在,我們的狀態(tài)總是與本地存儲同步,確保一致性。
我們來看看 src/composables/connect/index.js。
import { reactive, watch } from "vue";
const defaultState = {
address: "",
chainId: "",
status: false,
};
const STATE_NAME = "userState";
const getDefaultState = () {
if (localStorage.getItem(STATE_NAME) !== null) {
return JSON.parse(localStorage.getItem(STATE_NAME));
}
return defaultState;
};
const state = reactive(getDefaultState());
watch(
() state,
() {
localStorage.setItem(STATE_NAME, JSON.stringify(state));
},
{ deep: true }
);
export default () => {
if (localStorage.getItem(STATE_NAME) === null) {
localStorage.setItem(STATE_NAME, JSON.stringify(state));
}
return {
state,
};
};
創(chuàng)建動作
我們的動作由將在我們的應用程序中使用的函數(shù)組成。我們將創(chuàng)建三個函數(shù)。
- ? connectWalletConnect,用于觸發(fā)WalletConnect模式以連接錢包
- ? autoConnect,在DApp連接后處理WalletConnect會話的一致性,所以當DApp連接后,你刷新頁面時,用戶的會話仍然是活動的。
- ? disconnectWallet,斷開DApp與錢包的連接并結束用戶的會話。
讓我們直接跳到代碼中
ConnectWalletConnect
還是在我們的connect文件夾(src/composables/connect)中,創(chuàng)建connectWalletConnect文件。首先,我們導入我們的索引文件,來自以太坊的提供者,以及我們之前在 src/walletConnect/provider.js 文件中創(chuàng)建的提供者。
import { providers } from "ethers";
import connect from "./index";
import { provider } from "../../walletConnect/provider";
const connectWalletConnect = async () => {
try {
const { state } = connect();
// Enable session (triggers QR Code modal)
await provider.enable();
const web3Provider = new providers.Web3Provider(provider);
const signer = await web3Provider.getSigner();
const address = await signer.getAddress();
state.status = true;
state.address = address;
state.chainId = await provider.request({ method: "eth_chainId" });
provider.on("disconnect", (code, reason) => {
console.log(code, reason);
console.log("disconnected");
state.status = false;
state.address = "";
localStorage.removeItem("userState");
});
provider.on("accountsChanged", (accounts) => {
if (accounts.length > 0) {
state.address = accounts[0];
}
});
provider.on("chainChanged", (chainId) => {
state.chainId = chainId
});
} catch (error) {
console.log(error);
}
};
export default connectWalletConnect;
接下來,我們有一個try-catch語句。在我們的嘗試語句中,我們從connect()中獲得我們的狀態(tài),并彈出我們的QR模式進行連接。一旦連接,我們就將我們的地址和chainId分配給狀態(tài)屬性,并使我們的state.status讀作true。
然后,我們用提供者觀察三個事件:斷開連接、賬戶變更和鏈式連接。
- ? 一旦用戶直接從他們的錢包斷開連接,就會觸發(fā)斷開連接。
- ? accountsChanged是在用戶切換他們錢包中的賬戶時觸發(fā)的。如果賬戶數(shù)組的長度大于0,我們將state.address分配給數(shù)組中的第一個地址(accounts[0]),也就是當前地址。
- ? 如果用戶切換了他們的鏈/網(wǎng)絡,就會觸發(fā) chainChainged。例如,如果他們將他們的鏈從Ethereum mainnet切換到rinkeby testnet,我們的應用程序將state.chainId從1變?yōu)?。
然后,我們的catch語句只是將任何錯誤記錄到控制臺。
回到connect文件夾中的index.js文件,導入connectWalletConnect動作。在這里,我們創(chuàng)建一個動作對象,并將其與我們的狀態(tài)一起導出。
import { reactive, watch } from "vue";
import connectWalletConnect from "./connectWalletConnect";
const STATE_NAME = "userState";
const defaultState = {
address: "",
chainId: "",
status: false,
};
const getDefaultState = () {
if (localStorage.getItem(STATE_NAME) !== null) {
return JSON.parse(localStorage.getItem(STATE_NAME));
}
return defaultState;
};
const state = reactive(getDefaultState());
const actions = {
connectWalletConnect,
};
watch(
() state,
() {
localStorage.setItem(STATE_NAME, JSON.stringify(state));
},
{ deep: true }
);
export default () => {
if (localStorage.getItem(STATE_NAME) === null) {
localStorage.setItem(STATE_NAME, JSON.stringify(state));
}
return {
state,
...actions,
};
};
autoConnect
讓我們繼續(xù)看autoConnect.js,以及我們的動作。與 connectWalletConnect 類似,創(chuàng)建一個 autoConnect.js 文件。我們導入索引文件并對其進行解構,以獲得我們的狀態(tài),并使用connect()進行connectWalletConnect。
import connect from "./index";
const autoConnect = () {
const { state, connectWalletConnect } = connect();
if (state.status) {
if (localStorage.getItem("walletconnect") == null) {
console.log("disconnected");
console.log("disconnected");
state.status = false;
state.address = "";
localStorage.removeItem("userState");
}
if (localStorage.getItem("walletconnect")) {
(async () => {
console.log("start");
connectWalletConnect();
})();
}
}
};
export default autoConnect;
你應該知道的一件事是,一旦WalletConnect成功連接到一個DApp,所有關于該錢包的信息(包括地址和鏈ID)都在本地存儲中,在一個名為walletconnect的項目下。一旦會話被斷開,它就會被自動刪除。
autoConnect檢查我們的state.status是否為true。如果是,我們就檢查本地存儲中是否有一個walletConnect項目。如果它不在本地存儲中,我們就刪除我們的狀態(tài)中的所有現(xiàn)有數(shù)據(jù)和本地存儲中的userState項。
然而,如果walletconnect存在于你的本地存儲中,我們有一個異步函數(shù),通過啟動connectWalletConnect();為我們的DApp "重新激活 "現(xiàn)有會話。因此,如果我們刷新頁面,連接仍然是活躍的,并且可以監(jiān)聽我們的提供者事件。
斷開錢包
讓我們來看看最后一個動作:disconnectWallet。這個動作允許我們從DApp本身結束會話。
首先,我們導入我們的提供者和狀態(tài)。然后,我們使用provider.disconnect();來斷開會話,之后我們將狀態(tài)重置為默認值,并刪除本地存儲中的 "userState "項目。
import { provider } from "../../walletConnect/provider";
import connect from "./index";
const disconnectWallet = async () => {
const { state } = connect();
await provider.disconnect();
state.status = false;
state.address = "";
localStorage.removeItem("userState");
}
export default disconnectWallet;
現(xiàn)在我們可以回到我們的src/composables/connect/index.js中,像這樣更新動作對象。
const actions = {
connectWalletConnect,
autoConnect,
disconnectWallet
};
在我們的組件中實現(xiàn)邏輯
讓我們打開我們的StatusContainer組件,將我們的可組合文件中的邏輯連接到接口上。像往常一樣,導入你的可組合文件并對其進行解構,以獲得動作(連接和斷開)和我們的狀態(tài)。
<script>
import connect from '../composables/connect/index';
export default {
name: 'StatusContainer',
setup: () {
const { connectWalletConnect, disconnectWallet, state } = connect();
const connectUserWallet = async () => {
await connectWalletConnect();
};
const disconnectUser = async() => {
await disconnectWallet()
}
return {
connectUserWallet,
disconnectUser,
state
}
}
}
</script>
然后我們返回函數(shù)(disconnectUser,connectUserWallet)和狀態(tài),以便在模板中使用。
<template>
<div class="hello">
<h1>Welcome to Your Vue.js Dapp</h1>
<div v-if="state.status">
<button @click="connectUserWallet" class="button">Connected</button>
<h3>Address: {{state.address}}</h3>
<h3>ChainId: {{state.chainId}}</h3>
<button @click="disconnectUser" class="disconnect__button">Disconnect</button>
</div>
<button v-else @click="connectUserWallet" class="button"> Connect Wallet</button>
</div>
</template>
首先,我們使用v-if來有條件地顯示東西,使用state.status。如果我們連接了并且state.status為真,我們就會顯示Connected按鈕、用戶地址和chainId。同時,我們會顯示一個斷開連接的按鈕,觸發(fā)我們的disconnectUser函數(shù)。

如果用戶沒有連接,且state.status為false,我們只顯示連接錢包按鈕,點擊后會觸發(fā)我們的connectUserWallet函數(shù)。

添加自動連接
讓我們進入App.vue組件,將自動連接邏輯添加到該組件中。與我們之前所做的類似,我們導入我們的composable,并對其進行解構以獲得autoConnect動作。使用Vue的onMounted,我們將啟動autoConnect()函數(shù)。如前所述,這使我們能夠監(jiān)聽來自錢包的實時事件,即使我們刷新頁面。
<script>
import StatusContainer from './components/StatusContainer.vue'
import connect from './composables/connect/index';
import {onMounted} from "vue";
export default {
name: 'App',
components: {
StatusContainer
},
setup: () {
const { autoConnect} = connect();
onMounted(async () => {
await autoConnect()
})
}
}
</script>
結論
如果你一路走到這里,那就恭喜你了!
在這篇文章中,我們介紹了在你的Vue DApps中實現(xiàn)WalletConnect的逐步細節(jié)。從用正確的配置設置我們的項目和構建我們的界面,到編寫必要的邏輯以確保我們的應用程序始終與錢包保持同步。
原文鏈接:https://blog.logrocket.com/integrating-walletconnect-vue-js-dapps/