Solana Wallet-Adapter简明教程

Solana Wallet-Adapter是一套库,可用于简化支持钱包浏览器扩展的过程。本文介绍Solana Wallet-Adapter的组件、安装和配置方法,以及如何在DApp中连接钱包、访问账户信息并发送交易。

Solana Wallet-Adapter简明教程
一键发币: SOL | BNB | ETH | BASE | Blast | ARB | OP | POLYGON | AVAX | FTM | OK

在前两课中我们讨论了密钥对。 密钥对用于定位帐户和签署交易。 虽然密钥对的公钥可以完全安全地共享,但密钥应始终保存在安全的位置。 如果用户的密钥被泄露,那么恶意行为者就可以耗尽其帐户中的所有资产,并以该用户的权限执行交易。

“钱包”是指存储密钥以保证其安全的任何东西。 这些安全存储选项通常可以描述为“硬件”或“软件”钱包。 硬件钱包是与计算机分开的存储设备。 软件钱包是您可以安装在现有设备上的应用程序。

软件钱包通常以浏览器扩展的形式出现。 这使得网站可以轻松地与钱包进行交互。 此类互动通常仅限于:

  • 查看钱包的公钥(地址)
  • 提交交易以供用户批准
  • 将批准的交易发送到网络
  • 提交交易后,最终用户可以“确认”交易并将其连同“签名”一起发送到网络。

签署交易需要使用你的密钥。 通过让网站向你的钱包提交交易并让钱包处理签名,你可以确保永远不会将您的密钥暴露给网站。 相反,只需与钱包应用程序共享密钥。

1、Phantom钱包

Phantom 是 Solana 生态系统中使用最广泛的软件钱包之一。 Phantom 支持一些最流行的浏览器,并具有用于随时随地连接的移动应用程序。 你可能希望你的去中心化应用程序支持多个钱包,但本课程将重点关注 Phantom。

除非自己创建钱包应用程序,否则你的代码永远不需要询问用户的密钥。 相反,你可以要求用户使用信誉良好的钱包连接到你的网站。

2、Solana Wallet-Adapter

Solana Wallet Adapter是一套库,可用于简化支持钱包浏览器扩展的过程。

Solana 的钱包适配器包含多个模块化包。 核心功能位于 @solana/wallet-adapter-base@solana/wallet-adapter-react 中。

还有一些包为常见的 UI 框架提供组件。 在本课以及整个课程中,我们将使用 @solana/wallet-adapter-react-ui 中的组件。

最后,还有一些软件包是特定钱包的适配器,包括 Phantom。 你可以使用 @solana/wallet-adapter-wallets来包含所有支持的钱包,也可以选择特定的钱包包,例如 @solana/wallet-adapter-phantom

2.1 安装Wallet-Adapter

当向现有的 React 应用程序添加钱包支持时,你首先要安装适当的软件包。 你需要 @solana/wallet-adapter-base@solana/wallet-adapter-react。 如果你计划使用提供的 React 组件,还需要添加 @solana/wallet-adapter-react-ui

开箱即用支持所有支持钱包标准的钱包,并且几乎所有当前的 Solana 钱包都支持钱包标准。 但是,如果你希望添加对任何不支持该标准的钱包的支持,请为它们添加一个包。

npm install @solana/wallet-adapter-base \
    @solana/wallet-adapter-react \
    @solana/wallet-adapter-react-ui

2.2 连接到钱包

@solana/wallet-adapter-react 允许我们通过钩子和上下文提供者持久保存和访问钱包连接状态,即:

  • useWallet
  • WalletProvider
  • useConnection
  • ConnectionProvider

为了使它们正常工作,对 useWalletuseConnection 的任何使用都应包装在 WalletProviderConnectionProvider 中。 确保这一点的最佳方法之一是将整个应用程序包装在 ConnectionProviderWalletProvider 中:

import { NextPage } from "next";
import { FC, ReactNode } from "react";
import {
  ConnectionProvider,
  WalletProvider,
} from "@solana/wallet-adapter-react";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom";
import * as web3 from "@solana/web3.js";

export const Home: NextPage = (props) => {
  const endpoint = web3.clusterApiUrl("devnet");
  const wallet = new PhantomWalletAdapter();

  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider wallets={[wallet]}>
        <p>Put the rest of your app here</p>
      </WalletProvider>
    </ConnectionProvider>
  );
};

