Home>Articles>Santa’s Christmas wishlist deployed on Stellar blockchain with Soroban | Part 1
Published :6 December 2025
blockchain

Santa’s Christmas wishlist deployed on Stellar blockchain with Soroban | Part 1

instagram

Santa’s Christmas wishlist deployed on Stellar blockchain with Soroban | Part 1

Press enter or click to view image in full size

Santa has got into some trouble this year, kids’ wishlists have been compromised and now he has to make sure the wishlists are not sent to him from the unknown source of truth. We are going to design a Stellar smart contract with Soroban to empower the Santa process of collecting the wishlists from kids.

If thinking about the implementation, kids usually change their minds with the gifts they want, they have a ttl by default. The data they provide becomes invalid after a while. Stellar has this feature implemented by default and we will see in a second how this can be played to take advantage of it. The concepts you will learn in this article are — storage, events, auth and ttl.

First of all make sure you have Rust installed, if not you can install it from here

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

and Stellar CLI, please follow this instruction to install it https://developers.stellar.org/docs/tools/cli/install-cli

Let’s start

  1. Generate stellar accounts on testnet and initiate the project
stellar keys generate santa
stellar keys generate kid1
stellar keys generate kid2
stellar keys list #to see the keys

stellar contract init ./santa --name santa_whishlist

One wishlist item would look like this

#![no_std]

use soroban_sdk::{
contract, contractimpl, contracttype, symbol_short, Address, Env, String, Vec,
};

#[derive(Clone)]
#[contracttype]
pub struct Wish {
pub id: u32,
pub text: String,
pub created_at_ledger: u32,
pub fulfilled: bool,
}

In order to be able to access our storage we need a DataKey enum

#[derive(Clone)]
#[contracttype]
pub enum DataKey {
/// Persistent: Stores the vector of wishes for a given owner.
Wishes(Address),
/// Persistent: Stores the next auto-increment ID for a user.
NextId(Address),
/// Instance: Stores the Address of the Contract Admin.
Admin,
}

Events we have events

#[contractevent]
pub struct WishAddedEvent {
pub user: Address,
pub add: u32,
}

#[contractevent]
pub struct WishFulfilledEvent {
pub user: Address,
pub wish_id: u32,
}

And errors

#[contracterror]
pub enum ContractError
WishNotFound = 1,
TooLateToChange = 2
}

Storage Strategy: Instance vs. Persistent

You might notice we are using two different storage strategies in our DataKey: Instance and Persistent.

In Soroban, Instance storage is tied to the contract itself. It is best for “Global” state — data that applies to the application as a whole. In our case, the Admin key (Santa’s address) is stored here because there is only one Santa for this contract.

Persistent storage is tied to the combination of the Contract ID and a specific key (like a user’s Address). This is perfect for the Wishes and NextId. Why? Because Kid1’s wishlist belongs to Kid1. If Kid1 stops believing in Santa (and stops interacting with the contract), we want her data to eventually expire (TTL) independently of Kid2’s data. This prevents state bloat on the blockchain.

Now, let’s look at the contract implementation.

The Contract Logic

We need to implement the SeasonaWishlist. We will use a __constructor to ensure that when the contract is deployed, we immediately set who "Santa" (the admin) is. We will also use require_auth to ensure that only Kid1 can write to Kid1's list, but only Santa can mark items as fulfilled.

Join Medium for free to get updates from this writer.

Add the following logic to your lib.rs:

// ... (imports and structs from above)

#[contract]
pub struct SeasonalWishlist;

// Helpers to manage TTL (Time To Live)
// We bump the lifespan of data every time it is accessed.
fn bump_persistent_ttl(env: &Env, key: &DataKey) {
env.storage().persistent().extend_ttl(key, 2_000, 5_000); // If < 2000 ledgers, bump to 5000
}

fn bump_instance_ttl(env: &Env) {
env.storage().instance().extend_ttl(2_000, 5_000);
}

fn fail(env: &Env, e: ContractError) -> ! {
panic_with_error!(env, e);
}

fn ensure_not_christmas(env: &Env) {
// Get the current time from the blockchain ledger
let current_time = env.ledger().timestamp();
// The deadline timestamp (Unix seconds).
// Example: Dec 25, 2025 00:00:00 UTC
let christmas_deadline = env.storage().instance().get::<_, u64>(&DataKey::ChristmasDeadline).unwrap_or(1_766_620_800);
if current_time >= christmas_deadline {
// It is Christmas (or later), we cannot accept changes!
fail(&env, ContractError::TooLateToChange);
}
}

#[contractimpl]
impl SeasonalWishlist {
/// The Constructor: Runs once on deployment.
/// Sets the "Santa" (Admin) and christmas deadline of the contract.
pub fn __constructor(env: Env, admin: Address, christmas_deadline: u64) {
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::ChristmasDeadline, &christmas_deadline);
}

pub fn set_christmas_deadline(env: &Env, christmas_deadline: u64) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("Santa missing");
admin.require_auth();

env.storage().instance().set(&DataKey::ChristmasDeadline, &christmas_deadline);
}

