针对 Solana 的类型安全 Rust ↔ TypeScript 通信

发布: (2025年12月18日 GMT+8 07:14)
5 min read
原文: Dev.to

Source: Dev.to

“Type‑Safe Rust ↔ TypeScript Communication for Solana”的封面图片

LUMOS 个人资料图片
Rector Sol 个人资料图片

使用链上 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。根据项目结构自行调整导入路径。

类型映射参考

LUMOSRustTypeScript
u8u64u8u64number
u128u128bigint
i8i64i8i64number
boolboolboolean
StringStringstring
PublicKeyPubkeyPublicKey

类型概览

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]

有问题吗?请在下方留言!

Back to Blog

相关文章

阅读更多 »