请注意, ConnectionProvider 需要一个端点属性,而 WalletProvider 需要一个 wallets 属性。 我们将继续使用 Devnet 集群的端点,目前我们仅将 PhantomWalletAdapter 用于 wallets

此时,你可以使用 wallet.connect() 连接,这将指示钱包提示用户是否有权查看其公钥并请求批准交易。

虽然你可以在 useEffect 钩子中执行此操作,但你通常希望提供更复杂的功能。 例如,你可能希望用户能够从支持的钱包列表中进行选择,或者在连接后断开连接。

2.3 @solana/wallet-adapter-react-ui

你可以为此创建自定义组件,也可以利用 @solana/wallet-adapter-react-ui 提供的组件。 最简单方法是使用 WalletModalProviderWalletMultiButton

import { NextPage } from "next";
import { FC, ReactNode } from "react";
import {
  ConnectionProvider,
  WalletProvider,
} from "@solana/wallet-adapter-react";
import {
  WalletModalProvider,
  WalletMultiButton,
} from "@solana/wallet-adapter-react-ui";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom";
import * as web3 from "@solana/web3.js";

const Home: NextPage = (props) => {
  const endpoint = web3.clusterApiUrl("devnet");
  const wallet = new PhantomWalletAdapter();

  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider wallets={[wallet]}>
        <WalletModalProvider>
          <WalletMultiButton />
          <p>Put the rest of your app here</p>
        </WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
};

export default Home;

WalletModalProvider 添加了呈现模式屏幕的功能,供用户选择他们想要使用的钱包。 WalletMultiButton 更改行为以匹配连接状态:

如果需要更具体的功能,还可以使用更精细的组件:

  • WalletConnectButton
  • WalletModal
  • WalletModalButton
  • WalletDisconnectButton
  • WalletIcon

2.4 访问账户信息

一旦你的站点连接到钱包, useConnection 将检索 Connection 对象, useWallet 将获取 WalletContextStateWalletContextState 有一个属性 publicKey,当未连接到钱包时,该属性为 null;当连接钱包时,该属性具有用户帐户的公钥。 通过公钥和连接,你可以获取帐户信息等。

import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
import { FC, useEffect, useState } from "react";

export const BalanceDisplay: FC = () => {
  const [balance, setBalance] = useState(0);
  const { connection } = useConnection();
  const { publicKey } = useWallet();

  useEffect(() => {
    if (!connection || !publicKey) {
      return;
    }

    connection.onAccountChange(
      publicKey,
      (updatedAccountInfo) => {
        setBalance(updatedAccountInfo.lamports / LAMPORTS_PER_SOL);
      },
      "confirmed",
    );

    connection.getAccountInfo(publicKey).then((info) => {
      setBalance(info.lamports);
    });
  }, [connection, publicKey]);

  return (
    <div>
      <p>{publicKey ? `Balance: ${balance / LAMPORTS_PER_SOL} SOL` : ""}</p>
    </div>
  );
};

请注意对 connection.onAccountChange() 的调用,一旦网络确认交易,该调用就会更新显示的帐户余额。

2.5 发送交易

WalletContextState 还提供了 sendTransaction 函数,你可以使用该函数提交交易以供批准。

const { publicKey, sendTransaction } = useWallet();
const { connection } = useConnection();

const sendSol = (event) => {
  event.preventDefault();

  const transaction = new web3.Transaction();
  const recipientPubKey = new web3.PublicKey(event.target.recipient.value);

  const sendSolInstruction = web3.SystemProgram.transfer({
    fromPubkey: publicKey,
    toPubkey: recipientPubKey,
    lamports: LAMPORTS_PER_SOL * 0.1,
  });

  transaction.add(sendSolInstruction);
  sendTransaction(transaction, connection).then((sig) => {
    console.log(sig);
  });
};

当调用此函数时,连接的钱包将显示交易以供用户批准。 如果获得批准,则交易将被发送。

3、实验室

让我们采用上一课中的 Ping 程序,构建一个前端,让用户批准对程序执行 ping 操作的交易。 该程序的公钥是 ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa,数据帐户的公钥是 Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod

3.1 下载Phantom浏览器扩展并设置为Devnet

首先下载 Phantom 浏览器扩展程序。 在撰写本文时,它支持 Chrome、Brave、Firefox 和 Edge 浏览器,因此你还需要安装其中一种浏览器。 按照 Phantom 的说明创建新帐户和新钱包。

