閱讀時間 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 錢包適配器(「SWA」),你會在許多地方看到它,因為它使用簡便、涵蓋了 Solana 上所有最常見的錢包,並且由 Solana 社群定期維護。 那麼,它究竟是什麼?SWA 是一套針對 Solana 應用程式的模組化 TypeScript 錢包適配器與元件,讓您能輕鬆將您的去中心化應用程式(dApp)與使用者所選擇的錢包連接起來(開箱即用即可支援十多種 Solana 錢包!)。
以下是考慮使用 SWA 的幾個理由:
- 開源專案,由 Solana-labs 提供支援
- 支援多種錢包:讓您的使用者能使用自己偏好的錢包,同時您也不必為了支援新錢包而大費周章
- 內建可自訂的現成使用者介面
- 包含錨點支援
- 內建主要功能:連線、斷線、自動連線
- 支援多種前端框架
來試試看吧!
設定您的執行環境
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,以確認所有檔案都已正確複製。您的終端機畫面應類似如下:

安裝依賴項:
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 的連結:

接著,返回終端機,並在my-solana-dapp目錄中執行以下指令,建立一個.env檔案:
echo > .env
我們建議在建立正式環境網站時,使用 .env 檔案來保護您的金鑰。
在您選擇的程式碼編輯器中開啟您的專案,並導航至您剛建立的.env檔案。宣告兩個變數:REACT_APP_SOLANA_RPC_HOST和REACT_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。做得很好!您應該會看到類似以下的內容:

歡迎隨意點擊瀏覽並探索這個開發框架。您可以「連接錢包」並申請空投(開發網與測試網)、簽署訊息,或是將 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的函式,以便我們將其綁定至頁面上的按鈕。該按鈕將:
- 檢查錢包是否已連線。我們可以透過檢查是否能在 Wallet Adapter 的useWallet()方法中找到publicKey來確認。
- 嘗試為已連線的錢包取得getTokenAccounts(請注意,我們需要將公鑰轉換為字串,才能在篩選參數中使用,可透過.toString() 方法達成)。
- 處理錯誤。
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社群伺服器上與我們聊天,那裡聚集了您見過最酷的開發者們 😎
我們 ❤️ 您的回饋!
想針對這份指南分享您的想法嗎?我們非常樂意聆聽!
