☀️ Solana
← Target Reference | VM: SBPF
Solana is the high-performance blockchain powered by SBPF (Solana Berkeley
Packet Filter). Trident is designed to compile to SBPF bytecode (.so) and link against
os.solana.* for Solana-specific runtime bindings. Programs are stateless -- all state
lives in accounts passed into each transaction.
Runtime Parameters
| Parameter | Value |
|---|---|
| VM | SBPF |
| Runtime binding | os.solana.* |
| Account model | Stateless programs (state in passed accounts) |
| Storage model | Account-based |
| Transaction model | Signed (Ed25519) |
| Cost model | Compute units (200K default, 1.4M max) |
| Cross-chain | -- |
Programming Model
Entry Points
Solana programs have a single entry point: process instruction. Every transaction specifies which accounts to pass and what instruction data to send. The program receives accounts by index.
program my_token
use os.solana.account
use os.solana.log
// Single entry point -- dispatch on instruction data
fn main() {
let instruction: Field = pub_read()
match instruction {
0 => { initialize() }
1 => { transfer() }
2 => { balance_of() }
_ => { assert(false) }
}
}
Unlike Ethereum where functions are exported by selector, Solana programs receive raw instruction data and dispatch manually.
State Access
Programs are stateless -- they own no storage. All state lives in accounts that are passed into the transaction by the caller. Each account is a byte buffer with an owner, lamport balance, and data.
use os.solana.account
// Accounts are accessed by index (order in the transaction)
let owner: Field = os.solana.account.owner(0) // account 0's owner program
let lamports: Field = os.solana.account.lamports(0) // account 0's balance
let data: Field = os.solana.account.data(0, offset) // read from account 0's data
// Write to account data (program must own the account)
os.solana.account.write_data(0, offset, value)
// Account properties
let key: Digest = os.solana.account.key(0) // account 0's public key
let is_writable: Bool = os.solana.account.is_writable(0)
let is_signer: Bool = os.solana.account.is_signer(0)
The account model is fundamentally different from Ethereum:
- Ethereum: contract owns storage, callers invoke functions
- Solana: program is stateless, callers supply accounts containing data
Identity and Authorization
Signers are accounts that signed the transaction. The runtime verifies Ed25519 signatures before the program runs.
use os.solana.account
// Check if an account signed the transaction
assert(os.solana.account.is_signer(0))
// The signer's public key is their identity
let authority: Digest = os.solana.account.key(0)
Program Derived Addresses (PDAs) are deterministic addresses derived from a program ID and seeds. They allow programs to "sign" for accounts they control.
use os.solana.pda
// Find a PDA for this program
let (pda, bump): (Digest, Field) = os.solana.pda.find(
program_id,
[seed1, seed2] // seeds array
)
// Verify an account matches a PDA
let expected: Digest = os.solana.pda.create_address(
program_id,
[seed1, seed2, bump_seed]
)
assert_digest(os.solana.account.key(2), expected)
Value Transfer
SOL transfers move lamports between accounts:
use os.solana.transfer
// Transfer lamports from account 0 to account 1
// Account 0 must be a signer
os.solana.transfer.lamports(0, 1, amount)
SPL token transfers use CPI to the Token Program (see below).
Cross-Contract Interaction
Cross-Program Invocation (CPI) lets programs call other programs:
use os.solana.cpi
// Invoke another program
os.solana.cpi.invoke(
program_id, // target program
accounts, // account metas (index, is_signer, is_writable)
instruction_data // raw instruction bytes
)
// Invoke with PDA signer (program signs for its PDA)
os.solana.cpi.invoke_signed(
program_id,
accounts,
instruction_data,
signer_seeds // PDA derivation seeds
)
Common CPI targets:
- System Program -- create accounts, transfer SOL
- Token Program -- SPL token operations
- Associated Token Account -- canonical token account derivation
Events
Solana uses program logs and structured event data:
event Transfer { from: Digest, to: Digest, amount: Field }
// reveal compiles to sol_log_data (structured event)
reveal Transfer { from: sender, to: receiver, amount: value }
reveal maps to sol_log_data for indexed event consumption.
seal emits only the commitment hash as log data.
use os.solana.log
// Raw log message
os.solana.log.msg("transfer complete")
// Raw structured data
os.solana.log.data(bytes)
Portable Alternative (os.*)
Programs that don't need Solana-specific features can use os.*
instead of os.solana.* for cross-chain portability:
os.solana.* (this OS only) |
os.* (any OS) |
|---|---|
os.solana.account.data(idx, off) |
os.state.read(key) → account data read |
os.solana.account.key(0) + is_signer |
os.neuron.id() → first signer key |
os.solana.transfer.lamports(from, to, amt) |
os.signal.send(from, to, amt) → system transfer |
os.solana.clock.unix_timestamp() |
os.time.now() → Clock sysvar |
Use os.solana.* when you need: PDAs, CPI, specific account indices,
rent exemption checks, or other Solana-specific features. See
os.md for the full os.* API.
Ecosystem Mapping
| Solana/Anchor concept | Trident equivalent |
|---|---|
declare_id!("...") |
Program identified by compiled bytecode hash |
#[program] mod my_program |
program my_token with use os.solana.* |
pub fn initialize(ctx: Context<Init>) |
fn initialize() with account index access |
ctx.accounts.authority |
os.solana.account.key(N) (account by index) |
#[account] struct MyAccount |
Account data at os.solana.account.data(N, offset) |
Account<'info, Mint> |
os.solana.account.data(N, offset) (manual deserialization) |
Signer<'info> |
assert(os.solana.account.is_signer(N)) |
has_one = authority |
assert(os.solana.account.key(N) == expected) |
#[account(mut)] |
assert(os.solana.account.is_writable(N)) |
system_program::transfer |
os.solana.transfer.lamports(from, to, amount) |
CpiContext::new(...) + invoke |
os.solana.cpi.invoke(program, accounts, data) |
invoke_signed |
os.solana.cpi.invoke_signed(program, accounts, data, seeds) |
Pubkey::find_program_address(seeds) |
os.solana.pda.find(program_id, seeds) |
msg!("log message") |
os.solana.log.msg("log message") |
emit!(MyEvent { ... }) |
reveal MyEvent { ... } |
Clock::get()?.unix_timestamp |
os.solana.clock.unix_timestamp() |
Clock::get()?.slot |
os.solana.clock.slot() |
Clock::get()?.epoch |
os.solana.clock.epoch() |
Rent::get()?.minimum_balance(size) |
os.solana.rent.minimum_balance(size) |
require!(condition, ErrorCode) |
assert(condition) |
os.solana.* API Reference
| Module | Function | Signature | Description |
|---|---|---|---|
| account | key(index) |
U32 -> Digest |
Account public key |
owner(index) |
U32 -> Field |
Account owner program | |
lamports(index) |
U32 -> Field |
Account lamport balance | |
data(index, offset) |
(U32, U32) -> Field |
Read from account data | |
write_data(index, offset, val) |
(U32, U32, Field) -> () |
Write to account data | |
data_len(index) |
U32 -> U32 |
Account data length | |
is_signer(index) |
U32 -> Bool |
Was this account a signer? | |
is_writable(index) |
U32 -> Bool |
Is this account writable? | |
| pda | find(program, seeds) |
(Field, [Field]) -> (Digest, Field) |
Find PDA + bump |
create_address(program, seeds) |
(Field, [Field]) -> Digest |
Derive PDA address | |
| cpi | invoke(program, accounts, data) |
(Field, [...], [Field]) -> () |
Cross-program invocation |
invoke_signed(program, accounts, data, seeds) |
(Field, [...], [Field], Field) -> () |
CPI with PDA signer | |
| transfer | lamports(from, to, amount) |
(U32, U32, Field) -> () |
SOL transfer |
| system | create_account(from, new, lamports, space, owner) |
(...) -> () |
Create new account |
allocate(account, space) |
(U32, U32) -> () |
Allocate account space | |
assign(account, owner) |
(U32, Field) -> () |
Assign account owner | |
| log | msg(text) |
[Field] -> () |
Log message |
data(bytes) |
[Field] -> () |
Log structured data | |
| clock | slot() |
-> Field |
Current slot |
epoch() |
-> Field |
Current epoch | |
unix_timestamp() |
-> Field |
Unix timestamp | |
| rent | minimum_balance(size) |
U32 -> Field |
Rent-exempt minimum |
Notes
Solana's stateless account model is fundamentally different from Ethereum's contract storage. Programs don't own state -- accounts do. The caller must supply every account the program will read or write, and the runtime validates account ownership and signer status before execution begins.
Compute unit budget: 200K default per instruction, 1.4M max per transaction.
The compiler reports cost in compute units via --costs.
For VM details, see sbpf.md. For mental model migration from Anchor/Rust, see For Onchain Devs.