Building a Solana dApp with both on-chain Rust and off-chain TypeScript? Here's how to keep your types perfectly synchronized.
The Problem
You define a struct in Rust:
#[account]
pub struct PlayerAccount {
pub wallet: Pubkey,
pub level: u16,
pub experience: u64,
}
Then manually recreate it in TypeScript:
export interface PlayerAccount {
wallet: PublicKey;
level: number;
experience: number;
}
What could go wrong?
- Field order mismatch → deserialization fails
- Type size differences → corrupted data
- Forgotten updates → runtime errors
The Solution: LUMOS
Define once, generate both:
// schema.lumos
#[solana]
#[account]
struct PlayerAccount {
wallet: PublicKey,
level: u16,
experience: u64,
inventory: [String],
last_active: i64,
}
Generate:
lumos generate schema.lumos
Complete Workflow
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ schema.lumos │────▶│ lumos generate │────▶│ generated.rs/.ts │
└─────────────────┘ └──────────────────┘ └─────────────────────┘
│
┌─────────────────────────────────┼─────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Rust Program │◀────────────▶│ Solana RPC │◀────────────▶│ TypeScript │
│ (Anchor/Borsh) │ │ (On-chain) │ │ Client │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Rust Program Usage
use anchor_lang::prelude::*;
mod generated;
use generated::PlayerAccount;
#[program]
pub mod game_program {
use super::*;
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(())
}
pub fn add_experience(ctx: Context<UpdatePlayer>, amount: u64) -> Result<()> {
let player = &mut ctx.accounts.player;
player.experience = player.experience.checked_add(amount).unwrap();
player.last_active = Clock::get()?.unix_timestamp;
Ok(())
}
}
TypeScript Client Usage
Fetch Account Data
import { Connection, PublicKey } from '@solana/web3.js';
import { PlayerAccount, PlayerAccountBorshSchema } from './generated';
async function getPlayerAccount(
connection: Connection,
playerPubkey: PublicKey
): Promise<PlayerAccount> {
const accountInfo = await connection.getAccountInfo(playerPubkey);
if (!accountInfo) {
throw new Error('Player account not found');
}
// Skip 8-byte discriminator for Anchor accounts
const data = accountInfo.data.slice(8);
return PlayerAccountBorshSchema.decode(data);
}
Subscribe to Updates
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'
);
}
// Usage
const subscriptionId = subscribeToPlayer(connection, playerPubkey, (player) => {
console.log(`Level: ${player.level}, XP: ${player.experience}`);
});
Build Transactions
import * as anchor from '@coral-xyz/anchor';
async function addExperience(
program: anchor.Program,
playerPubkey: PublicKey,
amount: number
): Promise<string> {
const tx = await program.methods
.addExperience(new anchor.BN(amount))
.accounts({ player: playerPubkey })
.rpc();
console.log(`Added ${amount} XP. TX: ${tx}`);
return tx;
}
Type Mapping Reference
| 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 |
[T] |
Vec<T> |
T[] |
Option<T> |
Option<T> |
`T \ |
Benefits
✅ Single Source of Truth - Define types once
✅ Guaranteed Sync - Rust and TypeScript always match
✅ Correct Borsh - Field order and sizes guaranteed
✅ Zero Runtime Overhead - Generated code is identical to manual
✅ IDE Support - Full TypeScript autocomplete
Get Started
{% raw %}
cargo install lumos-cli
lumos generate schema.lumos
- Documentation: https://docs.lumos-lang.org/guides/client-interaction/
- GitHub: https://github.com/getlumos/lumos
Questions? Drop them below!
Top comments (0)