@anywalletkit/wallet-solana

中文文档
0.6.0发布于 2026/11/03Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 2751

Solana wallet for Any Wallet Kit: SLIP-10 m/44'/501'/i'/0' accounts, SOL and SPL transfers with ATA creation, noble ed25519 signing (no WebCrypto needed), built on @solana/kit.

原始使用文档
文档目录7 节
  1. Install
  2. What you sign, what it costs, and when the wallet refuses
  3. Token-2022
  4. Token info
  5. Versioned transactions and provider wires
  6. Broadcast outcome and timeouts
  7. Activity history

@anywalletkit/wallet-solana

npm

Solana accounts at m/44'/501'/i'/0' (SLIP-10 ed25519 — the Phantom / Solflare default), pinned by golden vectors in @anywalletkit/conformance.

Install

npm install @anywalletkit/wallet-solana
# or: pnpm add @anywalletkit/wallet-solana  ·  yarn add @anywalletkit/wallet-solana

On npm: @anywalletkit/wallet-solana, published from the release workflow with provenance.

Signing uses noble ed25519 over the compiled message bytes, so there is no WebCrypto dependency: the same code runs in Node and in React Native Hermes without the Ed25519 subtle polyfill.

import WalletManagerSolana from '@anywalletkit/wallet-solana'

const wallet = new WalletManagerSolana(seedPhrase, { provider: ['https://rpc-a', 'https://rpc-b'] })
const account = await wallet.getAccount(0)
await account.transfer({ recipient: 'So1…', amount: 10_000_000n })                 // SOL (lamports)
await account.transfer({ token: USDC_MINT, recipient: 'So1…', amount: 1_000_000n }) // SPL, creates the ATA if missing
await account.sendTransaction(transactionMessage)                                  // any @solana/kit message
wallet.dispose()

What you sign, what it costs, and when the wallet refuses

Every transfer / sendTransaction / signTransaction goes through one plan, so the network is consulted once per fact and a failing step throws before the key is used:

  1. Describe — account.describeTransaction(tx) (or describeTransaction(tx, {account}), pure/offline) decodes System, Token, Token-2022, Associated-Token and Compute-Budget instructions and reports kind (sol-transfer, spl-transfer, token2022-transfer, ata-create, program-call, mixed) plus warnings: unknown-program, sets-authority, closes-account, approves-delegate, unlimited-approve, foreign-fee-payer, missing-signers, unresolved-lookup-tables, burn-address, durable-nonce, and lifetime: blockhash, or durable-nonce when the first instruction is AdvanceNonceAccount (the signature never expires).
  2. Hard checks — fee payer must be this wallet (allowForeignFeePayer to override), a provider transaction must not need other signatures (MissingSignersError), transfers to the System-program address are refused (UnsafeTransactionError, allowBurn to override). Every sendTransaction / signTransaction (message, kit Transaction or base64) also refuses the drainer shapes: System Assign / Allocate / CreateAccountAllowPrefund (and the WithSeed forms) of this wallet's account or a CreateAccount of it, AuthorizeNonceAccount by this wallet, a token SetAuthority (or the metadata / group update-authority handoff), a token Batch, lamports moved to anyone but this wallet by an instruction that is not a visible transfer (CloseAccount, WithdrawExcessLamports, UnwrapLamports, WithdrawNonceAccount, TransferSolWithSeed), a Stake-program instruction this wallet signs that hands a stake account over (Authorize* to another key, a Withdraw to anyone else, a SetLockup* with a foreign custodian or a lockup), a stake account the wallet funds that the same transaction does not initialize (or split into) with this wallet as staker and withdrawer — an SPL Stake Pool WithdrawStake into it that names this wallet as the user stake authority counts (a pool's delayed exit) — an undecodable System / Token / Stake instruction (UnsafeTransactionError with code core-instruction, sets-authority, closes-account or moves-lamports; allowAuthorityChanges: true to override, e.g. for a token launch that renounces its mint authority). A durable-nonce transaction is refused (code durable-nonce) unless allowDurableNonce is set in the config or the call's options. transfer / quoteTransfer refuse a recipient that is not a wallet: a token account, a mint or a program (recipient-not-wallet); an address with no account yet is a wallet. The same checks run in signCompiled and signTransactionMessage. What a program the wallet signs for does by CPI is proved by the simulation (step 4). Swap-aggregator transactions belong on sendSerialized, which adds a program allowlist and a simulated balance / ownership proof and ignores that flag.
  3. Compute budget — by default the price is the 75th percentile of getRecentPrioritizationFees over the writable accounts (clamped by minPriorityMicroLamports / maxPriorityMicroLamports) and the unit limit is the simulated usage × 1.1. Set priorityFee: {microLamports, computeUnitLimit} for explicit values or priorityFee: false to add nothing; a budget the caller already put in the message is kept.
  4. Simulation — simulateTransaction (sigVerify: false) runs before signing; a failure throws SimulationError with the program logs. It also returns the post-state of the wallet, of its token accounts the transaction writes and of every written account the transaction may create (one that does not exist yet, or a bare System account; one extra read for the pre-state), and refuses what CPI would do there: the wallet's account handed to a program or given data (wallet-owner), a token account's owner or close authority changed, or set on a token account of this wallet that the transaction creates, this wallet's associated token account created for another owner (sets-authority), a delegate allowance no top-level Approve shows (approves-delegate); allowAuthorityChanges skips this too. simulate: false disables the gate (and the CPI proof with it).
  5. Fee quote — quoteSendTransaction / quoteTransfer return {fee, baseFee, priorityFee, microLamports, computeUnitLimit, tokenFee?}; transferMaxFee / transactionMaxFee compare against fee.
