Solana 钱包 API 开发者指南:连接、签名、发交易

和 window.solana、钱包适配器打交道:检测 Phantom 与 Solflare、处理 connect() 的返回差异,以及签名时别踩的坑。

BoltTx Team··9 min read
solana钱包wallet-adapterphantomsolflare开发者

如果你在搭一个让用户连钱包提交交易的 Solana 前端,你会在钱包集成上花的时间比文档让你以为的多。Solana 钱包生态过得去、但不完美;SDK 帮你处理大部分活、但粗糙边角你不处理就会变成生产问题。

这篇我们讲正确集成 Solana 钱包要知道的事:SDK 选择、签名模式、交易提交,以及把"能跑"和"生产级"分开的那些细节。

"Solana 钱包 API"到底指什么

这词有几种不同含义:

这篇讲前两个。钱包相关的 RPC 细节大部分在 Solana RPC 开发者指南里。

SDK 全景

大多数前端集成的选择是 @solana/wallet-adapter。它处理:

React 前端的话,相关的包提供 hook:

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

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

function App() {
  return (
    <ConnectionProvider endpoint={RPC_URL}>
      <WalletProvider wallets={wallets} autoConnect>
        <WalletModalProvider>
          <YourApp />
        </WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
}

非 React 应用直接用底层的 @solana/wallet-adapter-base

通过钱包发交易

两种模式:

模式 1:wallet adapter 帮你提交。

const tx = new Transaction().add(yourInstruction);
const signature = await sendTransaction(tx, connection, {
  skipPreflight: true,
  maxRetries: 0,
});

Wallet adapter 用你提供的 connection 签名提交。简单,大多数情况下行。

模式 2:分开签名,自己提交。

const tx = new Transaction().add(yourInstruction);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx.feePayer = publicKey;

const signed = await signTransaction(tx);

const signature = await connection.sendRawTransaction(signed.serialize(), {
  skipPreflight: true,
  maxRetries: 0,
});

这个模式给你提交侧的更多控制。要给发送用一个跟 wallet provider 不一样的 RPC、或者要在提交前 inspect/log 签名后的交易时,这个模式好用。

为什么经常想要两个不同的 RPC

很多生产应用用这个模式:读 RPC 一个(传给 ConnectionProvider),写 RPC 另一个(直接给 sendRawTransaction 用)。

理由:

两个用同一个 RPC 是妥协。各用对的能给到更好的用户体验(读 RPC 让 UI 更灵)和更好的经济模型(写 RPC 让被夹代价更低)。

const READ_RPC = "https://your-read-rpc.example/?api-key=...";
const WRITE_RPC = "https://bolttx.io/?api-key=...";

const readConnection = new Connection(READ_RPC, "confirmed");
const writeConnection = new Connection(WRITE_RPC, "processed");

// 给 provider 传 readConnection
<ConnectionProvider endpoint={READ_RPC}>...</ConnectionProvider>

// 直接用 writeConnection 发送
const signature = await writeConnection.sendRawTransaction(signed.serialize(), {
  skipPreflight: true,
  maxRetries: 0,
});

常见的钱包集成错误

硬编码一个 wallet adapter。 把用户锁死在一个钱包上。永远包含多个选项。

忽略连接失败。 wallet-adapter 在拒绝或没装时会抛错。包 try/catch、显示有意义的错误。

不处理网络不匹配。 用户在 devnet 但你 app 要 mainnet。检测到提示切换。

轮询钱包状态。 Hook 已经给你响应式状态了。别按间隔轮询 wallet.publicKey

提交交易不确认。 钱包返回签名,但交易可能没上链。永远确认。

忘了 blockhash 新鲜度。 哪儿都一样的问题:陈旧 blockhash 静默失败。签前刷新。

不处理用户取消的情况。 一些钱包返回特定错误码;优雅处理(用户取消时别显示"交易失败")。

移动钱包集成

移动是另一回事。移动钱包用深链做交易签名——你的 dApp 通过深链打开钱包、钱包签、再跳回来。比桌面流程复杂:

大多数团队用 Mobile Wallet Adapter (MWA) 把这层抽象掉。如果你做移动优先的 dApp,给这块预留时间——它跟桌面不一样。

不同 app 类型的常见模式

DEX UI / swap 接口。 读 RPC 拿 quote、wallet adapter 签、Anti-MEV 写 RPC 提交。给用户反馈展示成交价 vs 预期成交价。

NFT 市场。 读量大用来看列表;写流量给购买/挂单。两边都要可靠;读流量大头。

借贷 dApp。 混合读写。读必须准(别显示陈旧的抵押健康度)。写必须可靠确认。

代币 launchpad / IDO。 发币期间是突发写流量。写 RPC 在拥堵下的表现是关键——你最需要它的时刻,正是大多数服务商表现降级的时刻。

钱包 app 自己。 读量大,主要是余额/历史、偶尔写交易。读 RPC 质量主导用户感知。

这周可以做什么

搭钱包集成 dApp 的话:

  1. 设 wallet-adapter,带多个钱包选项。 至少 Phantom、Solflare、Backpack。
  2. 用分开的读写 RPC。 Provider 给读用,sendRawTransaction 给写用。
  3. 分开签名、自己提交。 给你提交路径的控制权。
  4. 提交后确认。 别信"签名返回了"等于"上链了"。
  5. 处理取消情况。 用户拒绝交易时别显示错误。
  6. 多钱包测试。 Phantom 和 Solflare 在细节行为上有微妙差别。
  7. 写侧加每笔签名级别的遥测。 跟踪用户实际体验。

在钱包 App 提交侧试一下 BoltTx

BoltTx 专门给钱包集成 dApp 的写侧做了优化:

集成:

import { Connection } from "@solana/web3.js";

const writeConnection = new Connection(
  "https://bolttx.io/?api-key=YOUR_API_KEY",
  "processed"
);

const signed = await wallet.signTransaction(tx);
const signature = await writeConnection.sendRawTransaction(signed.serialize(), {
  skipPreflight: true,
  maxRetries: 0,
});

免费档注册。配你自己挑的读 RPC。用真实用户交易模式跑一周。

常见问题

用 wallet-adapter 还是自己写? 用 wallet-adapter。自己写钱包检测是个泥潭,你不会想跳进去。

该支持哪些钱包? 最少 Phantom、Solflare。加 Backpack 和 Glow 覆盖大多数用户。移动加 Mobile Wallet Adapter。

用户在 devnet 但 app 要 mainnet 怎么处理? 通过 connection 的 genesis hash 检测,让用户在他们钱包里切换。别想用编程方式切。

消息签名做 auth 的对的方式是什么? Wallet adapter 的 signMessage。后端生成 nonce、让用户签、后端验签名。

所有钱包都支持 signAllTransactions 吗? 大部分主流的支持;一些小钱包不。在你目标的钱包上测一下。

为什么交易显示"成功"但用户没拿到结果? 签名返回不等于上链。永远跟 connection 确认。发送后用 confirmTransaction

延伸阅读

返回博客列表