跳至主要內容

如何透過 Solana 錢包適配器與 Scaffold 將使用者連接到您的 dApp

更新於
2025年11月26日

閱讀時間 19 分鐘

概覽

您是否已在 Solana 上進行開發,並準備將您的 dApp 推向網路?您將需要一種方式來將您的工具與用戶的錢包連接起來。雖然有幾種方法可以將您的 dApp 與用戶的錢包連接,但 Solana 已開發出幾款實用的工具,讓您能輕鬆上手:Solana 錢包適配器(Solana Wallet Adapter)和Solana dApp Scaffold

您將負責的工作內容

  • 了解什麼是 Solana 錢包適配器
  • 部署 Solana dApp Scaffold
  • 自訂您的環境
  • 建立一個簡單的 dApp 來查詢已連線的錢包 

您需要準備的物品

  • Node.js(版本 16.15 或更高)
  • 一個 Solana 錢包(例如Phantom
  • 關於 [Solana Web3] 及Solana SPL 代幣函式庫的使用經驗
  • 已具備 TypeScript 經驗,並已安裝ts-node
  • HTML/CSS 的基礎知識
  • 具備前端網頁開發經驗將有所助益,但並非跟上課程的必要條件。我們將使用 React 建立一個Next.js專案。Solana 社群也支援 Vue、Angular 和 Svelte。請造訪他們的社群頁面以獲取更多資訊。

什麼是錢包轉接器

你是否曾注意到,許多 Solana 的去中心化應用程式(dApp)在連接錢包時,其使用者介面(UI)看起來都很相似? 

Solana 錢包轉接器

這就是 Solana 錢包適配器(「SWA」),你會在許多地方看到它,因為它使用簡便、涵蓋了 Solana 上所有最常見的錢包,並且由 Solana 社群定期維護。 那麼,它究竟是什麼?SWA 是一套針對 Solana 應用程式的模組化 TypeScript 錢包適配器與元件,讓您能輕鬆將您的去中心化應用程式(dApp)與使用者所選擇的錢包連接起來(開箱即用即可支援十多種 Solana 錢包!)。

以下是考慮使用 SWA 的幾個理由: 

  1. 開源專案,由 Solana-labs 提供支援
  2. 支援多種錢包:讓您的使用者能使用自己偏好的錢包,同時您也不必為了支援新錢包而大費周章
  3. 內建可自訂的現成使用者介面 
  4. 包含錨點支援
  5. 內建主要功能:連線、斷線、自動連線
  6. 支援多種前端框架

來試試看吧!

設定您的執行環境

SWA 支援多種前端框架:

  • React
  • Material-UI, 
  • Ant Design
  • Angular Material UI
  • Vue(由社群支援)
  • Angular(由社群支援)
  • Svelte(社群支持版)

您可以依照此處的 npm說明,將這些套件新增至現有專案中。不過,在本範例中,我們將使用 Solana dApp Scaffold 建立一個新專案。dApp Scaffold 包含 SWA 以及幾個預先建置的元件,讓您能快速上手!

注意:若您要將此適配器新增至現有專案,請注意,截至本文撰寫時,當前的 SWA 尚不支援 React 18。請使用 React 17。

請在終端機中使用以下指令建立一個新的專案目錄:

mkdir my-solana-dapp
cd my-solana-dapp

複製 dApp 基礎架構: 

git clone https://github.com/solana-labs/dapp-scaffold.git . 

`.` 會將骨架複製到您的專案目錄中,而不會在該目錄內建立新的目錄。 

在終端機中輸入ls,以確認所有檔案都已正確複製。您的終端機畫面應類似如下:

已克隆 Checkpoint 儲存庫

安裝依賴項: 

npm 安裝
# 或
yarn 安裝

此外,請一併加入 SPL-token 函式庫,我們將在本次練習的後續步驟中使用它: 

npm i @solana/spl-token
# 或
yarn add @solana/spl-token

新增 Quicknode 自訂 RPC 端點

若要在 Solana 上進行開發,您需要一個 API 端點來連線至該網路。您可以使用公開節點(這些節點已整合至 Scaffold 中),也可以自行部署並管理基礎架構;不過,如果您希望將回應時間縮短 8 倍,不妨將繁重的工作交給我們處理。


了解為何超過 50% 的 Solana 專案選擇 Quicknode,並點此免費註冊。

我們將在 Solana Devnet 上啟動我們的節點,但您也可以根據自身需求啟動相應的節點。請複製 HTTP Provider 的連結:

新的 Solana 端點

接著,返回終端機,並在my-solana-dapp目錄中執行以下指令,建立一個.env檔案:

echo > .env

我們建議在建立正式環境網站時,使用 .env 檔案來保護您的金鑰。

在您選擇的程式碼編輯器中開啟您的專案,並導航至您剛建立的.env檔案。宣告兩個變數:REACT_APP_SOLANA_RPC_HOSTREACT_APP_NETWORK。 將您的 RPC URL 貼入 REACT_APP_SOLANA_RPC_HOST 變數中,並將 REACT_APP_NETWORK 設定為您將要使用的 Solana 叢集(devnet、mainnet-beta 或 testnet)。此網路應與您選取 RPC 端點時所選用的網路相同。您的檔案內容應類似如下: 

REACT_APP_SOLANA_RPC_HOST=https://example.solana-devnet.quiknode.pro/00000000000/
REACT_APP_NETWORK=devnet

我們還必須更新`next.config.js` 檔案,以便在 Next.js 應用程式中使用這些環境變數。請在專案根目錄中開啟該檔案,並將內容替換為以下內容: 

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}

