This guide walks you through the fundamentals of writing smart contracts on Soroban using the Rust SDK.
Prerequisites
Before you begin, ensure you have:
Rust toolchain installed
Basic understanding of Rust programming
Soroban SDK added to your Cargo.toml
Basic Contract Structure
Every Soroban contract follows a standard structure using three key macros:
Define the contract struct
Use the #[contract] macro to mark your contract struct: use soroban_sdk :: {contract, contractimpl};
#[contract]
pub struct Contract ;
The contract struct doesn’t need any fields. It serves as a namespace for your contract functions.
Implement contract functions
Use the #[contractimpl] macro to implement your contract’s public functions: #[contractimpl]
impl Contract {
pub fn add ( a : u64 , b : u64 ) -> u64 {
a + b
}
}
All pub functions in a #[contractimpl] block become part of your contract’s public interface.
Add the no_std attribute
Soroban contracts must be no_std to compile to WebAssembly: #![no_std]
use soroban_sdk :: {contract, contractimpl};
#[contract]
pub struct Contract ;
#[contractimpl]
impl Contract {
pub fn add ( a : u64 , b : u64 ) -> u64 {
a + b
}
}
Working with Storage
Soroban provides three types of storage, each with different persistence characteristics:
Persistent Storage
For data that needs to persist long-term:
use soroban_sdk :: {contract, contractimpl, Env , Symbol };
#[contractimpl]
impl Contract {
pub fn put ( env : Env , key : Symbol , val : Symbol ) {
env . storage () . persistent () . set ( & key , & val )
}
pub fn get ( env : Env , key : Symbol ) -> Option < Symbol > {
env . storage () . persistent () . get ( & key )
}
pub fn del ( env : Env , key : Symbol ) {
env . storage () . persistent () . remove ( & key )
}
}
Temporary Storage
For data that only needs to exist for a short time:
pub fn set_temp ( env : Env , key : Symbol , value : i64 ) {
env . storage () . temporary () . set ( & key , & value );
}
Instance Storage
For data tied to the contract instance lifetime:
pub fn set_instance ( env : Env , key : Symbol , value : i64 ) {
env . storage () . instance () . set ( & key , & value );
}
Always choose the appropriate storage type based on your data’s lifetime requirements. Persistent storage is more expensive but provides longer retention.
Using Constructors
You can initialize contract state using the __constructor function:
use soroban_sdk :: {contract, contractimpl, contracttype, Env };
#[contracttype]
pub enum DataKey {
Persistent ( u32 ),
Temp ( u32 ),
Instance ( u32 ),
}
#[contractimpl]
impl Contract {
pub fn __constructor ( env : Env , init_key : u32 , init_value : i64 ) {
env . storage ()
. persistent ()
. set ( & DataKey :: Persistent ( init_key ), & init_value );
env . storage ()
. temporary ()
. set ( & DataKey :: Temp ( init_key * 2 ), & ( init_value * 2 ));
env . storage ()
. instance ()
. set ( & DataKey :: Instance ( init_key * 3 ), & ( init_value * 3 ));
}
}
The constructor is called once when the contract is deployed and must receive all required arguments.
Environment Parameter
The Env type provides access to the contract’s execution environment:
pub fn example ( env : Env ) {
// Access storage
let storage = env . storage ();
// Get ledger info
let ledger = env . ledger ();
// Publish events
let events = env . events ();
// Access crypto functions
let crypto = env . crypto ();
}
Supported Types
Soroban contracts support various types:
Primitive Types
u32, i32, u64, i64, u128, i128
bool
() (unit type)
SDK Types
Address - Contract and account addresses
Symbol - Short strings (up to 32 characters)
String - Arbitrary length strings
Bytes, BytesN - Binary data
Vec - Dynamic arrays
Map - Key-value mappings
Custom Types
Use #[contracttype] to define custom types (see Custom Types guide).
Complete Example
Here’s a complete contract that demonstrates key concepts:
#![no_std]
use soroban_sdk :: {contract, contractimpl, contracttype, Env , Symbol };
#[contract]
pub struct Contract ;
#[contracttype]
pub enum DataKey {
Counter ,
Owner ,
}
#[contractimpl]
impl Contract {
/// Initialize the contract with an owner
pub fn __constructor ( env : Env , owner : Symbol ) {
env . storage () . persistent () . set ( & DataKey :: Owner , & owner );
env . storage () . persistent () . set ( & DataKey :: Counter , & 0 i64 );
}
/// Increment the counter
pub fn increment ( env : Env ) -> i64 {
let mut counter : i64 = env . storage ()
. persistent ()
. get ( & DataKey :: Counter )
. unwrap_or ( 0 );
counter += 1 ;
env . storage () . persistent () . set ( & DataKey :: Counter , & counter );
counter
}
/// Get the current counter value
pub fn get_counter ( env : Env ) -> i64 {
env . storage ()
. persistent ()
. get ( & DataKey :: Counter )
. unwrap_or ( 0 )
}
}
Next Steps
Testing Contracts Learn how to write tests for your contracts
Custom Types Define custom data structures
Error Handling Handle errors gracefully in your contracts
Events Publish events from your contracts