Skip to main content
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:
1

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.
2

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.
3

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, &0i64);
    }

    /// 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