针对 Solana 的类型安全 Rust ↔ TypeScript 通信
发布: (2025年12月18日 GMT+8 07:14)
5 min read
原文: Dev.to
Source: Dev.to

使用链上 Rust 与链下 TypeScript 构建 Solana dApp?
以下是确保类型完美同步的方法。
问题
你在 Rust 中定义了一个结构体:
#[account]
pub struct PlayerAccount {
pub wallet: Pubkey,
pub level: u16,
pub experience: u64,
}
然后在 TypeScript 中手动重建它:
export interface PlayerAccount {
wallet: PublicKey;
level: number;
experience: number;
}
可能出现的问题是什么?
- 字段顺序不匹配 → 反序列化失败
- 类型大小差异 → 数据损坏
- 忘记更新 → 运行时错误
解决方案:LUMOS
一次定义,生成两者:
// schema.lumos
#[solana]
#[account]
struct PlayerAccount {
wallet: PublicKey,
level: u16,
experience: u64,
inventory: [String],
last_active: i64,
}
生成绑定:
lumos generate schema.lumos
完整工作流
flowchart LR
A[schema.lumos] --> B[lumos generate]
B --> C[generated.rs / generated.ts]
C --> D[Rust Program (Anchor/Borsh)]
C --> E[Solana RPC (On‑chain)]
C --> F[TypeScript Client]
D <--> E
E <--> F
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ schema.lumos │────▶│ lumos generate │────▶│ generated.rs/.ts │
└─────────────────┘ └──────────────────┘ └─────────────────────┘
│
┌─────────────────────────────────┼─────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Rust Program │◀────────────▶│ Solana RPC │◀────────────▶│ TypeScript │
│ (Anchor/Borsh) │ │ (On‑chain) │ │ Client │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Rust 程序使用
use anchor_lang::prelude::*;
mod generated;
use generated::PlayerAccount;
/// The program entry point.
#[program]
pub mod game_program {
use super::*;
/// Initializes a new player account.
pub fn initialize(ctx: Context<Initialize>, wallet: Pubkey) -> Result<()> {
let player = &mut ctx.accounts.player;
player.wallet = wallet;
player.level = 1;
player.experience = 0;
player.inventory = Vec::new();
player.last_active = Clock::get()?.unix_timestamp;
Ok(())
}
/// Adds experience points to a player.
pub fn add_experience(ctx: Context<AddExperience>, amount: u64) -> Result<()> {
let player = &mut ctx.accounts.player;
player.experience = player
.experience
.checked_add(amount)
.expect("Experience overflow");
player.last_active = Clock::get()?.unix_timestamp;
Ok(())
}
}
/// Context for the `initialize` instruction.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + PlayerAccount::LEN)]
pub player: Account<'info, PlayerAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
/// Context for the `add_experience` instruction.
#[derive(Accounts)]
pub struct AddExperience<'info> {
#[account(mut, has_one = wallet)]
pub player: Account<'info, PlayerAccount>,
pub signer: Signer<'info>,
}
Source: …
TypeScript 客户端使用
下面列出了三种在 TypeScript 客户端与 Solana 程序交互的常见模式:
1. 获取账户数据
import { Connection, PublicKey } from '@solana/web3.js';
import { PlayerAccount, PlayerAccountBorshSchema } from './generated';
/**
* 从区块链检索 `PlayerAccount`。
*
* @param connection – 活动的 Solana `Connection`。
* @param playerPubkey – 玩家账户的公钥。
* @returns 反序列化后的 `PlayerAccount`。
* @throws 如果账户不存在则抛出错误。
*/
export async function getPlayerAccount(
connection: Connection,
playerPubkey: PublicKey
): Promise<PlayerAccount> {
const accountInfo = await connection.getAccountInfo(playerPubkey);
if (!accountInfo) {
throw new Error('Player account not found');
}
// Anchor 生成的账户前面会有 8 字节的 discriminator。
const data = accountInfo.data.slice(8);
return PlayerAccountBorshSchema.decode(data);
}
2. 订阅更新
import { Connection, PublicKey } from '@solana/web3.js';
import { PlayerAccount, PlayerAccountBorshSchema } from './generated';
/**
* 注册一个监听器,当玩家账户发生变化时触发。
*
* @param connection – 活动的 Solana `Connection`。
* @param playerPubkey – 玩家账户的公钥。
* @param callback – 在账户更新时调用,参数为更新后的 `PlayerAccount`。
* @returns 订阅 ID(可用于稍后通过 `connection.removeAccountChangeListener` 移除)。
*/
export function subscribeToPlayer(
connection: Connection,
playerPubkey: PublicKey,
callback: (player: PlayerAccount) => void
): number {
return connection.onAccountChange(
playerPubkey,
(accountInfo) => {
const data = accountInfo.data.slice(8);
const player = PlayerAccountBorshSchema.decode(data);
callback(player);
},
'confirmed'
);
}
/* 示例用法 */
const subscriptionId = subscribeToPlayer(connection, playerPubkey, (player) => {
console.log(`Level: ${player.level}, XP: ${player.experience}`);
});
3. 构建交易
import * as anchor from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';
/**
* 调用程序中的 `addExperience` 指令。
*
* @param program – Anchor `Program` 实例。
* @param playerPubkey – 要更新的玩家账户。
* @param amount – 要添加的经验值数量。
* @returns 交易签名。
*/
export async function addExperience(
program: anchor.Program,
playerPubkey: PublicKey,
amount: number
): Promise<string> {
const txSignature = await program.methods
.addExperience(new anchor.BN(amount))
.accounts({ player: playerPubkey })
.rpc();
console.log(`Added ${amount} XP. TX: ${txSignature}`);
return txSignature;
}
这些代码片段假设你已经生成了 Borsh schema(PlayerAccountBorshSchema)以及程序的 Anchor IDL。根据项目结构自行调整导入路径。
类型映射参考
| LUMOS | Rust | TypeScript |
|---|---|---|
u8‑u64 | u8‑u64 | number |
u128 | u128 | bigint |
i8‑i64 | i8‑i64 | number |
bool | bool | boolean |
String | String | string |
PublicKey | Pubkey | PublicKey |
类型概览
icKey
Pubkey
PublicKey
[T]
Vec
T[]
Option
Option
T \
优势
- ✅ 单一真实来源 – 只需定义一次类型。
- ✅ 同步保证 – Rust 与 TypeScript 始终保持一致。
- ✅ 正确的 Borsh – 字段顺序和大小得到保证。
- ✅ 零运行时开销 – 生成的代码与手写代码完全相同。
- ✅ IDE 支持 – 完整的 TypeScript 自动完成。
入门
cargo install lumos-cli
lumos generate schema.lumos
文档: [Link to docs]
GitHub: [Repository URL]
有问题吗?请在下方留言!