module.exports = {
nextConfig,
env: {
REACT_APP_SOLANA_RPC_HOST: process.env.REACT_APP_SOLANA_RPC_HOST,
REACT_APP_NETWORK: process.env.REACT_APP_NETWORK
}
}

現在請開啟./src/contexts/ContextProvider.tsx,並將第 20 至 21 行內容替換,以更新 Scaffold 的端點:

    const 網路 = WalletAdapterNetwork.開發網;
const 端點 = useMemo(() => clusterApiUrl(網路), [網路]);

    const 網路 = process.env.REACT_APP_NETWORK 設定為 WalletAdapterNetwork;
const endpoint = process.env.REACT_APP_SOLANA_RPC_HOST;

一切就緒!讓我們上線我們的應用程式吧! 

npm 執行 dev
# 或
yarn dev

如果您已按照所有步驟操作至此,您應該可以前往 http://localhost:3000/ 並看到您的 Scaffold。做得很好!您應該會看到類似以下的內容: 

Solana Scaffold 登入畫面

歡迎隨意點擊瀏覽並探索這個開發框架。您可以「連接錢包」並申請空投(開發網與測試網)、簽署訊息,或是將 SOL 傳送至隨機錢包。 

Solana 錢包適配器與 Scaffold 入門指南

在開始製作我們的元件之前,讓我們先瀏覽一下工作區,以便更清楚地了解各項功能是如何運作的。我們不會過於深入探討 React 的運作原理,而是先介紹幾個與 Solana dApp 相關的關鍵要素。 

「Wallet Connect」按鈕

請查看 /my-solana-dapp/src/components/AppBar.tsx. 您應該會看到我們導入了 WalletMultiButton 來自 solana/錢包介面程式 - React UI. 這個按鈕, <WalletMultiButton className="btn btn-ghost mr-4" /> 這是使用者與我們的錢包適配器互動的方式。您還會注意到這裡使用了 setAutoConnect 此方法用於設定使用者切換開關。我們在此不會進行任何變更,但若您想設定或停用 autoConnect 您可以使用此表單。 

連線 上下文

