Soroban SDK & Rust - Writing Your First Smart Contract on the Stellar Blockchain

Soroban SDK & Rust - Writing Your First Smart Contract on the Stellar Blockchain

Hello! My name is Luiz, and in this article, I'll guide you through the basics of creating a smart contract on the Stellar blockchain using the Soroban SDK and Rust.

We'll build a simple Greeting Contract that interacts with users by storing and returning customized greeting messages. So, without further ado, let's get started!


Installing Stellar CLI and Setting Up the Development Environment

The best way to install stellar-cli on your computer is via cargo and Rust. Follow these steps to set up your environment:

  1. Install Rust by following the official documentation:
    • Rust Installation Guide
    • After installing Rust, add the wasm32-unknown-unknown target:
      rustup target add wasm32-unknown-unknown
      
  2. Install Stellar CLI by running:
    cargo install --locked stellar-cli --features opt
    
  3. Verify the installation:
    stellar --help
    
  4. Initialize a new smart contract project:
    stellar contract init greeting.soroban
    
  5. Bonus Tip: Create a rustfmt.toml file in the project root to define custom formatting:
    hard_tabs = true
    tab_spaces = 2
    
  6. Bonus Tip: Set up a Makefile to manage builds, tests, and formatting across multiple contracts:
    CONTRACT_DIRS := $(wildcard contracts/*)
    
    default: build
    
    all: test
    
    build:
      @set -e; \
      for dir in $(CONTRACT_DIRS); do \
        echo "Building contract in $$dir..."; \
        $(MAKE) -C $$dir build || exit 1; \
      done
      @ls -l target/wasm32-unknown-unknown/release/*.wasm
    
    test:
      @set -e; \
      for dir in $(CONTRACT_DIRS); do \
        echo "Building contract in $$dir..."; \
        $(MAKE) -C $$dir test || exit 1; \
      done
    

Coding the Greeting Contract

Our contract will have two functions:

  • say_hello: Returns a greeting message based on stored user data.
    • If user data exists, it returns the stored custom message.
    • Otherwise, it returns the default message: "Hello, Developer!"
  • customize_greeting: Allows users to set their custom greeting message.

1. Creating storage.rs

This file will manage the contract's storage, ensuring consistency and ease of access.

#![allow(unused)]
use soroban_sdk::{contracttype, symbol_short, Address, Env, IntoVal, String};

#[contracttype]
pub enum UserInfoRegistry {
    UserInfo(Address),
}

#[contracttype]
#[derive(Clone)]
pub struct UserInfo {
    pub address: Address,
    pub message: String,
}

pub struct StorageClient;

impl StorageClient {
    pub fn get_default_user_info(env: Env, user: Address) -> UserInfo {
        let message: String = "Hello, Developer!".into_val(&env);
        UserInfo { address: user.clone(), message }
    }
}

2. Exposing the Storage Module

In the main contract file, include mod storage; to access the StorageClient module.

#![no_std]
use soroban_sdk::{contract, contractimpl, vec, Env, String, Vec};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
    pub fn hello(env: Env, to: String) -> Vec<String> {
        vec![&env, String::from_str(&env, "Hello"), to]
    }
}

mod storage;
mod test;

3. Implementing say_hello

#[contractimpl]
  impl Contract {
    // ...
    pub fn say_hello(env: Env, to: Address) -> storage::UserInfo {
      let key = storage::UserInfoRegistry::UserInfo(to.clone()); // gets the key on the storage

      let user_record = env
        .storage()
        .instance()
        .get(&key)
        .unwrap_or(storage::StorageClient::get_default_user_info(env, to)); // gets the user record from the storage

      return user_record; // returns the user record
    }
    // ...
  }

4. Implementing customize_greeting

#[contractimpl]
impl Contract {
  // ...
  pub fn customize_greeting(env: Env, to: Address, message: String) -> storage::UserInfo {
    let key = storage::UserInfoRegistry::UserInfo(to.clone()); // gets the key on the storage

    let mut user_record =
      env
        .storage()
        .instance()
        .get(&key)
        .unwrap_or(storage::StorageClient::get_default_user_info(
          env.clone(),
          to,
        )); // gets the user record from the storage

    user_record.message = message; // modify the message

    env.storage().instance().set(&key, &user_record); // save message on storage

    return user_record; // returns the user record
  }
  // ...
}

5. Final Implementation

#![no_std]
use soroban_sdk::{contract, contractimpl, Address, Env, String};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
    pub fn say_hello(env: Env, to: Address) -> storage::UserInfo {
        let key = storage::UserInfoRegistry::UserInfo(to.clone()); // gets the key on the storage

        let user_record = env
            .storage()
            .instance()
            .get(&key)
            .unwrap_or(storage::StorageClient::get_default_user_info(env, to)); // gets the user record from the storage

        return user_record; // returns the user record
    }

    pub fn customize_greeting(env: Env, to: Address, message: String) -> storage::UserInfo {
        let key = storage::UserInfoRegistry::UserInfo(to.clone()); // gets the key on the storage

        let mut user_record =
            env
                .storage()
                .instance()
                .get(&key)
                .unwrap_or(storage::StorageClient::get_default_user_info(
                    env.clone(),
                    to,
                )); // gets the user record from the storage

        user_record.message = message;

        env.storage().instance().set(&key, &user_record);

        return user_record; // returns the user record
    }
}

mod storage;
mod test;

Writing Tests

In this section, we’ll explore how to write and run unit tests for Soroban smart contracts using Rust. Unit testing plays a crucial role in smart contract development by allowing developers to verify the correctness of individual contract functions in an isolated environment

1. Testing say_hello

#[test]
fn test_say_hello() {
    let env = Env::default();

    let contract_id = env.register(Contract, ()); // registers the contract
    let client = ContractClient::new(&env, &contract_id); // deploys/instantiates the contract

    let alice = Address::generate(&env); // generates a random address

    let greeting = client.say_hello(&alice); // calls the say_hello function

    let message = String::from_str(&env, "Hello Developer!"); // creates the default expected message

    // asserts that the values match
    assert_eq!(greeting.address, alice);
    assert_eq!(greeting.message, message);
}

2. Testing customize_greeting

#[test]
fn test_customize_greeting() {
    let env: Env = Env::default();

  let contract_id = env.register(Contract, ()); // registers the contract
  let client = ContractClient::new(&env, &contract_id); // deploys/instantiates the contract

  let alice = Address::generate(&env); // generates a random address

  let message = String::from_str(&env, "Hello Alice!"); // creates the customized expected message

  let greeting = client.customize_greeting(&alice, &message); // calls the customize_greeting function to change the message on storage

  let greeting_say_hello = client.say_hello(&alice); // calls the say_hello function to ensure that the message was changed

    // asserts that the values match
  assert_eq!(greeting.address, alice);
  assert_eq!(greeting.message, message);
  assert_eq!(greeting_say_hello.address, alice);
  assert_eq!(greeting_say_hello.message, message);
}

Run tests with:

make test

Deploying and Interacting with the Contract

Now that we have our smart contract ready and tested by unit tests, let's deploy it to the Stellar Testnet network using stellar-cli. This will allow us to interact with our contract in a real blockchain environment, simulating how it would work in production. We'll go through the process of setting up a testnet network connection, creating a test account, deploying our contract, and interacting with it step by step.

1. Adding Network to Stellar CLI

stellar network add --global testnet \
  --rpc-url https://soroban-testnet.stellar.org:443 \
  --network-passphrase "Test SDF Network ; September 2015"

2. Creating a Test Account

stellar keys generate --global alice --network testnet

3. Deploying the Contract

stellar contract deploy \
  --wasm target/wasm32-unknown-unknown/release/greeting_contract.wasm \
  --source alice \
  --network testnet

ℹ️ Simulating install transaction…
ℹ️ Signing transaction: feecf902b1eba34529ed340c9bf28cacd179c3c0e628ccdbb50414d1b5265a51
🌎 Submitting install transaction…
ℹ️ Using wasm hash 9be6a4a2a6c1c4794ce50d38ce69929d0f8d5eb58a9a4451b861c07d8f1dda25
ℹ️ Simulating deploy transaction…
ℹ️ Transaction hash is 38a2ee724c0e1c3ab7c90bdb732d777c21e3e8bd5332bc2aae422deabadbf756
🔗 https://stellar.expert/explorer/testnet/tx/38a2ee724c0e1c3ab7c90bdb732d777c21e3e8bd5332bc2aae422deabadbf756
ℹ️ Signing transaction: 38a2ee724c0e1c3ab7c90bdb732d777c21e3e8bd5332bc2aae422deabadbf756
🌎 Submitting deploy transaction…
🔗 https://stellar.expert/explorer/testnet/contract/CCSYEHWP6B2UZ7AIJKMV2EP5CFAPAEUE2EDJQN4AN5IUNKP5GHT5RW2X
 Deployed!
CCSYEHWP6B2UZ7AIJKMV2EP5CFAPAEUE2EDJQN4AN5IUNKP5GHT5RW2X

4. Invoking the Contract

stellar contract invoke \
  --id CCSYEHWP6B2UZ7AIJKMV2EP5CFAPAEUE2EDJQN4AN5IUNKP5GHT5RW2X \
  --source alice \
  --network testnet \
  -- say_hello \
  --to GBGOKKUZ4OMRT26PDCSBCVON2FZCGBNDHGXSMQT2AHAKR22GRR74U2B4

ℹ️ Simulation identified as read-only. Send by rerunning with `--send=yes`.
{"address":"GBGOKKUZ4OMRT26PDCSBCVON2FZCGBNDHGXSMQT2AHAKR22GRR74U2B4","message":"Hello Developer!"}

Conclusion

Congratulations! You've successfully written, tested, deployed, and invoked your first Soroban smart contract on the Stellar Blockchain using Rust. Now, you can explore more advanced contract functionalities and integrate them into real-world applications.

Happy coding! 🚀

Profile picture
Luiz Fernando - Senior Software Engineer

Thanks for reading!

I hope you enjoyed reading this article. If you have any questions or feedback, don't hesitate to reach out to me on my social media bellow. Have a great day!

Carousel imageCarousel imageCarousel imageCarousel imageCarousel image

Latest Insights

Luiz Fernando

Senior Software Engineer

Senior Software Engineer with a passion for creating elegant solutions to complex problems

Connect With Me

Contact Me

If you're interested in learning more about my work, have a question, or just want to say hi, please don't hesitate to reach out.

© 2026 Luiz Fernando. All rights reserved