拥有钱包后,单击 Phantom UI 右下角的设置齿轮。 向下滚动并单击行项目“更改网络”,然后选择“Devnet”。 这可确保 Phantom 连接到我们将在本实验室中使用的同一网络。

3.2 下载起始代码

下载该项目的起始代码。 该项目是一个简单的 Next.js 应用程序。 除了 AppBar 组件之外,它大部分是空的。 我们将在这个实验室中构建其余部分。

可以在控制台中使用命令 npm run dev 查看其当前状态。

3.3 将应用程序包装在上下文提供程序中

首先,我们将创建一个新组件来包含我们将使用的各种钱包适配器提供程序。 在 Components 文件夹中创建一个名为 WalletContextProvider.tsx 的新文件。

让我们从功能组件的一些样板开始:

import { FC, ReactNode } from "react";

const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
  return (

  ));
};

export default WalletContextProvider;

为了正确连接到用户的钱包,我们需要 ConnectionProviderWalletProviderWalletModalProvider。 首先从 @solana/wallet-adapter-react@solana/wallet-adapter-react-ui 导入这些组件。 然后将它们添加到 WalletContextProvider 组件中。 请注意, ConnectionProvider 需要端点参数, WalletProvider 需要钱包数组。 现在,只需分别使用一个空字符串和一个空数组。

import { FC, ReactNode } from "react";
import {
  ConnectionProvider,
  WalletProvider,
} from "@solana/wallet-adapter-react";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";

const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
  return (
    <ConnectionProvider endpoint={""}>
      <WalletProvider wallets={[]}>
        <WalletModalProvider>{children}</WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
};

export default WalletContextProvider;

我们最后需要的是 ConnectionProvider 的实际端点和 WalletProvider 支持的钱包。

对于端点,我们将使用之前使用过的 @solana/web3.js 库中的 clusterApiUrl 函数,因此你需要导入它。 对于钱包列表,你还需要导入 @solana/wallet-adapter-wallets 库。

导入这些库后,创建一个常量端点,使用 clusterApiUrl 函数获取 Devnet 的 url。 然后创建一个常量 wallets 并将其设置为包含新构造的 PhantomWalletAdapter 数组。 最后,分别替换 ConnectionProviderWalletProvider中的空字符串和空数组。

要完成此组件,请在导入下方添加 require('@solana/wallet-adapter-react-ui/styles.css'); ,以确保钱包适配器库组件的样式和行为正确。

import { FC, ReactNode } from "react";
import {
  ConnectionProvider,
  WalletProvider,
} from "@solana/wallet-adapter-react";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
import * as web3 from "@solana/web3.js";
import * as walletAdapterWallets from "@solana/wallet-adapter-wallets";
require("@solana/wallet-adapter-react-ui/styles.css");

const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const endpoint = web3.clusterApiUrl("devnet");
  const wallets = [new walletAdapterWallets.PhantomWalletAdapter()];

  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider wallets={wallets}>
        <WalletModalProvider>{children}</WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
};

export default WalletContextProvider;

3.4 添加钱包多按钮

接下来让我们设置“连接”按钮。 当前按钮只是一个占位符,因为我们将使用 Wallet-Adapter 的“多按钮”,而不是使用标准按钮或创建自定义组件。 此按钮与我们在 WalletContextProvider 中设置的提供程序交互,让用户选择钱包、连接到钱包以及与钱包断开连接。 如果你需要更多自定义功能,可以创建一个自定义组件来处理此问题。

在添加“多按钮”之前,我们需要将应用程序包装在 WalletContextProvider 中。 通过将其导入 index.tsx 并将其添加到结束标记之后来执行此操作:

import { NextPage } from "next";
import styles from "../styles/Home.module.css";
import WalletContextProvider from "../components/WalletContextProvider";
import { AppBar } from "../components/AppBar";
import Head from "next/head";
import { PingButton } from "../components/PingButton";

const Home: NextPage = (props) => {
  return (
    <div className={styles.App}>
      <Head>
        <title>Wallet-Adapter Example</title>
        <meta name="description" content="Wallet-Adapter Example" />
      </Head>
      <WalletContextProvider>
        <AppBar />
        <div className={styles.AppBody}>
          <PingButton />
        </div>
      </WalletContextProvider>
    </div>
  );
};

export default Home;