接下來請查看/my-solana-dapp/src/contexts/ContextProvider.tsx。這個檔案就是我們稍早更新端點和網路設定的地方,也是「錢包上下文提供者」的所在位置,我們可以在這裡配置錢包適配器。 

  • 首先,請注意我們的`wallets`變數。您會發現其中列出了一系列常見的 Solana 錢包。試著使用`//`將其中一個或多個註解掉,或是移除`new SlopeWalletAdapter()`上的註解(位於 `imports` 區段的第 4 至 11 行,以及 `declarations` 區段的第 22 至 35 行)。刷新您的網站並連接您的錢包。您應該會發現可用的錢包清單已經發生變化。 實際上,`wallets` 變數會透過 `Wallet ProviderComponent` 傳遞給 `Wallet Adapter`,用以設定我們的 dApp 允許哪些錢包。
  • autoconnect決定了用戶的錢包在頁面載入時如何與網站互動。正如我們先前所討論的,我們的AppBar切換按鈕可讓用戶在網站介面上控制此功能。
  • onError 會告訴我們的程式該如何處理錯誤
  • 端點網路設定決定您的應用程式將如何連線至 Solana 網路
  • Scaffold 已為我們預先設定好,但如果您是在不使用 Scaffold 的情況下設定專案,則需確保您的應用程式處於此上下文中。您可以在以下位置查看相關設定: /my-solana-dapp/src/pages/_app.tsx: <ContextProvider> 是我們的 AppBar 以及 ContentContainer.

元件

在繼續之前,請查看「Components」資料夾(路徑:/my-solana-dapp/src/components)。您會看到與我們應用程式中現有各項工具/按鈕(例如:空投、簽署訊息、發送交易)相關的元件。歡迎隨意瀏覽其中幾個檔案,了解它們的運作方式——我們稍後就要開始製作自己的元件了! 如果您對 React 還不太熟悉,沒關係。只需知道這些檔案(或稱元件)基本上都是我們網站的建構模塊。我們會將這些建構模塊放置在適當的位置(例如,您可以在 Home View 頁面中看到被呼叫的RequestAirdrop/my-solana-dapp/src/views/home/index.tsx)。

為您的 dApp 建立自訂元件

既然我們已經建立好 dApp 環境,並了解其運作方式,接下來就來新增我們的元件吧!在這個練習中,我們將使用另一篇教學中開發的腳本,用以取得與所有者錢包相關的所有代幣帳戶(可由此處取得)。 

建立可重複使用的元件範本

讓我們建立一個元件範本,既可用於這項練習,日後也能讓您輕鬆建立新的元件。

在 components 目錄中建立一個名為template.tsx 的新檔案。在終端機中,按下 CTRL+C 停止伺服器,然後輸入以下指令:

echo > ./src/components/template.tsx

開啟template.tsx檔案,將這段程式碼貼入其中,然後儲存。 

import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { FC, useState } from 'react';
import { notify } from "../utils/notifications";
//Add import dependencies here

export const Template: FC = () => {
const { connection } = useConnection();
const { publicKey } = useWallet();

//State Variables here

//dApp Scripts here

return(<div>

{/* Render Results Here */}

</div>)
}

設定您的元件

這會建立一個 React 元件的骨架,我們可以藉此新增自訂功能!請複製此範本,並儲存為名為GetTokens.tsx 的新檔案。

cp ./src/components/template.tsx ./src/components/GetTokens.tsx

GetTokens.tsx 中,將export const Template改為以下內容,以重新命名該元件: 

匯出 const GetTokens

對於這個用於檢視錢包中代幣帳戶的工具,我們需要新增幾個額外的依賴項,以便與 SPL 代幣函式庫進行互動。請在此處的「新增匯入依賴項」下加入以下內容:

import { GetProgramAccountsFilter } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";

讓我們使用setState建立一個新的狀態變數tokenTable(如果你不熟悉 React 也沒關係——這就是其中一項「魔法」,它能幫助我們在找到所需內容後,將結果渲染到頁面中)。請在模板中的「State Variables here」註解後面,立即加入以下程式碼:  

    const [tokenTable, setTokenTable] = useState(null);

建立您的查詢

我們參考了另一份Quicknode 指南中關於如何取得 Solana 錢包所持有之所有代幣帳戶的程式碼,並進行了若干調整,使其更適合在 React 中使用。具體來說,我們不打算使用console.log來顯示結果,而是希望將結果寫入一個表格(作為JSX 元素),以便在我們的網站上渲染。
 

