HackQuest 上学习 Solana
HackQuest 上有很多生态上学习课程,我主要选了 EVM 和 SVM 这两个生态相关的知识进行学习。
钱包适配器
如果您构建 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>
</>
);
};