如果你运行该应用程序,一切看起来应该仍然相同,因为右上角的当前按钮仍然只是一个占位符。 要解决此问题,请打开 AppBar.tsx 并将 <button>Connect</button>替换为 <WalletMultiButton/>。 你需要从 @solana/wallet-adapter-react-ui 导入 WalletMultiButton

import { FC } from "react";
import styles from "../styles/Home.module.css";
import Image from "next/image";
import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";

export const AppBar: FC = () => {
  return (
    <div className={styles.AppHeader}>
      <Image src="/solanaLogo.png" height={30} width={200} />
      <span>Wallet-Adapter Example</span>
      <WalletMultiButton />
    </div>
  );
};

此时,你应该能够运行应用程序并与屏幕右上角的多按钮进行交互。 现在应该显示为“选择钱包”。 如果你有 Phantom 扩展程序并已登录,应该能够使用这个新按钮将你的 Phantom 钱包连接到该网站。

3.5 创建按钮来 ping 程序

现在我们的应用程序可以连接到 Phantom 钱包,让我们发出“Ping!” 按钮实际上做了一些事情。

首先打开 PingButton.tsx 文件。 我们将用创建事务并将其提交到 Phantom 扩展以供最终用户批准的代码替换 onClick 内的 console.log

首先,我们需要一个连接、钱包的公钥和 Wallet-AdaptersendTransaction 函数。 为此,我们需要从 @solana/wallet-adapter-react 导入 useConnectionuseWallet。 当我们在这里时,我们还导入 @solana/web3.js,因为我们需要它来创建我们的交易。

import { useConnection, useWallet } from '@solana/wallet-adapter-react'
import * as web3 from '@solana/web3.js'
import { FC, useState } from 'react'
import styles from '../styles/PingButton.module.css'

export const PingButton: FC = () => {

  const onClick = () => {
    console.log('Ping!')
  }

  return (
    <div className={styles.buttonContainer} onClick={onClick}>
      <button className={styles.button}>Ping!</button>
    </div>
  )
}

现在使用 useConnection 挂钩创建连接常量,并使用 useWallet 钩子创建 publicKeysendTransaction 常量。

import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import * as web3 from "@solana/web3.js";
import { FC, useState } from "react";
import styles from "../styles/PingButton.module.css";

export const PingButton: FC = () => {
  const { connection } = useConnection();
  const { publicKey, sendTransaction } = useWallet();

  const onClick = () => {
    console.log("Ping!");
  };

  return (
    <div className={styles.buttonContainer} onClick={onClick}>
      <button className={styles.button}>Ping!</button>
    </div>
  );
};

这样,我们就可以填充 onClick 的主体了。

首先,检查 connectionpublicKey是否存在(如果其中一个不存在,则用户的钱包尚未连接)。

接下来,构造两个 PublicKey 实例,一个用于程序 ID  ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa,另一个用于数据帐户  Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod

接下来,构造一个 Transaction,然后构造一个新的 TransactionInstruction,其中包含数据帐户作为可写密钥。

接下来,将指令添加到交易中。

最后调用 sendTransaction

const onClick = () => {
  if (!connection || !publicKey) {
    return;
  }

  const programId = new web3.PublicKey(PROGRAM_ID);
  const programDataAccount = new web3.PublicKey(DATA_ACCOUNT_PUBKEY);
  const transaction = new web3.Transaction();

  const instruction = new web3.TransactionInstruction({
    keys: [
      {
        pubkey: programDataAccount,
        isSigner: false,
        isWritable: true,
      },
    ],
    programId,
  });

  transaction.add(instruction);
  sendTransaction(transaction, connection).then((sig) => {
    console.log(sig);
  });
};

就是这样! 如果你刷新页面,连接钱包,然后单击 ping 按钮,Phantom 应该会显示一个弹出窗口以确认交易。

3.6 打磨用户体验

你可以做很多事情来改善这里的用户体验。 例如,可以将 UI 更改为仅在连接钱包时显示 Ping 按钮,否则显示其他提示。 用户确认交易后,可以链接到 Solana Explorer 上的交易,以便他们可以轻松查看交易详细信息。 你尝试的越多,你就会越舒服,所以发挥创意吧!

还可以从本实验室下载完整的源代码,以了解所有这些内容。


原文链接:Interact With Wallets

DefiPlot翻译整理,转载请标明出处

通过 NowPayments 打赏