如何使用 Solana 钱包适配器和脚手架将用户连接到你的 dApp

  • QuickNode
  • 发布于 2024-09-13 12:17
  • 阅读 29

本文介绍了如何使用Solana Wallet Adapter和dApp Scaffold构建与用户钱包连接的dApp。内容包括环境设置、组件创建、API集成等步骤,并提供了详细的代码示例。通过本指南,开发者能够快速上手Solana生态并构建自己的去中心化应用。

概览

正在构建 Solana 并准备将你的 dApp 推向网络吗?你需要一种方法将你的工具与用户的钱包连接起来。虽然有几种方法可以将你的 dApp 连接到用户的钱包,Solana 创建了一些方便的工具,让入门变得简单: Solana Wallet AdapterSolana dApp Scaffold

你将要做什么

  • 学习 Solana Wallet Adapter 是什么
  • 部署 Solana dApp Scaffold
  • 自定义你的环境
  • 创建一个简单的 dApp 来查询连接的钱包

你需要准备什么

  • Nodejs(版本 16.15 或更高)
  • 一个 Solana 钱包(例如 Phantom
  • Solana Web3Solana SPL Token Library 的经验
  • Typescript 经验并已安装 ts-node
  • HTML/CSS 的基本知识
  • 有前端网页开发经验将会有所帮助,但不是必需的。我们将使用 React 创建一个 Next.js 项目。Solana 社区也支持 Vue、Angular 和 Svelte。访问他们的 社区页面 获取更多信息。

什么是 Wallet Adapter

有没有注意到很多 Solana dApp 在连接钱包时都有类似的用户界面?

Solana Wallet Adapter

这就是 Solana Wallet Adapter(“SWA”),你能在很多地方看到它,因为它易于使用,涵盖了所有最常见的 Solana 钱包,并且由 Solana 社区定期维护。那么它是什么呢?SWA 是一组模块化的 TypeScript 钱包适配器和组件,用于 Solana 应用程序,允许你轻松将 dApp 连接到用户选择的钱包(开箱即用支持十几个 Solana 钱包!)。

考虑使用 SWA 的一些理由:

  1. 开源,由 Solana-labs 支持

  2. 多钱包支持:允许用户使用他们选择的钱包,而不必让你为支持新钱包而头疼

  3. 包含现成的可定制用户界面

  4. 包含 Anchor 支持

  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 Scaffold:

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

代码中的 **.** 将在你的项目目录中克隆该脚手架,而不会在其中创建新目录。

在终端中输入 ls 以确保一切都正确复制。你的终端应该看起来像这样:

Checkpoint Repo Cloned

安装依赖项:

npm install
## 或
yarn install

去添加 SPL-token 库,我们将在本练习中后面使用:

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

添加 QuickNode 自定义 RPC 端点

要在 Solana 上构建,你需要一个 API 端点来连接网络。你可以使用公共节点(这些节点已经集成到脚手架中),或者部署和管理自己的基础设施;然而,如果你想要 8 倍更快的响应时间,你可以让我们来完成繁重的工作。

我们将要在 Solana Devnet 下启动节点,但你可以启动适合你需求的节点。复制 HTTP 提供者链接:

New Solana Endpoint

然后,回到你的终端,从你的 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 行:

    const network = WalletAdapterNetwork.Devnet;
    const endpoint = useMemo(() => clusterApiUrl(network), [network]);

改为:

    const network = process.env.REACT_APP_NETWORK as WalletAdapterNetwork;
    const endpoint = process.env.REACT_APP_SOLANA_RPC_HOST;

一切准备就绪!让我们启动我们的应用程序!

npm run dev
## 或
yarn dev

如果你遵循了所有步骤,现在应该能够访问 http://localhost:3000/ 并看到你的脚手架。干得好!你应该看到类似于下图的内容:

Solana Scaffold Landing Screen

随意点击并探索脚手架。你可以连接钱包并请求空投(devnet 和 testnet),签名消息,或者向随机钱包发送 SOL。

定位于 Solana Wallet Adapter 和 Scaffold

在创建我们的组件之前,让我们看看工作区周围,以便更好地理解一切是如何运作的。在这里不深入 React 的什么内容之前,让我们涵盖与 Solana dApps 相关的一些基本部分。

钱包连接按钮

查看 /my-solana-dapp/src/components/AppBar.tsx。你应该看到我们从 solana/wallet-adapter-react-ui 导入了 WalletMultiButton。这个按钮,<WalletMultiButton className="btn btn-ghost mr-4" /> 是用户与我们钱包适配器交互的方式。你还会在这里注意到 setAutoConnect 方法的使用,设置用户切换。我们不会在这里进行更改,但如果你想设置或禁用 autoConnect,可以使用这种形式。

连接上下文

接下来查看 /my-solana-dapp/src/contexts/ContextProvider.tsx。这个文件是我们先前更新端点和网络的地方,也是 Wallet Context Provider 的主控,这里我们可以配置钱包适配器。

  • 首先,注意我们的 wallets 变量。你会注意到列出了常见的 Solana 钱包列表。尝试注释掉一个或多个钱包,用 // 或者取消 new SlopeWalletAdapter() 的注释(在导入中,第 4-11 行,以及在声明中,第 22-35 行)。刷新你的网站并连接你的钱包。你会注意到可用钱包的列表已经更改。实际上,wallets 变量通过 Wallet Provider 组件传递给钱包适配器,以确定哪些钱包将被允许在我们的 dApp 上使用。

  • autoconnect 确定用户的钱包在加载时如何与站点互动。正如我们前面讨论的,我们的 AppBar 切换允许用户控制这部分内容在站点的用户界面上。

  • onError 告诉我们的程序如何处理错误

  • endpointnetwork 设置你的应用将如何连接到 Solana 网络

  • 脚手架已为我们配置,但如果你在不使用脚手架的情况下设置项目,你需要确保这个上下文环绕你的应用。你可以在 /my-solana-dapp/src/pages/_app.tsx 看到这是如何完成的:<ContextProvider> 是我们 AppBarContentContainer 的父组件。

组件

在继续之前,查看组件文件夹 /my-solana-dapp/src/components。你将看到与我们应用的每个预先存在的工具/按钮相关的组件(例如,Airdrop、Sign Message、Send Transaction)。随意探索其中一些文件以查看它们是如何工作的——我们很快将创建自己的组件!如果你不熟悉 React,那也没关系。只需知道这些文件,或组件,基本上是我们网站的构建块。我们将这些构建块放在适当的地方(例如,你可以在主页查看 RequestAirdrop 被调用:/my-solana-dapp/src/views/home/index.tsx)。

为你的 dApp 创建自定义组件

现在我们已经建立了 dApp 环境并了解了它的工作原理,让我们添加我们的组件!在这个练习中,我们将使用我们在另一个教程中开发的一段脚本,获取与一个所有者的钱包关联的所有令牌账户(可在这里访问)。

创建可重用的组件模板

让我们制作一个组件模板,以便在这个练习中使用,并且你也可以在将来轻松创建新组件。

在组件目录中创建一个名为 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";
// 在这里添加导入依赖项

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

    // 状态变量在这里

    // dApp 脚本在这里

    return(<div>

    {/* 在这里渲染结果 */}

    </div>)
}
设置你的组件

