Skip to main content

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:
  • key: The key to check
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:
  • key: The key to retrieve
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:
  • key: The key to remove
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

FeatureTemporaryPersistent
CostLowerHigher
RecoveryNot recoverableRecoverable via ESS
RecreationCan recreate after expirationCannot recreate while expired
Use caseTime-sensitive, non-criticalCritical, long-term
TTL rangeShorter (minutes to hours)Longer (days to months)

Best Practices

  1. Use appropriate TTL values
    • Set TTL based on data staleness requirements
    • Shorter TTLs reduce costs but require more frequent updates
  2. Handle missing data gracefully
    • Always handle None from get() calls
    • Provide sensible defaults or error handling
  3. Consider data freshness
    • Store timestamps with data when freshness matters
    • Validate age before using cached data
  4. Batch operations when possible
    • Update multiple entries in single contract call
    • Reduce transaction overhead
  5. 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