請將這段程式碼新增至您的 state 變數下方 //dApp Scripts Here以及return() 之前: 

    async function getTokenAccounts(wallet: string) {
const filters:GetProgramAccountsFilter[] = [
{
dataSize: 165, // number of bytes
},
{
memcmp: {
offset: 32, // number of bytes
bytes: wallet, // base58 encoded string
},
}];
const accounts = await connection.getParsedProgramAccounts(
TOKEN_PROGRAM_ID, // new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
{
filters: filters,
}
);
console.log(`Found ${accounts.length} token account(s) for wallet ${wallet}: `);
if(accounts.length === 0) {
return(<div>No Token Accounts Found</div>)
}
else{
const rows = accounts.map((account,i)=>{
//Parse the account data
const parsedAccountInfo:any = account.account.data;
const mintAddress:string = parsedAccountInfo["parsed"]["info"]["mint"];
const tokenBalance: number = parsedAccountInfo["parsed"]["info"]["tokenAmount"]["uiAmount"];
return (
<tr key={i+1}>
<td key={'index'}>{i+1}</td>
<td key={'mint address'}>{mintAddress}</td>
<td key={'balance'}>{tokenBalance}</td>
</tr>)
})
const header = (<tr>
<th>Token No.</th>
<th>Mint Address</th>
<th>Qty</th>
</tr>)
setTokenTable(<table>{header}{rows}</table>)
}
}

您會發現我們的 .map 此方法會傳回一個 <tr> 針對每個找到的帳戶,都會產生一行。當呼叫此函式時,該 getTokenAccounts 此方法應從區塊鏈中擷取所有代幣帳戶,根據結果建立一個表格元件,並使用 React 將該表格元件加入我們的狀態中。我們還新增了一些邏輯,以便在未找到任何 SPL 代幣帳戶時通知使用者。 

建立點擊處理程序

為了呼叫getTokenAccounts,請建立一個名為onClick的函式,以便我們將其綁定至頁面上的按鈕。該按鈕將:

  1. 檢查錢包是否已連線。我們可以透過檢查是否能在 Wallet Adapter 的useWallet()方法中找到publicKey來確認。
  2. 嘗試為已連線的錢包取得getTokenAccounts(請注意,我們需要將公鑰轉換為字串,才能在篩選參數中使用,可透過.toString() 方法達成)。
  3. 處理錯誤。
    const onClick = async () => {
if (!publicKey) {
console.log('error', 'Wallet not connected!');
notify({ type: 'error', message: 'error', description: 'Wallet not connected!' });
return;
}
try {
await getTokenAccounts(publicKey.toString());

} catch (error: any) {
notify({ type: 'error', message: `Couldn't Find Token Accounts!`, description: error?.message });
console.log('error', `Error finding Token Accounts! ${error?.message}`);
}
};

建立一個按鈕

在您的元件的 return(), 您會看到一個 <div>. 在其中建立一個按鈕,用來呼叫我們的 onClick 函式。我們使用的是與範本中其他部分相同的 CSS。歡迎自由客製化!

        <div className="text-center">
<button
className="px-8 m-2 btn animate-pulse bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500"
onClick={onClick}
>
<span>Get Token Accounts</span>
</button>
</div>

渲染結果

現在透過呼叫我們先前使用getTokenAccounts 設定的tokenTable狀態變數來顯示結果。在您的div標籤內,位於「Render Results Here」註解之後,請加入:

            <div>{tokenTable}</div>

由於我們要等到完成標記查詢後才會定義變數,因此在程式找到任何結果之前,此處將保持空白。 

完成後,我們的元件現在看起來是這樣的: 

import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { FC, useState } from 'react';
import { notify } from "../utils/notifications";
//Add import dependencies here
import { GetProgramAccountsFilter } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";