const q = await account.quoteTransfer({ token: MINT, recipient, amount: 10_000n })
q.priorityFee  // lamports added by the compute budget
q.tokenFee     // Token-2022 transfer fee withheld by the mint (0 for plain SPL)

Token-2022

transfer reads the mint once: owner program, decimals and (for Token-2022) the TransferFee extension in force this epoch. Transfers use transferChecked (mint and decimals verified on chain); fee-bearing mints use transferCheckedWithFee so a changed fee makes the transaction fail instead of silently taking more. The recipient's ATA is derived for the right program.

Token info

getTokenInfo(mint) reads a mint from the chain, for a token the user adds by address: { standard: 'spl' | 'spl-token-2022', decimals, symbol, name, totalSupply, flags }. Name and symbol come from the Token-2022 metadata extension, else the Metaplex metadata account ("" when there is neither); anyone can name a token "USDC", so only the address identifies it. flags lists what a holder should know: mint-authority, freeze-authority, and the Token-2022 extensions permanent-delegate (someone can move or burn any balance), transfer-fee (non-zero), transfer-hook, non-transferable, default-frozen, pausable. An address that is not an initialized Token / Token-2022 mint throws NotATokenError.

const pyusd = await account.getTokenInfo('2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo')
// { standard: 'spl-token-2022', decimals: 6, symbol: 'PYUSD', name: 'PayPal USD',
//   flags: ['mint-authority', 'freeze-authority', 'permanent-delegate'], … }