pub fn add_wish(env: Env, user: Address, text: String) -> u32 {
ensure_not_christmas(&env);
// AUTH: Ensure the transaction signer is actually the user
user.require_auth();

// 1. Generate ID
let id_key = DataKey::NextId(user.clone());
let mut next_id: u32 = env.storage().persistent().get(&id_key).unwrap_or(1);
let current_id = next_id;
next_id += 1;
env.storage().persistent().set(&id_key, &next_id);

// 2. Load existing wishes
let wish_key = DataKey::Wishes(user.clone());
let mut wishes: Vec<Wish> = env.storage().persistent().get(&wish_key).unwrap_or_else(|| Vec::new(&env));

// 3. Add new wish
wishes.push_back(Wish {
id: current_id,
text,
created_at_ledger: env.ledger().sequence(),
fulfilled: false,
});

// 4. Save and Bump TTL
env.storage().persistent().set(&wish_key, &wishes);
bump_persistent_ttl(&env, &wish_key);
bump_instance_ttl(&env);

// EVENT: Emit an event so indexers know a wish was added
WishAddedEvent {
user,
add: current_id
}.publish(&env);

current_id
}

pub fn mark_fulfilled(env: Env, user: Address, wish_id: u32) {
// AUTH: Get the admin address and require THEIR signature
let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("Santa missing");
admin.require_auth();

let wish_key = DataKey::Wishes(user.clone());
let mut wishes: Vec<Wish> = env.storage().persistent().get(&wish_key).unwrap_or_else(|| Vec::new(&env));

// Iterate to find the wish and update it
let mut found = false;
for i in 0..wishes.len() {
let mut wish = wishes.get(i).unwrap();
if wish.id == wish_id {
wish.fulfilled = true;
wishes.set(i, wish);
found = true;
break;
}
}

if !found { fail(&env, ContractError::WishNotFound); }

env.storage().persistent().set(&wish_key, &wishes);
bump_persistent_ttl(&env, &wish_key);

WishFulfilledEvent {
user,
wish_id
}.publish(&env);
}

/// View function to see a user's list
pub fn get_list(env: Env, user: Address) -> Vec<Wish> {
let key = DataKey::Wishes(user.clone());
let wishes = env.storage().persistent().get(&key).unwrap_or_else(|| Vec::new(&env));
// Even reading data requires bumping TTL to keep it alive!
bump_persistent_ttl(&env, &key);
wishes
}
}

Building and Deploying

Now that our code is written, we need to compile it to WASM and deploy it to the testnet.

  1. Build the contract:
stellar contract build

2. Deploy the contract: Here is the tricky part. Because we added a __constructor, we must initialize the contract with the Admin argument (Santa's address) immediately upon deployment. This is the same as having a set_config() function that is invoked after you deploy the contract.

stellar contract deploy \
--wasm target/wasm32v1-none/release/santa_whishlist.wasm \
--source-account santa \
--network testnet \
--alias santas_wishlist \
-- \
--admin santa \ #our admin constructor arg
--christmas-deadline 1766620800 #constructor arg

You will get a contract id and for the simplicity i will create an env var named WISHLIST_ID

Interaction: The Christmas Flow

Now the fun begins. Let’s roleplay the interaction.

  1. Kid1 makes a wish. Kid1 calls the contract. The kid must sign it (--source kid1) so user.require_auth() passes.
stellar contract invoke \
--id $WISHLIST_ID \
--source kid1 \
--network testnet \
-- \
add_wish \
--user kid1 \
--text "A pony that runs on blockchain"

2. Santa checks the list. Santa can view Kid1’s list using the public view function.

stellar contract invoke \
--id $WISHLIST_ID \
--source santa \
--network testnet \
-- \
get_list \
--user kid1

3. Santa fulfills the wish. Only Santa can do this. If Kid1 tries to call this function, the admin.require_auth() check will fail and the transaction will panic.

stellar contract invoke \
--id $WISHLIST_ID \
--source santa \
--network testnet \
-- \
mark_fulfilled \
--user kid1 \
--wish_id 1
get_list invoked again
[{"created_at_ledger":1921551,"fulfilled":true,"id":1,"text":"A pony that runs on blockchain"}]

Wrapping up with TTL

The “Time To Live” (TTL) is the unsung hero here. In our code, you saw bump_persistent_ttl.

On the Stellar network, you pay “rent” for storing data. If Kid1 sends a wishlist and then disappears for 5 years, Santa shouldn’t have to pay to store that data forever.

By default, we set the data to live for roughly 5,000 ledgers (which is a short time for this example, but configurable). Every time Kid1 interacts with its list (adds a new item) or Santa reads it, we call extend_ttl, pushing the expiration date further out. If no one touches the data, the network eventually archives it, keeping the blockchain efficient and clean.

Congratulations! You just decentralized Christmas. You’ve learned how to secure functions with require_auth, manage distinct storage types, and handle the lifecycle of data with TTL.

“But, as the song goes, ‘Santa’s got a list, he’s checking it twice, going to find out who’s naughty or nice’. Next, we will make sure Santa’s smart contract contains a naughty list that he can check twice”

Sources : Medium

Listen To The Article

Author's Bio
Explore More Topics

Thangapandi

Founder & CEO Osiz Technologies

Mr.Thangapandi, the founder and CEO of Osiz, is a pioneering figure in the field of blockchain technology. His deep understanding of both blockchain technology and user experience has led to the creation of innovative and successful blockchain solutions for businesses and startups, solidifying Osiz's reputation as a reliable service provider in the industry. Because of his unwavering quest for innovation, Mr.Thanga Pandi is well-positioned to be a thought leader and early adopter in the rapidly changing blockchain space. He keeps Osiz at the forefront of this exciting industry with his forward-thinking approach.

Ask For A Free Demo!
Phone
Phone
* T&C Apply
+91 8925923818+91 8925923818https://t.me/Osiz_Technologies_Salessalesteam@osiztechnologies.com
Christmas Offer 2025

X-Mas 30%

Offer

Osiz Technologies Software Development Company USA
Osiz Technologies Software Development Company USA