export const GetTokens: FC = () => {
const { connection } = useConnection();
const { publicKey } = useWallet();

//State Variables here
const [tokenTable, setTokenTable] = useState(null);

//dApp Scripts here
async function getTokenAccounts(wallet: string) {
const filters:GetProgramAccountsFilter[] = [
{
dataSize: 165, // number of bytes
},
{
memcmp: {
offset: 32, // number of bytes
bytes: wallet, // base58 encoded string
},
}];
const accounts = await connection.getParsedProgramAccounts(
TOKEN_PROGRAM_ID, // new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
{
filters: filters,
}
);
console.log(`Found ${accounts.length} token account(s) for wallet ${wallet}: `);
if(accounts.length === 0) {
return(<div>No Token Accounts Found</div>)
}
else{
const rows = accounts.map((account,i)=>{
//Parse the account data
const parsedAccountInfo:any = account.account.data;
const mintAddress:string = parsedAccountInfo["parsed"]["info"]["mint"];
const tokenBalance: number = parsedAccountInfo["parsed"]["info"]["tokenAmount"]["uiAmount"];
return (
<tr key={i+1}>
<td key={'index'}>{i+1}</td>
<td key={'mint address'}>{mintAddress}</td>
<td key={'balance'}>{tokenBalance}</td>
</tr>)
})
const header = (<tr>
<th>Token No.</th>
<th>Mint Address</th>
<th>Qty</th>
</tr>)
setTokenTable(<table>{header}{rows}</table>)
}

}

const onClick = async () => {
if (!publicKey) {
console.log('error', 'Wallet not connected!');
notify({ type: 'error', message: 'error', description: 'Wallet not connected!' });
return;
}
try {
await getTokenAccounts(publicKey.toString());

} catch (error: any) {
notify({ type: 'error', message: `Couldn't Find Token Accounts!`, description: error?.message });
console.log('error', `Error finding Token Accounts! ${error?.message}`);
}
};

return(<div>
<div className="text-center">
<button
className="px-8 m-2 btn animate-pulse bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500"
onClick={onClick}
>
<span>Get Token Accounts</span>
</button>
</div>
{/* Render Results Here */}
<div>{tokenTable}</div>
</div>)
}

做得真棒!您已經使用 Solana Scaffold 建置了第一個 dApp 元件。現在是時候將它新增到網站上了。

部署您的元件

現在讓我們將這個元件新增到首頁中。開啟首頁視圖/my-solana-dapp/src/views/home/index.tsx。由於我們建立了一個新的元件,因此在使用之前必須先將其導入。請在第 14 行加入以下導入語句: 

import { GetTokens } from 'components/GetTokens';

讓我們將這個元件新增到現有的空投按鈕和錢包餘額顯示下方:

        <div>
<RequestAirdrop />
{/* ... */}
</div>
{/* ADD THIS 👇 */}
<div>
<GetTokens/>
</div>

不知道你們怎麼想,但我已經迫不及待想看看這東西實際運作的樣子了。來試試看吧!

npm 執行 dev
# 或
yarn dev

這就是我們那顆漂亮的鈕扣!

取得代幣帳戶按鈕

如果你像我一樣急於動手,很可能會收到錯誤訊息,因為你還沒有連接錢包(還記得我們之前設定過那個錯誤嗎?):  

「錢包未連線」錯誤

點擊右上角的「選擇錢包」來連接您的錢包。選擇您偏好的錢包。錢包連接完成後,您應該就能看到您的 Solana 餘額了。
好啦,現在您可以點擊「取得代幣帳戶」……咻!您看到類似這樣的畫面了嗎? 

成功的代幣表

如果您錢包中目前還沒有任何代幣,可以參考我們關於使用 Candy Machine 鑄造 NFT 的指南,開始行動。

玩得開心嗎?歡迎繼續在此基礎上進行開發,甚至可以加入一些自訂 CSS,讓這張表格呈現出你想要的樣貌!

總結

恭喜!你成功了!我們已經涵蓋了許多內容。完成這項練習後,你現在可以利用 Solana dApp Scaffold 和錢包適配器來建置自己的 dApp 了。這為你探索各類應用奠定了極佳的基礎,因此未來我們將以此為基礎,繼續建置更多元件!  

覺得這篇內容有幫助嗎?點此查看我們其他關於 Solana 的教學文章。訂閱我們的電子報,獲取更多關於 Solana 的文章與指南。如有任何意見回饋,歡迎透過Twitter聯絡我們。您隨時都可以在我們的Discord社群伺服器上與我們聊天,那裡聚集了您見過最酷的開發者們 😎

我們 ❤️ 您的回饋!
想針對這份指南分享您的想法嗎?我們非常樂意聆聽

分享這份指南