
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!
The best way to install stellar-cli on your computer is via cargo and Rust. Follow these steps to set up your environment:
wasm32-unknown-unknown target:
rustup target add wasm32-unknown-unknown
cargo install --locked stellar-cli --features opt
stellar --help
stellar contract init greeting.soroban
rustfmt.toml file in the project root to define custom formatting:hard_tabs = true
tab_spaces = 2
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
Our contract will have two functions:
say_hello: Returns a greeting message based on stored user data.
customize_greeting: Allows users to set their custom greeting message.storage.rsThis 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 }
}
}
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;
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
}
// ...
}
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
}
// ...
}
#![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;
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
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);
}
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
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.
stellar network add --global testnet \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015"
stellar keys generate --global alice --network testnet
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
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!"}
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! 🚀

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!






Hello, I'm Luiz, a Software Engineer from São Paulo, Brazil. In this article, I will share some best practices for frontend development that can help...

Hello, I'm Luiz, a Software Engineer from São Paulo, Brazil. In this article, we will explore the concept of Error Boundaries in React, how they work,...

Learn how to safely reclaim tons of disk space on your Mac without touching system files. This guide covers everything from clearing hidden caches, lo...

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

Hello, my name is Luiz and in this tutorial i'll teach you how to add a new layer of protection on the mobile side that will defend your app from Man...

Hello, I'm Luiz, a 26-year-old Software Engineer from São Paulo, Brazil. Today, I want to share some essential tips for debugging Android apps using R...

Hello this will be a quite different article where i share my notes while studying some topic/framework/language/technology in this section we will co...

Hello my name is Luiz and i'm a software engineer based in Brazil and today i'm going to explain how does react-relay works with Suspense from react,...

In this article i'll show you how to setup a static files CDN under Gitlab Organization using Gitlab Pages

In this article i'll talk about some tips to improove your gnome linux appearence to look like mine

Hello, I'm Luiz, a Software Engineer from São Paulo, Brazil. In this article, I will share some best practices for frontend development that can help...

Hello, I'm Luiz, a Software Engineer from São Paulo, Brazil. In this article, we will explore the concept of Error Boundaries in React, how they work,...

Learn how to safely reclaim tons of disk space on your Mac without touching system files. This guide covers everything from clearing hidden caches, lo...

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

Hello, my name is Luiz and in this tutorial i'll teach you how to add a new layer of protection on the mobile side that will defend your app from Man...

Hello, I'm Luiz, a 26-year-old Software Engineer from São Paulo, Brazil. Today, I want to share some essential tips for debugging Android apps using R...

Hello this will be a quite different article where i share my notes while studying some topic/framework/language/technology in this section we will co...

Hello my name is Luiz and i'm a software engineer based in Brazil and today i'm going to explain how does react-relay works with Suspense from react,...

In this article i'll show you how to setup a static files CDN under Gitlab Organization using Gitlab Pages

In this article i'll talk about some tips to improove your gnome linux appearence to look like mine