前端-Day 02 - Solana 钱包集成

0 阅读2分钟

Day 02 - Solana 钱包集成

日期: 第2天
目标: 集成 Solana 钱包连接功能

📋 任务清单

  • Solana Wallet Adapter 安装配置
  • WalletProvider 上下文创建
  • 连接钱包按钮组件
  • 钱包连接状态管理

💻 实现步骤

1. 安装依赖

npm install @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets @solana/wallet-adapter-base
npm install @solana/web3.js bs58

2. 创建 WalletProvider

src/contexts/SolanaWalletProvider.tsx

'use client'

import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { useMemo } from 'react';

require('@solana/wallet-adapter-react-ui/styles.css');

export function SolanaWalletProvider({ children }: { children: React.ReactNode }) {
  const network = WalletAdapterNetwork.Mainnet;
  
  const endpoint = useMemo(() => {
    return process.env.NEXT_PUBLIC_RPC_URL || 'https://api.mainnet-beta.solana.com';
  }, []);

  const wallets = useMemo(
    () => [
      new PhantomWalletAdapter(),
      new SolflareWalletAdapter(),
    ],
    []
  );

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

3. 连接钱包按钮组件

src/app/[locale]/components/buttons/ConnectButton.tsx

'use client'

import { useWallet } from '@solana/wallet-adapter-react';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import { FC } from 'react';

export const ConnectButton: FC = () => {
  const { connected, publicKey, wallet } = useWallet();

  return (
    <div className="flex items-center gap-4">
      <WalletMultiButton style={{
        backgroundColor: '#3B82F6',
        padding: '8px 16px',
        borderRadius: '8px',
        color: 'white',
        fontWeight: 'bold',
      }} />
      
      {connected && publicKey && (
        <div className="text-sm text-gray-600">
          {wallet?.adapter.name}: {publicKey.toBase58().slice(0, 8)}...
        </div>
      )}
    </div>
  );
};

export default ConnectButton;

4. 在 Layout 中使用 Provider

src/app/[locale]/layout.tsx

import { SolanaWalletProvider } from '@/contexts/SolanaWalletProvider';
import ConnectButton from './ components/buttons/ConnectButton';

export default function RootLayout({
  children,
  params: { locale }
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  return (
    <html lang={locale}>
      <body>
        <SolanaWalletProvider>
          <nav className="flex justify-between items-center p-4 bg-gray-100">
            <h1 className="text-2xl font-bold">Geng</h1>
            <ConnectButton />
          </nav>
          <main>{children}</main>
        </SolanaWalletProvider>
      </body>
    </html>
  );
}

5. 自定义钱包连接 Hook

src/app/[locale]/hooks/useWalletConnection.ts

'use client'

import { useWallet } from '@solana/wallet-adapter-react';
import { useWalletModal } from '@solana/wallet-adapter-react-ui';
import { useCallback } from 'react';

export const useWalletConnection = () => {
  const { connected, publicKey, signTransaction, sendTransaction } = useWallet();
  const { setVisible } = useWalletModal();

  const ensureConnected = useCallback(async () => {
    if (!connected) {
      setVisible(true);
      return false;
    }
    return true;
  }, [connected, setVisible]);

  const getAddress = useCallback(() => {
    return publicKey?.toBase58() || null;
  }, [publicKey]);

  return {
    connected,
    publicKey,
    address: getAddress(),
    signTransaction,
    sendTransaction,
    ensureConnected,
  };
};

⚠️ 常见问题

问题1:钱包未连接时应用崩溃

现象:

Error: Cannot read properties of undefined (reading 'toBase58')

原因: 没有正确的条件检查,访问 publicKey 时未确认连接

解决方案:

const { connected, publicKey } = useWallet();

// ❌ 错误做法
const address = publicKey.toBase58(); // publicKey 可能为 null

// ✅ 正确做法
if (!connected || !publicKey) {
  return <div>Please connect your wallet first</div>;
}

const address = publicKey.toBase58();

问题2:网络切换后钱包断开连接

现象: 用户手动切换网络后应用功能异常

原因: RPC 端点与钱包网络不同步

解决方案:

const [network, setNetwork] = useState(WalletAdapterNetwork.Mainnet);

const endpoint = useMemo(() => {
  if (network === WalletAdapterNetwork.Devnet) {
    return 'https://api.devnet.solana.com';
  }
  return 'https://api.mainnet-beta.solana.com';
}, [network]);

// 提供网络切换接口
const switchNetwork = useCallback((newNetwork: WalletAdapterNetwork) => {
  setNetwork(newNetwork);
  // 触发钱包重新连接
  disconnect();
}, []);

📊 验证清单

  • 应用启动时显示连接钱包按钮
  • 点击按钮能打开钱包选择界面
  • 连接成功后显示钱包地址
  • 断开连接后界面恢复初始状态
  • 切换钱包能正常工作

🧪 测试代码

// pages/test/wallet.tsx
'use client'

import { useWallet } from '@solana/wallet-adapter-react';
import { useWalletConnection } from '@/app/[locale]/hooks/useWalletConnection';

export default function WalletTest() {
  const { connected, publicKey } = useWallet();
  const { address, ensureConnected } = useWalletConnection();

  const handleTest = async () => {
    const isConnected = await ensureConnected();
    if (isConnected) {
      console.log('Wallet connected:', address);
    }
  };

  return (
    <div className="p-8 space-y-4">
      <p>Connected: {String(connected)}</p>
      <p>Address: {address}</p>
      <button
        onClick={handleTest}
        className="px-4 py-2 bg-blue-500 text-white rounded"
      >
        Test Connection
      </button>
    </div>
  );
}

📝 总结

✅ 集成了 Solana Wallet Adapter
✅ 创建了钱包连接 UI 组件
✅ 实现了安全的钱包状态管理
✅ 解决了常见的连接问题

下一步: 项目上下文和工具函数(Day 03)


时间估计: 2-3 小时
难度等级: ⭐⭐ 中等
关键词: Solana, Wallet Adapter, 钱包连接