{"content":{"title":"如何使用 Solana 钱包适配器和脚手架将用户连接到你的 dApp","body":"### 概览\r\n\r\n正在构建 Solana 并准备将你的 dApp 推向网络吗？你需要一种方法将你的工具与用户的钱包连接起来。虽然有几种方法可以将你的 dApp 连接到用户的钱包，Solana 创建了一些方便的工具，让入门变得简单： [Solana Wallet Adapter](https://github.com/solana-labs/wallet-adapter) 和 [Solana dApp Scaffold](https://github.com/solana-labs/dapp-scaffold)。\r\n\r\n### 你将要做什么\r\n\r\n- 学习 Solana Wallet Adapter 是什么\r\n- 部署 Solana dApp Scaffold\r\n- 自定义你的环境\r\n- 创建一个简单的 dApp 来查询连接的钱包\r\n\r\n### 你需要准备什么\r\n\r\n- [Nodejs](https://nodejs.org/en/)（版本 16.15 或更高）\r\n- 一个 Solana 钱包（例如 [Phantom](https://phantom.app/)）\r\n- 有 [Solana Web3](https://www.npmjs.com/package/@solana/web3.js) 和 [Solana SPL Token Library](https://www.npmjs.com/package/@solana/spl-token) 的经验\r\n- Typescript 经验并已安装 [ts-node](https://www.npmjs.com/package/ts-node)\r\n- HTML/CSS 的基本知识\r\n- 有前端网页开发经验将会有所帮助，但不是必需的。我们将使用 React 创建一个 [Next.js](https://nextjs.org/) 项目。Solana 社区也支持 Vue、Angular 和 Svelte。访问他们的 [社区页面](https://github.com/solana-labs/wallet-adapter#community) 获取更多信息。\r\n\r\n### 什么是 Wallet Adapter \r\n\r\n有没有注意到很多 Solana dApp 在连接钱包时都有类似的用户界面？\r\n\r\n![Solana Wallet Adapter](https://img.learnblockchain.cn/2025/02/24/0-4a4cdbca763c8954cbe5ae22c036e84b.png)\r\n\r\n这就是 Solana Wallet Adapter（“SWA”），你能在很多地方看到它，因为它易于使用，涵盖了所有最常见的 Solana 钱包，并且由 Solana 社区定期维护。那么它是什么呢？SWA 是一组模块化的 TypeScript 钱包适配器和组件，用于 Solana 应用程序，允许你轻松将 dApp 连接到用户选择的钱包（开箱即用支持十几个 Solana 钱包！）。\r\n\r\n考虑使用 SWA 的一些理由：\r\n\r\n1. 开源，由 Solana-labs 支持\r\n\r\n2. 多钱包支持：允许用户使用他们选择的钱包，而不必让你为支持新钱包而头疼\r\n\r\n3. 包含现成的可定制用户界面\r\n\r\n4. 包含 [Anchor](https://learnblockchain.cn/article/6657) 支持\r\n\r\n5. 内置关键功能：连接、断开连接、自动连接\r\n\r\n6. 支持多个前端框架\r\n\r\n让我们尝试一下吧！\r\n\r\n### 设置你的环境 \r\n\r\nSWA 支持多种前端框架：\r\n\r\n- React\r\n\r\n- Material-UI\r\n\r\n- Ant Design\r\n\r\n- Angular Material UI\r\n\r\n- Vue（社区支持）\r\n\r\n- Angular（社区支持）\r\n\r\n- Svelte（社区支持）\r\n\r\n你可以使用 **npm** 指令将这些包添加到现有项目中，具体可以在 [这里](https://github.com/solana-labs/wallet-adapter#ui-components)。然而，此示例将创建一个使用 Solana dApp Scaffold 的新项目。dApp Scaffold 包括 SWA 和一些预构建的组件，让你快速启动！\r\n\r\n> **注意**：如果你要将适配器添加到现有项目中，在写作本文时当前的 SWA 不支持 React 18。请使用 React 17。\r\n\r\n在终端中创建一个新的项目目录：\r\n\r\n```\r\nmkdir my-solana-dapp\r\ncd my-solana-dapp\r\n```\r\n\r\n克隆 dApp Scaffold：\r\n\r\n```\r\ngit clone https://github.com/solana-labs/dapp-scaffold.git .\r\n```\r\n\r\n代码中的 `**.**` 将在你的项目目录中克隆该脚手架，而不会在其中创建新目录。\r\n\r\n在终端中输入 **ls** 以确保一切都正确复制。你的终端应该看起来像这样：\r\n\r\n![Checkpoint Repo Cloned](https://img.learnblockchain.cn/2025/02/24/1-a42cb5be518d8e1a7c0a1b39cb2100dc.png)\r\n\r\n安装依赖项：\r\n\r\n```\r\nnpm install\r\n## 或\r\nyarn install\r\n```\r\n\r\n去添加 SPL-token 库，我们将在本练习中后面使用：\r\n\r\n```\r\nnpm i @solana/spl-token\r\n## 或\r\nyarn add @solana/spl-token\r\n```\r\n\r\n### 添加 QuickNode 自定义 RPC 端点 \r\n\r\n要在 Solana 上构建，你需要一个 API 端点来连接网络。你可以使用公共节点（这些节点已经集成到脚手架中），或者部署和管理自己的基础设施；然而，如果你想要 8 倍更快的响应时间，你可以让我们来完成繁重的工作。\r\n\r\n \r\n我们将要在 Solana Devnet 下启动节点，但你可以启动适合你需求的节点。复制 HTTP 提供者链接：\r\n\r\n![New Solana Endpoint](https://img.learnblockchain.cn/2025/02/24/2-9b8c136f77deeffa7ef3d7e66edc9bb8.png)\r\n\r\n然后，回到你的终端，从你的 **my-solana-dapp** 目录中使用以下命令创建 **.env** 文件：\r\n\r\n```\r\necho > .env\r\n```\r\n\r\n在创建生产站点时，我们建议使用 .env 来保护你的密钥。\r\n\r\n在你选择的代码编辑器中打开你的项目，并导航到新创建的 **.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 端点时相同的网络。你的文件应该类似于：\r\n\r\n```\r\nREACT_APP_SOLANA_RPC_HOST=https://example.solana-devnet.quiknode.pro/00000000000/\r\nREACT_APP_NETWORK=devnet\r\n```\r\n\r\n我们还必须更新 **next.config.js** 以在我们的 Next.js 应用程序中使用这些环境变量。在你的项目根目录中打开文件，并用以下内容替换：\r\n\r\n```\r\n/** @type {import('next').NextConfig} */\r\nconst nextConfig = {\r\n  reactStrictMode: true,\r\n}\r\n\r\nmodule.exports = {\r\n  nextConfig,\r\n  env: {\r\n    REACT_APP_SOLANA_RPC_HOST: process.env.REACT_APP_SOLANA_RPC_HOST,\r\n    REACT_APP_NETWORK: process.env.REACT_APP_NETWORK\r\n  }\r\n}\r\n```\r\n \r\n\r\n现在通过打开 **./src/contexts/ContextProvider.tsx** 来更新脚手架的端点，并替换第 20-21 行：\r\n\r\n```\r\n    const network = WalletAdapterNetwork.Devnet;\r\n    const endpoint = useMemo(() => clusterApiUrl(network), [network]);\r\n```\r\n\r\n改为：\r\n\r\n```\r\n    const network = process.env.REACT_APP_NETWORK as WalletAdapterNetwork;\r\n    const endpoint = process.env.REACT_APP_SOLANA_RPC_HOST;\r\n```\r\n\r\n一切准备就绪！让我们启动我们的应用程序！\r\n\r\n```\r\nnpm run dev\r\n## 或\r\nyarn dev\r\n```\r\n\r\n如果你遵循了所有步骤，现在应该能够访问 [**http://localhost:3000/**](http://localhost:3000/) 并看到你的脚手架。干得好！你应该看到类似于下图的内容：\r\n\r\n![Solana Scaffold Landing Screen](https://img.learnblockchain.cn/2025/02/24/3-56cb0f702e58f3a711368eafe4480a6e.png)\r\n\r\n随意点击并探索脚手架。你可以连接钱包并请求空投（devnet 和 testnet），签名消息，或者向随机钱包发送 SOL。\r\n\r\n### 定位于 Solana Wallet Adapter 和 Scaffold \r\n\r\n在创建我们的组件之前，让我们看看工作区周围，以便更好地理解一切是如何运作的。在这里不深入 React 的什么内容之前，让我们涵盖与 Solana dApps 相关的一些基本部分。\r\n\r\n##### **钱包连接按钮** \r\n\r\n查看 **/my-solana-dapp/src/components/AppBar.tsx**。你应该看到我们从 **solana/wallet-adapter-react-ui** 导入了 **WalletMultiButton**。这个按钮，`<WalletMultiButton className=\"btn btn-ghost mr-4\" />` 是用户与我们钱包适配器交互的方式。你还会在这里注意到 **setAutoConnect** 方法的使用，设置用户切换。我们不会在这里进行更改，但如果你想设置或禁用 **autoConnect**，可以使用这种形式。\r\n\r\n##### **连接上下文** \r\n\r\n接下来查看 **/my-solana-dapp/src/contexts/ContextProvider.tsx**。这个文件是我们先前更新端点和网络的地方，也是 **Wallet Context Provider** 的主控，这里我们可以配置钱包适配器。\r\n\r\n- 首先，注意我们的 **wallets** 变量。你会注意到列出了常见的 Solana 钱包列表。尝试注释掉一个或多个钱包，用 **//** 或者取消 **new SlopeWalletAdapter()** 的注释（在导入中，第 4-11 行，以及在声明中，第 22-35 行）。刷新你的网站并连接你的钱包。你会注意到可用钱包的列表已经更改。实际上，**wallets** 变量通过 **Wallet Provider** 组件传递给钱包适配器，以确定哪些钱包将被允许在我们的 dApp 上使用。\r\n\r\n- **autoconnect** 确定用户的钱包在加载时如何与站点互动。正如我们前面讨论的，我们的 **AppBar** 切换允许用户控制这部分内容在站点的用户界面上。\r\n\r\n- onError 告诉我们的程序如何处理错误\r\n\r\n- **endpoint** 和 **network** 设置你的应用将如何连接到 Solana 网络\r\n\r\n- 脚手架已为我们配置，但如果你在不使用脚手架的情况下设置项目，你需要确保这个上下文环绕你的应用。你可以在 **/my-solana-dapp/src/pages/_app.tsx** 看到这是如何完成的：`<ContextProvider>` 是我们 **AppBar** 和 **ContentContainer** 的父组件。\r\n\r\n##### **组件** \r\n\r\n在继续之前，查看组件文件夹 **/my-solana-dapp/src/components**。你将看到与我们应用的每个预先存在的工具/按钮相关的组件（例如，Airdrop、Sign Message、Send Transaction）。随意探索其中一些文件以查看它们是如何工作的——我们很快将创建自己的组件！如果你不熟悉 React，那也没关系。只需知道这些文件，或组件，基本上是我们网站的构建块。我们将这些构建块放在适当的地方（例如，你可以在主页查看 **RequestAirdrop** 被调用：**/my-solana-dapp/src/views/home/index.tsx**）。\r\n\r\n### 为你的 dApp 创建自定义组件 \r\n\r\n现在我们已经建立了 dApp 环境并了解了它的工作原理，让我们添加我们的组件！在这个练习中，我们将使用我们在另一个教程中开发的一段脚本，获取与一个所有者的钱包关联的所有令牌账户（[可在这里访问](https://learnblockchain.cn/article/11158)）。\r\n\r\n##### **创建可重用的组件模板** \r\n\r\n让我们制作一个组件模板，以便在这个练习中使用，并且你也可以在将来轻松创建新组件。\r\n\r\n在组件目录中创建一个名为 **template.tsx** 的新文件。在终端中，按 CTRL+C 停止服务器，并输入以下命令：\r\n\r\n```\r\necho > ./src/components/template.tsx\r\n```\r\n\r\n打开 **template.tsx** 并粘贴以下代码，然后保存。\r\n\r\n```\r\nimport { useConnection, useWallet } from '@solana/wallet-adapter-react';\r\nimport { FC, useState } from 'react';\r\nimport { notify } from \"../utils/notifications\";\r\n// 在这里添加导入依赖项\r\n\r\nexport const Template: FC = () => {\r\n    const { connection } = useConnection();\r\n    const { publicKey } = useWallet();\r\n\r\n    // 状态变量在这里\r\n\r\n    // dApp 脚本在这里\r\n\r\n    return(<div>\r\n\r\n    {/* 在这里渲染结果 */}\r\n\r\n    </div>)\r\n}\r\n```\r\n\r\n##### **设置你的组件** \r\n\r\n这创建了一个我们可以用来添加自己功能的 React 组件的框架！复制模板并保存为一个名为 **GetTokens.tsx** 的新文件。\r\n\r\n```\r\ncp ./src/components/template.tsx ./src/components/GetTokens.tsx\r\n```\r\n\r\n在 **GetTokens.tsx** 中，通过将 **export const Template** 更改为：\r\n\r\n```\r\nexport const GetTokens\r\n```\r\n\r\n对于我们查看钱包令牌账户的工具，我们需要向 SPL Token Library 添加一些额外的依赖项以帮助我们进行交互。在 _Add import dependencies here_ 下添加这些：\r\n\r\n```\r\nimport { GetProgramAccountsFilter } from '@solana/web3.js';\r\nimport { TOKEN_PROGRAM_ID } from \"@solana/spl-token\";\r\n```\r\n\r\n让我们使用 _setState_ 设置一个新的状态变量 **tokenTable**（如果你不熟悉 React 那也没关系——这是一点魔法，将帮助我们的结果在找到目标后渲染到页面）。在模板中的 _State Variables here_ 注释后添加以下代码：\r\n\r\n```\r\n    const [tokenTable, setTokenTable] = useState(null);\r\n```\r\n\r\n##### **创建你的查询** \r\n\r\n我们稍微修改了现有的 [QuickNode 指南：如何获取钱包持有的所有令牌](https://learnblockchain.cn/article/11158) 的代码，以便更加兼容 React。特别是我们希望将结果写入一个可以在我们的网站上渲染的表格（作为一个 _JSX 元素_），而不是使用 _console.log_ 来显示结果。\r\n\r\n在 _//dApp Scripts Here_ 下，并在 **return()** 之前添加这段代码：\r\n\r\n```\r\n    async function getTokenAccounts(wallet: string) {\r\n        const filters:GetProgramAccountsFilter[] = [\\\r\n            {\\\r\n              dataSize: 165, // 字节数\\\r\n            },\\\r\n            {\\\r\n              memcmp: {\\\r\n                offset: 32, // 字节数\\\r\n                bytes: wallet, // base58 编码字符串\\\r\n              },\\\r\n            }];\r\n        const accounts = await connection.getParsedProgramAccounts(\r\n            TOKEN_PROGRAM_ID, // new PublicKey(\"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\")\r\n            {\r\n              filters: filters,\r\n            }\r\n          );\r\n        console.log(`找到了 ${accounts.length} 个钱包 ${wallet} 的令牌账户：`);\r\n        if(accounts.length === 0) {\r\n            return(<div>未找到令牌账户</div>)\r\n        }\r\n        else{\r\n            const rows = accounts.map((account,i)=>{\r\n                // 解析账户数据\r\n                const parsedAccountInfo:any = account.account.data;\r\n                const mintAddress:string = parsedAccountInfo[\"parsed\"][\"info\"][\"mint\"];\r\n                const tokenBalance: number = parsedAccountInfo[\"parsed\"][\"info\"][\"tokenAmount\"][\"uiAmount\"];\r\n                return (\r\n                <tr key={i+1}>\r\n                    <td key={'index'}>{i+1}</td>\r\n                    <td key={'mint address'}>{mintAddress}</td>\r\n                    <td key={'balance'}>{tokenBalance}</td>\r\n                </tr>)\r\n            })\r\n            const header = (<tr>\r\n                <th>令牌编号</th>\r\n                <th>铸造地址</th>\r\n                <th>数量</th>\r\n            </tr>)\r\n            setTokenTable(<table>{header}{rows}</table>)\r\n        }\r\n    }\r\n```\r\n\r\n你会看到我们的 _.map_ 方法为找到的每个账户返回了一个 `<tr>` 行。当被调用时，**getTokenAccounts** 方法应该从链上获取所有令牌账户，创建一个包含结果的表格元素，并使用 React 将表格元素添加到状态中。我们还添加了一些逻辑，让用户知道是否找不到 SPL 令牌账户。\r\n\r\n##### **创建点击处理函数** \r\n\r\n为了调用 **getTokenAccounts**，创建一个名为 **onClick** 的函数，我们可以将其绑定到页面上的一个按钮。按钮将：\r\n\r\n1. 检查钱包是否已连接。我们可以通过检查 **publicKey** 是否在 _useWallet()_ 方法中找到来做到这一点。\r\n\r\n2. 尝试获取已连接钱包的 _getTokenAccounts_（请注意，我们需要将公钥转换为字符串才能在我们的过滤参数中使用，使用 _.toString()_）。\r\n\r\n3. 处理错误。\r\n\r\n```\r\n    const onClick = async () => {\r\n        if (!publicKey) {\r\n            console.log('错误', '钱包未连接！');\r\n            notify({ type: 'error', message: '错误', description: '钱包未连接！' });\r\n            return;\r\n        }\r\n        try {\r\n            await getTokenAccounts(publicKey.toString());\r\n\r\n        } catch (error: any) {\r\n            notify({ type: 'error', message: `未能找到令牌账户！`, description: error?.message });\r\n            console.log('错误', `寻找令牌账户时出错！ ${error?.message}`);\r\n        }\r\n    };\r\n```\r\n\r\n##### **创建一个按钮** \r\n\r\n在组件的 **return()** 中，你会看到一个 `<div>`。在其中创建一个按钮，调用我们的 **onClick** 函数。我们将使用与模板中其他部分相同的 CSS。随意自定义！\r\n\r\n```\r\n        <div className=\"text-center\">\r\n        <button\r\n                className=\"px-8 m-2 btn animate-pulse bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500\"\r\n                onClick={onClick}\r\n            >\r\n                <span>获取令牌账户</span>\r\n        </button>\r\n        </div>\r\n```\r\n\r\n##### **渲染结果** \r\n\r\n现在通过调用我们使用 **getTokenAccounts** 设置的 **tokenTable** 状态变量来显示结果。在你的 _div_ 中，在 _Render Results Here_ 注释之后添加：\r\n\r\n```\r\n            <div>{tokenTable}</div>\r\n```\r\n\r\n由于我们在令牌查询之前没有定义一个变量，所以在程序找到内容之前，这将是空白的。\r\n\r\n这是我们完成时组件的样子：\r\n\r\n```\r\nimport { useConnection, useWallet } from '@solana/wallet-adapter-react';\r\nimport { FC, useState } from 'react';\r\nimport { notify } from \"../utils/notifications\";\r\n//添加导入依赖项\r\nimport { GetProgramAccountsFilter } from '@solana/web3.js';\r\nimport { TOKEN_PROGRAM_ID } from \"@solana/spl-token\";\r\n\r\nexport const GetTokens: FC = () => {\r\n    const { connection } = useConnection();\r\n    const { publicKey } = useWallet();\r\n\r\n    // 状态变量在这里\r\n    const [tokenTable, setTokenTable] = useState(null);\r\n\r\n    // dApp 脚本在这里\r\n    async function getTokenAccounts(wallet: string) {\r\n        const filters:GetProgramAccountsFilter[] = [\\\r\n            {\\\r\n              dataSize: 165, // 字节数\\\r\n            },\\\r\n            {\\\r\n              memcmp: {\\\r\n                offset: 32, // 字节数\\\r\n                bytes: wallet, // base58 编码字符串\\\r\n              },\\\r\n            }];\r\n        const accounts = await connection.getParsedProgramAccounts(\r\n            TOKEN_PROGRAM_ID, // new PublicKey(\"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\")\r\n            {\r\n              filters: filters,\r\n            }\r\n          );\r\n        console.log(`找到了 ${accounts.length} 个钱包 ${wallet} 的令牌账户：`);\r\n        if(accounts.length === 0) {\r\n            return(<div>未找到令牌账户</div>)\r\n        }\r\n        else{\r\n            const rows = accounts.map((account,i)=>{\r\n                // 解析账户数据\r\n                const parsedAccountInfo:any = account.account.data;\r\n                const mintAddress:string = parsedAccountInfo[\"parsed\"][\"info\"][\"mint\"];\r\n                const tokenBalance: number = parsedAccountInfo[\"parsed\"][\"info\"][\"tokenAmount\"][\"uiAmount\"];\r\n                return (\r\n                <tr key={i+1}>\r\n                    <td key={'index'}>{i+1}</td>\r\n                    <td key={'mint address'}>{mintAddress}</td>\r\n                    <td key={'balance'}>{tokenBalance}</td>\r\n                </tr>)\r\n            })\r\n            const header = (<tr>\r\n                <th>令牌编号</th>\r\n                <th>铸造地址</th>\r\n                <th>数量</th>\r\n            </tr>)\r\n            setTokenTable(<table>{header}{rows}</table>)\r\n        }\r\n    }\r\n\r\n    const onClick = async () => {\r\n        if (!publicKey) {\r\n            console.log('错误', '钱包未连接！');\r\n            notify({ type: 'error', message: '错误', description: '钱包未连接！' });\r\n            return;\r\n        }\r\n        try {\r\n            await getTokenAccounts(publicKey.toString());\r\n\r\n        } catch (error: any) {\r\n            notify({ type: 'error', message: `未能找到令牌账户！`, description: error?.message });\r\n            console.log('错误', `寻找令牌账户时出错！ ${error?.message}`);\r\n        }\r\n    };\r\n\r\n    return(<div>\r\n        <div className=\"text-center\">\r\n        <button\r\n                className=\"px-8 m-2 btn animate-pulse bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500\"\r\n                onClick={onClick}\r\n            >\r\n                <span>获取令牌账户</span>\r\n        </button>\r\n        </div>\r\n    {/* 在这里渲染结果 */}\r\n        <div>{tokenTable}</div>\r\n    </div>)\r\n}\r\n```\r\n\r\n太棒了！你已经使用 Solana 脚手架构建了你的第一个 dApp 组件。现在是将其添加到网站的时机。\r\n\r\n### 部署你的组件 \r\n\r\n现在让我们把我们的组件添加到主页。打开主页视图 **/my-solana-dapp/src/views/home/index.tsx**。既然我们创建了一个新组件，就必须先导入它。在第 14 行添加这个导入：\r\n\r\n```\r\nimport { GetTokens } from 'components/GetTokens';\r\n```\r\n\r\n让我们将我们的组件添加到现有的空投按钮和钱包余额显示的下面：\r\n\r\n```\r\n        <div>\r\n          <RequestAirdrop />\r\n          {/* ... */}\r\n        </div>\r\n        {/* 在这里添加 👇 */}\r\n        <div>\r\n          <GetTokens/>\r\n        </div>\r\n```\r\n\r\n我不知道你怎么想，但我准备好看到这个东西运作了。让我们运行一下！\r\n\r\n```\r\nnpm run dev\r\n## 或\r\nyarn dev\r\n```\r\n\r\n我们的美丽按钮出现了！\r\n\r\n![Get Token Accounts Button](https://img.learnblockchain.cn/2025/02/24/4-800024ad6b669509ba097d9e9b6007b9.png)\r\n\r\n如果你像我一样有点急于探索，你可能会收到错误消息，因为你还没有连接钱包（记得我们设置了那个错误？）：\r\n\r\n![Wallet Not Connected Error](https://img.learnblockchain.cn/2025/02/24/5-b558b7a91fcc03de60954bf377caf98c.png)\r\n\r\n点击右上角的 **选择钱包** 以连接你的钱包。选择你喜欢的钱包。连接后，你应该能看到你的 Solana 余额。\r\n\r\n好了，**现在** 你可以点击“获取令牌账户”... 嘣！你看到这样的内容吗？\r\n\r\n![Succesful Token Table](https://img.learnblockchain.cn/2025/02/24/6-6d7a64cdb2e547090f31a28be9eb9ffd.png)\r\n\r\n如果你钱包中还没有任何代币，可以查看我们的 [使用 Candy Machine 铸造 NFT 的指南](https://www.quicknode.com/guides/web3-sdks/how-to-deploy-an-nft-on-solana-using-candy-machine-v2) 来开始。\r\n\r\n觉得好玩吗？随意继续在此基础上构建，甚至加入一些自定义 CSS，使表格看起来符合你想要的样子！\r\n\r\n### 完结\r\n\r\n恭喜你！你完成了！我们覆盖了很多内容。完成本练习后，你现在可以使用 Solana dApp Scaffold 和 Wallet Adapter 构建自己的 dApp。这是跳入各种事物的绝佳基础，因此我们将在未来从这个基础上构建更多组件！\r\n\r\n觉得有用吗？查看我们其他的一些 Solana 教程 [这里](https://www.quicknode.com/guides/)。订阅我们的 [通讯](https://go.quicknode.com/newsletter)，获取更多关于 Solana 的文章和指南。随时通过 [Twitter](https://twitter.com/QuickNode) 与我们联系。如有反馈，也欢迎随时与我们交流，我们在 [Discord](https://discord.gg/quicknode) 的社区服务器上，你将遇到一些你见过的最酷的开发者 😎 \r\n\r\n \r\n>- 原文链接： [quicknode.com/guides/sol...](https://www.quicknode.com/guides/solana-development/dapps/how-to-connect-users-to-your-dapp-with-the-solana-wallet-adapter-and-scaffold)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/25306","address":null},"history":null,"timestamp":1740450384,"version":1}