Versioned transactions and provider wires

  • transfer({..., lookupTables: [table]}) / sendTransaction({to, value, lookupTables}) fetch the tables and compress the v0 message.
  • A swap or DeFi provider's base64 wire transaction is accepted directly: signTransaction(base64) / sendTransaction(base64) decode it, run the hard checks, simulate, add this wallet's signature (others are preserved) and return / broadcast the wire. The message bytes are never altered.
  • sendSerialized(base64, guard) / checkSerialized(base64, guard) prove a provider-built wire before signing: the program allowlist, the hard checks, and a simulated balance proof against guard.expect (maxLamportsOut, tokenIn: {mint, minAmount, maxAmount?}, maxTokenOut). Every bound must be a non-negative bigint (invalid-expectation otherwise, before any RPC call); tokenIn.maxAmount refuses a wire that pays the wallet more than asked (token-in-excess). A module that knows the exact instructions its operation takes passes guard.instructions: every top-level instruction must match one rule — program, data prefix (data, hex), optional exact dataLength, and the accounts at given positions, compared after lookup tables are resolved — or the wire is refused (unexpected-instruction). A bridge out of Solana passes expect.bridgeOut: {destChain, recipient} (swap2 hands it to the account hook as bridgeOut). In a Mayan route (it runs one of Mayan's own programs, directly or through its CPI proxy) every top-level instruction, with the proxy unwrapped, must be one the guard follows (transfers to this wallet's own accounts or to a fee wallet, at most 3 % of what may leave; a Jupiter v2 route into the wallet or the bridge that takes no platform fee or positive-slippage share; Mayan's Swift / MCTP / Fast MCTP orders), and every Mayan order must pay recipient on destChain with a non-zero minimum, a referrer / Mayan fee rate of at most MAYAN_REFERRER_FEE_MAX_BPS (50) and fixed relayer fees of at most MAYAN_RELAYER_FEE_MAX_BPS (10 %) of its input. With the reviewed terms swap2 adds — destToken, minAmountOut + destDecimals, slippageBps; a Mayan order without destToken or minAmountOut is refused — an order's output token must be destToken and its own minimum (Swift's amountOutMin at Mayan's 8 decimals; for a plain MCTP / Fast MCTP USDC bridge, the bridged amount less its fees, with destToken the destination chain's native Circle USDC) at least minAmountOut less 1 bp of rounding — minAmountOut already is the minimum at the plan's slippage (bridge-recipient, bridge-token, bridge-min, bridge-payee, bridge-instruction, bridge-missing, bridge-fee otherwise). Other Solana bridges (Relay, LayerSwap, NEAR Intents, LI.FI Intents, …) carry their recipient in an off-chain order or in data the guard does not read: they are NOT recipient-checked, only held to the ceilings and the program allowlist, as without bridgeOut. serializedGuardFromExecution(expect, extra?) builds the guard from the swap2 engine's sendSerializedTransaction expectation with every field carried over (bridgeOut and its reviewed terms included) — map the hook with it, (tx, expect) => account.sendSerialized(tx, serializedGuardFromExecution(expect)), rather than by hand. Net balances alone let a deposit that also withdraws collateral and transfers it away pass; the rules do not.
  • getAccountInfos(addresses) reads raw accounts (owner, lamports, data) in the order asked, null where none exists, for modules that check a provider's figures against chain state.

Broadcast outcome and timeouts

A url provider gives up after timeoutMs (default 15 000) per request (AbortSignal.timeout, or a timer where the runtime lacks it); a read that times out is a NetworkError. A provider list fails over with the same signed bytes, never a new signature. A send the node refused (preflight failure, bad signature, an HTTP 4xx such as a rate limit) throws that error: nothing was forwarded, plan and sign again. Anything else — a timeout, a dropped connection, a 408 / 5xx, or a refusal after another provider went unanswered — throws BroadcastUnknownError (maybeSent: true, hash = the signature): the transaction may land, so do not sign it again; watch getTransactionReceipt(hash) until its blockhash expires.

Activity history

createSolanaHistory is the chain's HistorySource (@anywalletkit/core): newest-first ChainTx pages for an address over plain JSON-RPC, no indexer.

import { createSolanaHistory } from '@anywalletkit/wallet-solana/history'

const history = createSolanaHistory({ rpcUrls: ['https://api.mainnet-beta.solana.com'] })
const page = await history.listActivity('2tKRnQTAXmryVYxDNSajdZg1DfX2FbHKNboWr5EBiKhW', { limit: 25 })
const older = await history.listActivity(address, { cursor: page.cursor })
const one = await history.getTransaction(page.items[0].hash, address)
history.explorerTxUrl(one.hash) // https://solscan.io/tx/…
optiondefaultnotes
rpcUrl / rpcUrls—One or several JSON-RPC endpoints, tried in order per request (HTTP 429 / 5xx / network → next).
networkmainnetdevnet / testnet switch the CAIP-2 id and add ?cluster= to Solscan links.
rateLimit5 rpsRequests per second (a JSON-RPC batch counts as one).
batchSize10getTransaction calls per JSON-RPC batch (1–10).
maxTokenAccounts50Token accounts merged into the address's history.
fetch, timeoutMsglobal, 15 000AbortSignal.timeout when the runtime has it.

Endpoints. getTokenAccountsByOwner (Token + Token-2022, jsonParsed, cached per address) → getSignaturesForAddress for the wallet and each token account → getTransaction (jsonParsed, maxSupportedTransactionVersion: 0, confirmed) in batches of ≤ 10. A transaction the node pruned still yields a row (status from the signature list, no transfers, raw.tx === null).

Pages and cursor. Signatures of every list are merged by slot (newest first), deduplicated, and the page takes the top limit plus ties at the boundary slot, so each list's consumed rows form a prefix of what the node returned. The cursor is base64 JSON { v: 1, l: { <list address>: <before signature> | '' | null } }: one before signature per list, '' = not started, null = exhausted. The list set is frozen in the cursor; a token account created mid-pagination joins on the next fresh listing. Pagination is exact even when one transaction touches several lists.

Mapping. Native transfers come from preBalances/postBalances with the fee added back to the payer, token transfers from pre/postTokenBalances per mint attributed to the token accounts' owners (standard = spl / token2022 from programId, decimals from the balance), senders paired with receivers largest-first; a receiver without a sender is a mint (from = mint), a sender without a receiver a burn (to = mint). fee = meta.fee, status from meta.err, block = slot. call.programs lists every top-level instruction program id, then inner ones; call.to is the first program that is not plumbing (System, ComputeBudget, Memo, Token, Token-2022, Associated Token) and call.data that instruction's base58 data — what @anywalletkit/protocol-staking-spl-stake-pool-solana reads. A transaction made only of plumbing (a SOL or SPL transfer) has no call; to is then the recipient owner. nativeValue is the SOL the payer sent in a plain transfer, 0 for program calls. Symbols are not resolved here (no catalog dependency); the kit fills them.

Fixtures in test/fixtures/history/ were captured on 2026-10-14 from api.mainnet-beta.solana.com for the test wallet: signature pages for the wallet, its JitoSOL and USDC accounts, both token-account lists, the Jito DepositSol (3SmwE6qf…, the same transaction as the stake-pool module's activity fixture), a SOL send and receive, a USDC send and a Token-2022 → USDC aggregator swap. test/history.test.ts checks that the row this source builds for the Jito deposit is described as stake by the stake-pool module.

版本与发布记录

@anywalletkit/wallet-solana 共发布 14 个版本,首次发布于 2026/10/25;过去 12 个月发布了 14 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.0latest2026/11/035 天前
0.5.12026/10/308 天前
0.5.02026/10/309 天前
0.4.02026/10/2910 天前
0.3.22026/10/2810 天前
0.3.12026/10/2811 天前

关于 @anywalletkit/wallet-solana 的常见问题

@anywalletkit/wallet-solana 是什么?

Solana wallet for Any Wallet Kit: SLIP-10 m/44'/501'/i'/0' accounts, SOL and SPL transfers with ATA creation, noble ed25519 signing (no WebCrypto needed), built on @solana/kit.

如何安装 @anywalletkit/wallet-solana?

在项目目录运行 npm install @anywalletkit/wallet-solana,或使用 pnpm add / yarn add / bun add。

@anywalletkit/wallet-solana 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@anywalletkit/wallet-solana 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@anywalletkit/wallet-solana 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@anywalletkit/wallet-solana 可以商用吗?

@anywalletkit/wallet-solana 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@anywalletkit/wallet-solana 的下载量有多少?

根据 npm 官方下载统计,@anywalletkit/wallet-solana 在 2026/10/06 – 2026/11/04 这 30 天内被下载 2,751 次,日均约 92 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@anywalletkit/wallet-solana 还在维护吗?

最新版本 0.6.0 发布于 2026/11/03(5 天前);至今共发布 14 个版本,其中过去 12 个月发布了 14 个;首次发布于 2026/10/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。