Solana 的钱包适配器

418 阅读3分钟

HackQuest 上学习 Solana

HackQuest 上有很多生态上学习课程,我主要选了 EVM 和 SVM 这两个生态相关的知识进行学习。

image.png

钱包适配器

如果您构建 Web 应用程序,并且需要用户能够连接到他们的钱包并通过您的应用程序签署交易,您将需要 Solana 的钱包适配器。钱包适配器是一套模块化包:

  • 核心功能位于 @solana/wallet-adapter-base 中。
  • React 支持由 @solana/wallet-adapter-react 添加。
  • 其他包为常见的 UI 框架提供组件。@solana/wallet-adapter-react-ui

为 React 安装 Wallet-Adapter 库

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

连接到钱包

@solana/wallet-adapter-react 允许我们通过 hook 和 context providers 来持久化和访问钱包连接状态,即:

  • useWallet  使用钱包
  • WalletProvider  钱包提供商
  • useConnection 连接提供程序
  • ConnectionProvider

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

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 库,因此您需要导入它。 对于钱包数组,您还需要导入 @solana/wallet-adapter-wallets 库。

导入这些库后,创建一个使用 clusterApiUrl 函数获取 Devnet 的 URL。然后创建一个名为 wallets 并将其设置为空数组 - 由于所有钱包都支持 Wallet Standard,因此我们不再需要任何自定义的钱包适配器。最后,分别替换 ConnectionProvider 和 WalletProvider 中的空字符串和空数组。

要完成此组件,请添加 import'@solana/wallet-adapter-react-ui/styles.css'; 以确保 Wallet Adapter 库组件的样式和行为正确。

import { FC, ReactNode, useMemo } from "react";
import {
  ConnectionProvider,
  WalletProvider,
} from "@solana/wallet-adapter-react";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
import { clusterApiUrl } 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 = clusterApiUrl("devnet");
  const wallets = useMemo(() => [], []);
 
  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider wallets={wallets}>
        <WalletModalProvider>{children}</WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
};
 
export default WalletContextProvider;

添加钱包多按钮

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

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>
  );
};

此时,您应该能够运行该应用程序并与屏幕右上角的多按钮进行交互。它现在应该显示为“选择钱包”。如果您安装了钱包,您应该能够使用此按钮将您的钱包连接到该网站。

现在使用 useConnection 钩子创建一个连接常量,并使用 useWallet 钩子创建 publicKey 和 sendTransaction 常量。

转账

import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import {
  PublicKey,
  Transaction,
  TransactionInstruction,
  sendTransaction,
} from "@solana/web3.js";
import { FC, useState,useEffect } from "react";
import styles from "../styles/PingButton.module.css";
 
export const PingButton: FC = () => {
  const { connection } = useConnection();
  const { publicKey, sendTransaction } = useWallet();
 
  const updateBalance = async () => {
      if (!connection || !publicKey) {
        console.error("Wallet not connected or connection unavailable");
        return
      }
      console.log('LAMPORTS_PER_SOL',LAMPORTS_PER_SOL)
      try {
        connection.onAccountChange(
          publicKey,
          updatedAccountInfo => {
            console.log('updatedAccountInfo',updatedAccountInfo)
            setBalance(updatedAccountInfo.lamports / LAMPORTS_PER_SOL);
          },
          "confirmed",
        );
 
        const accountInfo = await connection.getAccountInfo(publicKey);
        console.log('accountInfo',accountInfo)
        if (accountInfo) {
          setBalance(accountInfo.lamports / LAMPORTS_PER_SOL);
        } else {
          throw new Error("Account info not found");
        }
      } catch (error) {
        console.error("Failed to retrieve account info:", error);
      }
    };
  
   useEffect(() => {
    console.log('publicKey',publicKey)
    updateBalance();
  }, [connection, publicKey]);
  
  const sendSol = async event => {
  event.preventDefault();
 
  if (!publicKey) {
    console.error("Wallet not connected");
    return;
  }
  console.log('event.currentTarget.recipient',event.currentTarget.recipient)
  try {
    const recipientPubKey = new PublicKey(event.currentTarget.recipient.value);
 
    const transaction = new Transaction();
    const sendSolInstruction = SystemProgram.transfer({
      fromPubkey: publicKey,
      toPubkey: recipientPubKey,
      lamports: 0.1 * LAMPORTS_PER_SOL,
    });
 
    transaction.add(sendSolInstruction);
 
    const signatureHash = await sendTransaction(transaction, connection);
    setSignature(signatureHash)
    console.log(`Transaction signature: ${signatureHash}`);
  } catch (error) {
    console.error("Transaction failed", error);
  }
};
 
  return (
    <>
    <WalletMultiButton />
      <p>{publicKey ? `Balance: ${balance} SOL` : ""}</p>
       <form onSubmit={sendSol} className="form">
        <label className = 'formField ' htmlFor="amount">Amount (in SOL) to send:</label>
        <input className="input formField" id="amount" type="text" placeholder="e.g. 0.1" required />
        <br />
        <label className = 'formField' htmlFor="recipient">Send SOL to:</label>
        <input className="input formField" id="recipient" type="text"  placeholder="e.g. 4Zw1fXuYuJhWhu9KLEYMhiPEiqcpKd6akw3WRZCv84HA" required />
        <button className = "formButton" type="submit" >Send</button>
      </form> 
    </>
  );
};

image.png