Documentation Index
Fetch the complete documentation index at: https://mintlify.com/stellar/rs-soroban-sdk/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Temporary storage provides a cost-effective option for data with limited relevance periods. Access temporary storage via env.storage().temporary().
Key Properties:
- Cheaper than persistent storage
- Permanently deleted when expired (not recoverable)
- Can be recreated with different values after expiration
- Never enters Expired State Stack (ESS)
- Best for time-sensitive data: oracle feeds, temporary offers, cache data
Type Definition
pub struct Temporary {
storage: Storage,
}
Access through the Storage type:
let temporary = env.storage().temporary();
Methods
has
Checks if a value exists for the given key.
pub fn has<K>(&self, key: &K) -> bool
where
K: IntoVal<Env, Val>,
Parameters:
Returns: true if a value exists, false otherwise
Example:
use soroban_sdk::{contract, contractimpl, Env, symbol_short};
#[contract]
pub struct OracleContract;
#[contractimpl]
impl OracleContract {
pub fn has_price(env: Env, symbol: Symbol) -> bool {
env.storage().temporary().has(&symbol)
}
}
get
Retrieves a value for the given key.
pub fn get<K, V>(&self, key: &K) -> Option<V>
where
V::Error: Debug,
K: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
Parameters:
Returns: Some(V) if the value exists, None otherwise
Panics: If the stored value cannot be converted to type V
Example:
use soroban_sdk::{contract, contractimpl, Env, Symbol, symbol_short};
#[contract]
pub struct OracleContract;
#[contractimpl]
impl OracleContract {
pub fn get_price(env: Env, symbol: Symbol) -> Option<i128> {
env.storage().temporary().get::<_, i128>(&symbol)
}
pub fn get_price_or_default(env: Env, symbol: Symbol) -> i128 {
env.storage()
.temporary()
.get::<_, i128>(&symbol)
.unwrap_or(0)
}
}
set
Stores a value for the given key.
pub fn set<K, V>(&self, key: &K, val: &V)
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
Parameters:
key: The key to store under
val: The value to store
Example:
use soroban_sdk::{contract, contractimpl, Env, Symbol, symbol_short};
#[contract]
pub struct OracleContract;
#[contractimpl]
impl OracleContract {
pub fn submit_price(env: Env, symbol: Symbol, price: i128) {
// Store price in temporary storage
env.storage().temporary().set(&symbol, &price);
// Extend TTL for 100 ledgers (~8 minutes)
env.storage().temporary().extend_ttl(&symbol, 50, 100);
}
}
update
Updates a value by applying a function to the current value.
pub fn update<K, V>(&self, key: &K, f: impl FnOnce(Option<V>) -> V) -> V
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
Parameters:
key: The key to update
f: Function that receives current value (or None) and returns new value
Returns: The new value after update
Example:
use soroban_sdk::{contract, contractimpl, Env, symbol_short};
#[contract]
pub struct CacheContract;
#[contractimpl]
impl CacheContract {
pub fn increment_counter(env: Env, key: Symbol) -> u32 {
env.storage().temporary().update(&key, |count: Option<u32>| {
count.unwrap_or(0) + 1
})
}
pub fn add_to_cache(env: Env, key: Symbol, value: i32) {
env.storage().temporary().update(&key, |current: Option<Vec<i32>>| {
let mut vec = current.unwrap_or_default();
vec.push_back(value);
vec
});
}
}
try_update
Updates a value by applying a fallible function.
pub fn try_update<K, V, E>(
&self,
key: &K,
f: impl FnOnce(Option<V>) -> Result<V, E>,
) -> Result<V, E>
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
Parameters:
key: The key to update
f: Fallible function that receives current value and returns Result<V, E>
Returns: Ok(V) with the new value, or Err(E) if the function fails
Example:
use soroban_sdk::{contract, contractimpl, contracterror, Env, Symbol};
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
InvalidValue = 1,
CacheFull = 2,
}
#[contract]
pub struct CacheContract;
#[contractimpl]
impl CacheContract {
const MAX_CACHE_SIZE: u32 = 100;
pub fn add_with_limit(env: Env, key: Symbol, value: i32) -> Result<(), Error> {
env.storage().temporary().try_update(&key, |current: Option<Vec<i32>>| {
let mut vec = current.unwrap_or_default();
if vec.len() >= Self::MAX_CACHE_SIZE {
return Err(Error::CacheFull);
}
if value < 0 {
return Err(Error::InvalidValue);
}
vec.push_back(value);
Ok(vec)
})?;
Ok(())
}
}
extend_ttl
Extends the time-to-live for a storage entry.
pub fn extend_ttl<K>(&self, key: &K, threshold: u32, extend_to: u32)
where
K: IntoVal<Env, Val>,
Parameters:
key: The storage key to extend
threshold: Only extend if TTL is below this value (in ledgers)
extend_to: New TTL value when extended (in ledgers)
Behavior:
- Only extends TTL if current TTL < threshold
- Sets TTL to
extend_to ledgers from current ledger
- No-op if TTL >= threshold
Example:
use soroban_sdk::{contract, contractimpl, Env, Symbol};
#[contract]
pub struct OracleContract;
#[contractimpl]
impl OracleContract {
pub fn submit_price(env: Env, symbol: Symbol, price: i128) {
env.storage().temporary().set(&symbol, &price);
// Keep price data for ~8 minutes (100 ledgers)
// Extend when TTL drops below 50 ledgers
env.storage().temporary().extend_ttl(&symbol, 50, 100);
}
pub fn submit_hourly_data(env: Env, hour: u32, data: Vec<i128>) {
let key = hour;
env.storage().temporary().set(&key, &data);
// Keep hourly data for ~16 hours (2000 ledgers)
env.storage().temporary().extend_ttl(&key, 1000, 2000);
}
}
TTL Guidelines for Temporary Storage:
- Short-term cache: 100-500 ledgers (~8-40 minutes)
- Hourly data: 1000-2000 ledgers (~1.3-2.7 hours)
- Daily data: 10000-20000 ledgers (~13-27 hours)
- Use shorter TTLs for temporary storage to optimize costs
remove
Removes a key and its value from storage.
pub fn remove<K>(&self, key: &K)
where
K: IntoVal<Env, Val>,
Parameters:
Behavior: No-op if the key doesn’t exist
Example:
use soroban_sdk::{contract, contractimpl, Env, Symbol};
#[contract]
pub struct CacheContract;
#[contractimpl]
impl CacheContract {
pub fn clear_cache(env: Env, key: Symbol) {
env.storage().temporary().remove(&key);
}
pub fn invalidate_prices(env: Env, symbols: Vec<Symbol>) {
for symbol in symbols.iter() {
env.storage().temporary().remove(&symbol);
}
}
}
Complete Example
Here’s a complete oracle contract demonstrating temporary storage usage:
use soroban_sdk::{
contract, contractimpl, contracterror, Env, Symbol, Address, Vec
};
#[derive(Clone)]
pub struct PriceData {
pub price: i128,
pub timestamp: u64,
pub source: Address,
}
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
Unauthorized = 1,
StalePrice = 2,
}
#[contract]
pub struct OracleContract;
#[contractimpl]
impl OracleContract {
const PRICE_TTL_THRESHOLD: u32 = 50; // ~4 minutes
const PRICE_TTL_EXTEND_TO: u32 = 100; // ~8 minutes
const MAX_PRICE_AGE: u64 = 600; // 10 minutes in seconds
/// Submit a price update (authorized sources only)
pub fn submit_price(
env: Env,
source: Address,
symbol: Symbol,
price: i128,
) -> Result<(), Error> {
source.require_auth();
let price_data = PriceData {
price,
timestamp: env.ledger().timestamp(),
source,
};
env.storage().temporary().set(&symbol, &price_data);
env.storage().temporary().extend_ttl(
&symbol,
Self::PRICE_TTL_THRESHOLD,
Self::PRICE_TTL_EXTEND_TO,
);
Ok(())
}
/// Get current price (returns error if stale)
pub fn get_price(env: Env, symbol: Symbol) -> Result<i128, Error> {
let price_data: Option<PriceData> = env.storage().temporary().get(&symbol);
match price_data {
Some(data) => {
let current_time = env.ledger().timestamp();
let age = current_time - data.timestamp;
if age > Self::MAX_PRICE_AGE {
Err(Error::StalePrice)
} else {
Ok(data.price)
}
}
None => Err(Error::StalePrice),
}
}
/// Get price with metadata
pub fn get_price_data(env: Env, symbol: Symbol) -> Option<PriceData> {
env.storage().temporary().get(&symbol)
}
/// Submit multiple prices in batch
pub fn submit_prices(
env: Env,
source: Address,
symbols: Vec<Symbol>,
prices: Vec<i128>,
) -> Result<(), Error> {
source.require_auth();
let timestamp = env.ledger().timestamp();
for i in 0..symbols.len() {
let symbol = symbols.get(i).unwrap();
let price = prices.get(i).unwrap();
let price_data = PriceData {
price,
timestamp,
source: source.clone(),
};
env.storage().temporary().set(&symbol, &price_data);
env.storage().temporary().extend_ttl(
&symbol,
Self::PRICE_TTL_THRESHOLD,
Self::PRICE_TTL_EXTEND_TO,
);
}
Ok(())
}
}
Temporary vs Persistent Storage
| Feature | Temporary | Persistent |
|---|
| Cost | Lower | Higher |
| Recovery | Not recoverable | Recoverable via ESS |
| Recreation | Can recreate after expiration | Cannot recreate while expired |
| Use case | Time-sensitive, non-critical | Critical, long-term |
| TTL range | Shorter (minutes to hours) | Longer (days to months) |
Best Practices
-
Use appropriate TTL values
- Set TTL based on data staleness requirements
- Shorter TTLs reduce costs but require more frequent updates
-
Handle missing data gracefully
- Always handle
None from get() calls
- Provide sensible defaults or error handling
-
Consider data freshness
- Store timestamps with data when freshness matters
- Validate age before using cached data
-
Batch operations when possible
- Update multiple entries in single contract call
- Reduce transaction overhead
-
Don’t store critical data
- Never use temporary storage for data that must be recoverable
- Use persistent storage for balances, ownership, etc.
Test Utilities
When the testutils feature is enabled, additional methods are available:
#[cfg(test)]
mod test {
use soroban_sdk::{Env, symbol_short, testutils::storage::Temporary};
#[test]
fn test_temporary_storage() {
let env = Env::default();
let storage = env.storage().temporary();
// Get all temporary storage entries
let all_data = storage.all();
// Get TTL for a specific key
let key = symbol_short!("price");
storage.set(&key, &100);
let ttl = storage.get_ttl(&key);
assert!(ttl > 0);
}
}
See Also
Source Reference
Implementation: soroban-sdk/src/storage.rs:390-477