这创建了一个我们可以用来添加自己功能的 React 组件的框架!复制模板并保存为一个名为 GetTokens.tsx 的新文件。

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

GetTokens.tsx 中,通过将 export const Template 更改为:

export const GetTokens

对于我们查看钱包令牌账户的工具,我们需要向 SPL Token Library 添加一些额外的依赖项以帮助我们进行交互。在 Add import dependencies here 下添加这些:

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 指南:如何获取钱包持有的所有令牌 的代码,以便更加兼容 React。特别是我们希望将结果写入一个可以在我们的网站上渲染的表格(作为一个 JSX 元素),而不是使用 console.log 来显示结果。

//dApp Scripts Here 下,并在 return() 之前添加这段代码:

    async function getTokenAccounts(wallet: string) {
        const filters:GetProgramAccountsFilter[] = [\
            {\
              dataSize: 165, // 字节数\
            },\
            {\
              memcmp: {\
                offset: 32, // 字节数\
                bytes: wallet, // base58 编码字符串\
              },\
            }];
        const accounts = await connection.getParsedProgramAccounts(
            TOKEN_PROGRAM_ID, // new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
            {
              filters: filters,
            }
          );
        console.log(`找到了 ${accounts.length} 个钱包 ${wallet} 的令牌账户:`);
        if(accounts.length === 0) {
            return(<div>未找到令牌账户</div>)
        }
        else{
            const rows = accounts.map((account,i)=>{
                // 解析账户数据
                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>令牌编号</th>
                <th>铸造地址</th>
                <th>数量</th>
            </tr>)
            setTokenTable(<table>{header}{rows}</table>)
        }
    }

你会看到我们的 .map 方法为找到的每个账户返回了一个 <tr> 行。当被调用时,getTokenAccounts 方法应该从链上获取所有令牌账户,创建一个包含结果的表格元素,并使用 React 将表格元素添加到状态中。我们还添加了一些逻辑,让用户知道是否找不到 SPL 令牌账户。

创建点击处理函数

为了调用 getTokenAccounts,创建一个名为 onClick 的函数,我们可以将其绑定到页面上的一个按钮。按钮将:

  1. 检查钱包是否已连接。我们可以通过检查 publicKey 是否在 useWallet() 方法中找到来做到这一点。

  2. 尝试获取已连接钱包的 getTokenAccounts(请注意,我们需要将公钥转换为字符串才能在我们的过滤参数中使用,使用 .toString())。

  3. 处理错误。

    const onClick = async () => {
        if (!publicKey) {
            console.log('错误', '钱包未连接!');
            notify({ type: 'error', message: '错误', description: '钱包未连接!' });
            return;
        }
        try {
            await getTokenAccounts(publicKey.toString());

        } catch (error: any) {
            notify({ type: 'error', message: `未能找到令牌账户!`, description: error?.message });
            console.log('错误', `寻找令牌账户时出错! ${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>获取令牌账户</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";
//添加导入依赖项
import { GetProgramAccountsFilter } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";

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

    // 状态变量在这里
    const [tokenTable, setTokenTable] = useState(null);

    // dApp 脚本在这里
    async function getTokenAccounts(wallet: string) {
        const filters:GetProgramAccountsFilter[] = [\
            {\
              dataSize: 165, // 字节数\
            },\
            {\
              memcmp: {\
                offset: 32, // 字节数\
                bytes: wallet, // base58 编码字符串\
              },\
            }];
        const accounts = await connection.getParsedProgramAccounts(
            TOKEN_PROGRAM_ID, // new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
            {
              filters: filters,
            }
          );
        console.log(`找到了 ${accounts.length} 个钱包 ${wallet} 的令牌账户:`);
        if(accounts.length === 0) {
            return(<div>未找到令牌账户</div>)
        }
        else{
            const rows = accounts.map((account,i)=>{
                // 解析账户数据
                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>令牌编号</th>
                <th>铸造地址</th>
                <th>数量</th>
            </tr>)
            setTokenTable(<table>{header}{rows}</table>)
        }
    }

    const onClick = async () => {
        if (!publicKey) {
            console.log('错误', '钱包未连接!');
            notify({ type: 'error', message: '错误', description: '钱包未连接!' });
            return;
        }
        try {
            await getTokenAccounts(publicKey.toString());

        } catch (error: any) {
            notify({ type: 'error', message: `未能找到令牌账户!`, description: error?.message });
            console.log('错误', `寻找令牌账户时出错! ${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>获取令牌账户</span>
        </button>
        </div>
    {/* 在这里渲染结果 */}
        <div>{tokenTable}</div>
    </div>)
}

太棒了!你已经使用 Solana 脚手架构建了你的第一个 dApp 组件。现在是将其添加到网站的时机。

部署你的组件

现在让我们把我们的组件添加到主页。打开主页视图 /my-solana-dapp/src/views/home/index.tsx。既然我们创建了一个新组件,就必须先导入它。在第 14 行添加这个导入:

import { GetTokens } from 'components/GetTokens';

让我们将我们的组件添加到现有的空投按钮和钱包余额显示的下面:

        <div>
          <RequestAirdrop />
          {/* ... */}
        </div>
        {/* 在这里添加 👇 */}
        <div>
          <GetTokens/>
        </div>

我不知道你怎么想,但我准备好看到这个东西运作了。让我们运行一下!

npm run dev
## 或
yarn dev

我们的美丽按钮出现了!

Get Token Accounts Button

如果你像我一样有点急于探索,你可能会收到错误消息,因为你还没有连接钱包(记得我们设置了那个错误?):

Wallet Not Connected Error

点击右上角的 选择钱包 以连接你的钱包。选择你喜欢的钱包。连接后,你应该能看到你的 Solana 余额。

好了,现在 你可以点击“获取令牌账户”... 嘣!你看到这样的内容吗?

Succesful Token Table

如果你钱包中还没有任何代币,可以查看我们的 使用 Candy Machine 铸造 NFT 的指南 来开始。

觉得好玩吗?随意继续在此基础上构建,甚至加入一些自定义 CSS,使表格看起来符合你想要的样子!

完结

恭喜你!你完成了!我们覆盖了很多内容。完成本练习后,你现在可以使用 Solana dApp Scaffold 和 Wallet Adapter 构建自己的 dApp。这是跳入各种事物的绝佳基础,因此我们将在未来从这个基础上构建更多组件!

觉得有用吗?查看我们其他的一些 Solana 教程 这里。订阅我们的 通讯,获取更多关于 Solana 的文章和指南。随时通过 Twitter 与我们联系。如有反馈,也欢迎随时与我们交流,我们在 Discord 的社区服务器上,你将遇到一些你见过的最酷的开发者 😎

  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。