| Crates.io | solzempic |
| lib.rs | solzempic |
| version | 0.1.1 |
| created_at | 2026-01-07 20:58:27.562927+00 |
| updated_at | 2026-01-07 21:24:20.380785+00 |
| description | Solzempic - Solana instruction framework |
| homepage | |
| repository | https://github.com/mgild/solzempic |
| max_upload_size | |
| id | 2029009 |
| size | 1,368,104 |
A lightweight, zero-overhead framework for building Solana programs with Pinocchio.
Solzempic provides the structure and safety of a framework without the bloat. It implements the Action pattern (Build, Validate, Execute) and provides type-safe account wrappers while maintaining full control over compute unit usage.
Anchor is excellent for getting started, but comes with significant overhead:
Vanilla Pinocchio gives maximum control, but requires writing boilerplate:
Solzempic provides just enough structure to eliminate boilerplate while maintaining zero overhead:
+------------------+---------------+------------------+
| Anchor | Solzempic | Vanilla Pinocchio|
+------------------+---------------+------------------+
| High abstraction | Right balance | No abstraction |
| Hidden costs | Explicit | Explicit |
| Magic macros | Thin macros | No macros |
| ~5000 CU/instr | ~100 CU/instr | ~100 CU/instr |
+------------------+---------------+------------------+
AccountRef<T>, AccountRefMut<T> with ownership validationSystemProgram, TokenProgram, Signer etc. with compile-time guarantees#[SolzempicInstruction] and #[derive(SolzempicDispatch)] for ergonomic dispatchno_std compatible: Works in constrained Solana runtime environment ┌─────────────────────────────────────────────────────────────┐
│ SOLZEMPIC FRAMEWORK │
└─────────────────────────────────────────────────────────────┘
│
┌────────────────────────────┼────────────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Framework │ │ Action │ │ Account │
│ Trait │ │ Pattern │ │ Wrappers │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ ┌───────┴───────┐ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ define_ │ │ build() │ │ validate() │ │ AccountRef │
│ framework! │ │ │ │ │ │ AccountRefMut│
│ │ │ Accounts + │ │ Invariants │ │ ShardRef- │
│ Creates: │ │ params from │ │ PDA checks │ │ Context │
│ - Solzempic │ │ raw bytes │ │ Ownership │ └──────────────┘
│ - AccountRef│ └──────────────┘ └──────────────┘ │
│ - AccountRef│ │ │ │
│ Mut │ └───────┬───────┘ │
└──────────────┘ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ execute() │◄───────────────────┘
│ │ │
└───────────────────►│ State changes│
│ CPI calls │
│ Token xfers │
└──────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ PROGRAM WRAPPERS │
├─────────────────┬─────────────────┬─────────────────┬──────────────┤
│ SystemProgram │ TokenProgram │ Signer │ Sysvars │
│ AtaProgram │ Mint │ Payer │ Clock │
│ AltProgram │ TokenAccount │ │ Rent │
│ Lut │ Vault │ │ SlotHashes │
└─────────────────┴─────────────────┴─────────────────┴──────────────┘
Add to your Cargo.toml:
[dependencies]
solzempic = { version = "0.1" }
pinocchio = { version = "0.7" }
bytemuck = { version = "1.14", features = ["derive"] }
In your program's lib.rs, use the #[SolzempicEntrypoint] attribute:
#![no_std]
use solzempic::SolzempicEntrypoint;
#[SolzempicEntrypoint("YourProgramId111111111111111111111111111")]
pub enum MyInstruction {
Initialize = 0,
Increment = 1,
}
This single attribute generates:
ID: Pubkey constant and id() -> &'static Pubkey functionAccountRef<'a, T>, AccountRefMut<'a, T>, ShardRefContext<'a, T> type aliases#[repr(u8)] on the enumTryFrom<u8> and dispatch methods#[solzempic::account(discriminator = 1)]
pub struct Counter {
pub discriminator: [u8; 8],
pub owner: Pubkey,
pub count: u64,
}
The #[account(discriminator = N)] macro automatically:
#[repr(C)], Pod, Zeroable derivesLoadable and Account traitsLEN, DISCRIMINATOR, and discriminator() methodUse the #[instruction] attribute on an impl block:
use solzempic::{instruction, Signer, ValidatedAccount};
use pinocchio::{AccountView, program_error::ProgramError, ProgramResult};
use solana_address::Address;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct IncrementParams {
pub amount: u64,
}
pub struct Increment<'a> {
pub counter: AccountRefMut<'a, Counter>,
pub owner: Signer<'a>,
}
#[instruction(IncrementParams)]
impl<'a> Increment<'a> {
fn build(accounts: &'a [AccountView], _params: &IncrementParams) -> Result<Self, ProgramError> {
if accounts.len() < 2 {
return Err(ProgramError::NotEnoughAccountKeys);
}
Ok(Self {
counter: AccountRefMut::load(&accounts[0])?,
owner: Signer::wrap(&accounts[1])?,
})
}
fn validate(&self, _program_id: &Address, _params: &IncrementParams) -> ProgramResult {
// Verify owner matches counter's owner
if self.owner.key() != &self.counter.get().owner {
return Err(ProgramError::IllegalOwner);
}
Ok(())
}
fn execute(&self, _program_id: &Address, params: &IncrementParams) -> ProgramResult {
self.counter.get_mut().count += params.amount;
Ok(())
}
}
The #[SolzempicEntrypoint] macro generates everything needed. Your process_instruction is simply:
fn process_instruction(
program_id: &Address,
accounts: &[AccountView],
data: &[u8],
) -> ProgramResult {
MyInstruction::process(program_id, accounts, data)
}
Every instruction follows the same three-phase pattern:
┌─────────────────────────────────────────────────────────────────────────┐
│ ACTION LIFECYCLE │
└─────────────────────────────────────────────────────────────────────────┘
│
┌───────────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ BUILD │ │ VALIDATE │ │ EXECUTE │
├───────────────────┤ ├───────────────────┤ ├───────────────────┤
│ • Deserialize │ │ • Check invariants│ │ • Modify state │
│ parameters │ │ • Verify PDAs │ │ • Transfer tokens │
│ • Load accounts │ ──► │ • Check ownership │ ──► │ • Create accounts │
│ • Wrap programs │ │ • Validate ranges │ │ • Emit events │
│ • Early validation│ │ • Business rules │ │ • CPI calls │
└───────────────────┘ └───────────────────┘ └───────────────────┘
│ │ │
│ FAIL FAST │ PURE CHECKS │ SIDE EFFECTS
│ (bad accounts = error) │ (no state changes) │ (point of no return)
▼ ▼ ▼
Phase 1: Build
parse_params)AccountRef::load, AccountRefMut::load)Signer::wrap, TokenProgram::wrap)Phase 2: Validate
Phase 3: Execute
AccountRef<T> - Read-Only Accesspub struct AccountRef<'a, T: Loadable, F: Framework> {
pub info: &'a AccountInfo,
data: &'a [u8],
// ...
}
impl<'a, T: Loadable, F: Framework> AccountRef<'a, T, F> {
/// Load with full validation (ownership + discriminator)
pub fn load(info: &'a AccountInfo) -> Result<Self, ProgramError>;
/// Load without ownership check (for cross-program reads)
pub fn load_unchecked(info: &'a AccountInfo) -> Result<Self, ProgramError>;
/// Get typed reference to account data
pub fn get(&self) -> &T;
/// Check if account is a PDA with given seeds
pub fn is_pda(&self, seeds: &[&[u8]]) -> (bool, u8);
}
AccountRefMut<T> - Read-Write Accessimpl<'a, T: Loadable, F: Framework> AccountRefMut<'a, T, F> {
/// Load with validation (ownership + discriminator + is_writable)
pub fn load(info: &'a AccountInfo) -> Result<Self, ProgramError>;
/// Get typed reference
pub fn get(&self) -> &T;
/// Get mutable typed reference
pub fn get_mut(&mut self) -> &mut T;
/// Reload after CPI (updates internal data pointer)
pub fn reload(&mut self);
}
impl<'a, T: Initializable, F: Framework> AccountRefMut<'a, T, F> {
/// Initialize a new account
pub fn init(info: &'a AccountInfo, params: T::InitParams) -> Result<Self, ProgramError>;
/// Initialize if uninitialized, otherwise load
pub fn init_if_needed(info: &'a AccountInfo, params: T::InitParams) -> Result<Self, ProgramError>;
/// Initialize a PDA account (create via CPI + initialize)
pub fn init_pda(
info: &'a AccountInfo,
payer: &AccountInfo,
system_program: &AccountInfo,
seeds: &[&[u8]],
space: usize,
params: T::InitParams,
) -> Result<Self, ProgramError>;
}
ShardRefContext<T> - Triplet NavigationFor sharded data structures that need access to prev/current/next:
pub struct ShardRefContext<'a, T: Loadable, F: Framework> {
pub prev: AccountRefMut<'a, T, F>,
pub current: AccountRefMut<'a, T, F>,
pub next: AccountRefMut<'a, T, F>,
}
impl<'a, T: Loadable, F: Framework> ShardRefContext<'a, T, F> {
pub fn new(prev: &'a AccountInfo, current: &'a AccountInfo, next: &'a AccountInfo) -> Result<Self, ProgramError>;
pub fn current_mut(&mut self) -> &mut T;
pub fn all_mut(&mut self) -> (&mut T, &mut T, &mut T);
}
All program and sysvar accounts validate their identity on construction:
// Programs
let system = SystemProgram::wrap(&accounts[0])?; // Validates key == 11111...
let token = TokenProgram::wrap(&accounts[1])?; // Validates SPL Token or Token-2022
let ata = AtaProgram::wrap(&accounts[2])?; // Validates ATA program
// Signers
let signer = Signer::wrap(&accounts[3])?; // Validates is_signer flag
let payer = Payer::wrap(&accounts[4])?; // Alias for Signer
// Sysvars
let clock = ClockSysvar::wrap(&accounts[5])?; // Validates Clock sysvar ID
let rent = RentSysvar::wrap(&accounts[6])?; // Validates Rent sysvar ID
// Token accounts
let mint = Mint::wrap(&accounts[7])?; // Validates token program ownership
let vault = Vault::wrap(&accounts[8], &authority)?; // Validates ownership + authority
let token_account = TokenAccountRefMut::load(&accounts[9])?;
Framework TraitThe Framework trait allows account wrappers to know your program's ID without passing it everywhere:
pub trait Framework {
const PROGRAM_ID: Pubkey;
}
// define_framework! generates:
pub struct Solzempic;
impl Framework for Solzempic {
const PROGRAM_ID: Pubkey = YOUR_PROGRAM_ID;
}
// Which allows:
AccountRefMut::<MyAccount>::load(&account)? // Automatically checks owner == YOUR_PROGRAM_ID
#[SolzempicEntrypoint("program_id")]The main entrypoint attribute that generates everything needed for your program:
#[SolzempicEntrypoint("Your11111111111111111111111111111111111111")]
pub enum MyInstruction {
Initialize = 0,
Transfer = 1,
Close = 2,
}
// Generates:
// - pub const ID: Address = ...
// - pub fn id() -> &'static Address
// - pub type AccountRef<'a, T> = ...
// - pub type AccountRefMut<'a, T> = ...
// - pub type ShardRefContext<'a, T> = ...
// - #[repr(u8)] on the enum
// - TryFrom<u8> for MyInstruction
// - MyInstruction::process() dispatch method
// - The program entrypoint
#[instruction(ParamsType)]Implements the Instruction trait on an impl block:
pub struct Transfer<'a> {
pub from: AccountRefMut<'a, TokenAccount>,
pub to: AccountRefMut<'a, TokenAccount>,
pub authority: Signer<'a>,
}
#[instruction(TransferParams)]
impl<'a> Transfer<'a> {
fn build(accounts: &'a [AccountView], params: &TransferParams) -> Result<Self, ProgramError> { ... }
fn validate(&self, program_id: &Address, params: &TransferParams) -> ProgramResult { ... }
fn execute(&self, program_id: &Address, params: &TransferParams) -> ProgramResult { ... }
}
// Generates InstructionParams and Instruction trait implementations
| Feature | Anchor | Solzempic |
|---|---|---|
| CU overhead | ~2000-5000 per instruction | ~100 (just your logic) |
| Binary size | Large (IDL, borsh) | Minimal |
| Account validation | Automatic, opaque | Explicit, transparent |
| Serialization | Borsh (copies data) | Zero-copy bytemuck |
| Learning curve | Low (magic) | Medium (explicit) |
| Debugging | Hard (generated code) | Easy (your code) |
| Flexibility | Constrained | Full control |
| Feature | Vanilla Pinocchio | Solzempic |
|---|---|---|
| Boilerplate | High (repeat validation) | Low (wrappers) |
| Structure | None (DIY) | Action pattern |
| Safety | Manual | Enforced by types |
| Program ID handling | Pass everywhere | Framework trait |
| Learning curve | High | Medium |
Solzempic adds no runtime overhead beyond what you'd write by hand:
All wrapper methods are #[inline] and compile away in release builds.
Typical instruction overhead:
| Macro | Purpose |
|---|---|
#[SolzempicEntrypoint("...")] |
Main entrypoint - generates ID, type aliases, dispatch, and entrypoint |
#[instruction(Params)] |
Implements Instruction trait on an impl block |
define_framework!(ID) |
Alternative: manually define framework type aliases |
define_account_types! { ... } |
Define account discriminator enum |
| Type | Purpose |
|---|---|
AccountRef<'a, T> |
Read-only typed account with ownership validation |
AccountRefMut<'a, T> |
Writable typed account with ownership + is_writable checks |
ShardRefContext<'a, T> |
Prev/current/next triplet for sharded data structures |
| Type | Validates |
|---|---|
SystemProgram |
Key == System Program |
TokenProgram |
Key == SPL Token or Token-2022 |
AtaProgram |
Key == Associated Token Program |
AltProgram |
Key == Address Lookup Table Program |
Lut |
Address Lookup Table account |
| Type | Validates |
|---|---|
Signer |
is_signer flag is true |
Payer |
Alias for Signer (semantic clarity) |
| Type | Purpose |
|---|---|
Mint |
SPL Token mint account |
Vault |
Token account with authority validation |
SolVault |
SOL vault (system-owned) |
TokenAccountRefMut |
Writable token account |
TokenAccountData |
Token account data struct |
| Type | Sysvar |
|---|---|
ClockSysvar |
Clock (slot, timestamp, epoch) |
RentSysvar |
Rent parameters |
SlotHashesSysvar |
Recent slot hashes |
InstructionsSysvar |
Current transaction instructions |
RecentBlockhashesSysvar |
Recent blockhashes |
| Trait | Purpose |
|---|---|
Instruction |
Three-phase pattern: build() → validate() → execute() |
InstructionParams |
Associates a params type with an instruction |
Framework |
Program-specific configuration (program ID) |
Loadable |
POD types with discriminator byte |
Initializable |
Types that can be initialized with params |
ValidatedAccount |
Common interface for validated wrappers |
| Function | Purpose |
|---|---|
create_pda_account() |
Create and initialize a PDA via CPI |
transfer_lamports() |
Transfer SOL between accounts |
rent_exempt_minimum() |
Calculate rent-exempt minimum for size |
parse_params::<T>() |
Zero-copy parameter parsing |
| Constant | Value |
|---|---|
SYSTEM_PROGRAM_ID |
System Program |
TOKEN_PROGRAM_ID |
SPL Token Program |
TOKEN_2022_PROGRAM_ID |
Token-2022 Program |
ASSOCIATED_TOKEN_PROGRAM_ID |
ATA Program |
ADDRESS_LOOKUP_TABLE_PROGRAM_ID |
ALT Program |
CLOCK_SYSVAR_ID |
Clock sysvar |
RENT_SYSVAR_ID |
Rent sysvar |
SLOT_HASHES_SYSVAR_ID |
SlotHashes sysvar |
INSTRUCTIONS_SYSVAR_ID |
Instructions sysvar |
RECENT_BLOCKHASHES_SYSVAR_ID |
RecentBlockhashes sysvar |
LAMPORTS_PER_BYTE |
Rent cost per byte |
MAX_ACCOUNT_SIZE |
Maximum account size (10MB) |
Solzempic uses Pinocchio's ProgramError throughout:
// Common errors returned by wrappers:
ProgramError::IllegalOwner // Account not owned by program
ProgramError::InvalidAccountData // Wrong discriminator or not writable
ProgramError::AccountAlreadyInitialized // init() on initialized account
ProgramError::IncorrectProgramId // Wrong program/sysvar ID
ProgramError::MissingRequiredSignature // Signer check failed
ProgramError::NotEnoughAccountKeys // Too few accounts passed
ProgramError::InvalidInstructionData // Params too short
Define your own errors by implementing Into<ProgramError>:
#[repr(u32)]
pub enum MyError {
InvalidPrice = 1000,
OrderNotFound = 1001,
}
impl From<MyError> for ProgramError {
fn from(e: MyError) -> Self {
ProgramError::Custom(e as u32)
}
}
Contributions are welcome! Please:
cargo clippy passesLicensed under the Apache License, Version 2.0. See LICENSE for details.
Built on top of Pinocchio, the minimal Solana program framework.