Skip to main content

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

Persistent storage is designed for data that must remain available indefinitely and be recoverable after expiration. Access persistent storage via env.storage().persistent(). Key Properties:
  • More expensive than temporary storage
  • Expired entries move to Expired State Stack (ESS) for recovery
  • Only one version can exist at a time (cannot recreate while expired version exists)
  • Best for critical data: token balances, ownership records, user profiles

Type Definition

pub struct Persistent {
    storage: Storage,
}
Access through the Storage type:
let persistent = env.storage().persistent();

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 Contract;

#[contractimpl]
impl Contract {
    pub fn check_balance(env: Env, user: Address) -> bool {
        let key = symbol_short!("balance");
        env.storage().persistent().has(&key)
    }
}

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, Address, symbol_short};

#[contract]
pub struct TokenContract;

#[contractimpl]
impl TokenContract {
    pub fn get_balance(env: Env, user: Address) -> i128 {
        let key = symbol_short!("balance");
        env.storage()
            .persistent()
            .get::<_, i128>(&key)
            .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, Address, symbol_short};

#[contract]
pub struct TokenContract;

#[contractimpl]
impl TokenContract {
    pub fn set_balance(env: Env, user: Address, amount: i128) {
        let key = symbol_short!("balance");
        env.storage().persistent().set(&key, &amount);
        
        // Extend TTL to ensure balance persists
        env.storage().persistent().extend_ttl(&key, 100, 1000);
    }
}

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, Address, symbol_short};

#[contract]
pub struct Counter;

#[contractimpl]
impl Counter {
    pub fn increment(env: Env) -> u32 {
        let key = symbol_short!("count");
        env.storage().persistent().update(&key, |count: Option<u32>| {
            count.unwrap_or(0) + 1
        })
    }
}

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, Address, symbol_short};

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
    InsufficientBalance = 1,
}

#[contract]
pub struct TokenContract;

#[contractimpl]
impl TokenContract {
    pub fn transfer(env: Env, from: Address, amount: i128) -> Result<(), Error> {
        let key = symbol_short!("balance");
        env.storage().persistent().try_update(&key, |balance: Option<i128>| {
            let current = balance.unwrap_or(0);
            if current < amount {
                Err(Error::InsufficientBalance)
            } else {
                Ok(current - amount)
            }
        })?;
        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 (prevents unnecessary operations)
Example:
use soroban_sdk::{contract, contractimpl, Env, Address, symbol_short};

#[contract]
pub struct TokenContract;

#[contractimpl]
impl TokenContract {
    pub fn bump_balance(env: Env, user: Address) {
        let key = symbol_short!("balance");
        
        // Extend if TTL drops below 100 ledgers
        // Extend to 1000 ledgers when triggered
        env.storage().persistent().extend_ttl(&key, 100, 1000);
    }
    
    pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
        // Extend TTL on every access
        let from_key = symbol_short!("from_bal");
        let to_key = symbol_short!("to_bal");
        
        env.storage().persistent().extend_ttl(&from_key, 100, 1000);
        env.storage().persistent().extend_ttl(&to_key, 100, 1000);
        
        // ... transfer logic
    }
}
Best Practices:
  • Set threshold to allow time for extension before expiration
  • Set extend_to considering access patterns and costs
  • Extend TTL on every write or read for critical data
  • Use higher extend_to values for frequently accessed data

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, Address, symbol_short};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
    pub fn close_account(env: Env, user: Address) {
        let key = symbol_short!("balance");
        env.storage().persistent().remove(&key);
    }
}

Complete Example

Here’s a complete token contract demonstrating persistent storage usage:
use soroban_sdk::{
    contract, contractimpl, contracterror, Env, Address, symbol_short
};

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
    InsufficientBalance = 1,
}

#[contract]
pub struct TokenContract;

#[contractimpl]
impl TokenContract {
    pub fn transfer(
        env: Env,
        from: Address,
        to: Address,
        amount: i128,
    ) -> Result<(), Error> {
        from.require_auth();
        
        let storage = env.storage().persistent();
        
        // Deduct from sender
        let from_key = symbol_short!("from_bal");
        let new_from_balance = storage.try_update(&from_key, |balance: Option<i128>| {
            let current = balance.unwrap_or(0);
            if current < amount {
                Err(Error::InsufficientBalance)
            } else {
                Ok(current - amount)
            }
        })?;
        
        // Add to receiver
        let to_key = symbol_short!("to_bal");
        storage.update(&to_key, |balance: Option<i128>| {
            balance.unwrap_or(0) + amount
        });
        
        // Extend TTL for both accounts
        // Extend if TTL < 100 ledgers, set to 1000 ledgers
        storage.extend_ttl(&from_key, 100, 1000);
        storage.extend_ttl(&to_key, 100, 1000);
        
        Ok(())
    }
    
    pub fn balance(env: Env, account: Address) -> i128 {
        let key = symbol_short!("balance");
        env.storage()
            .persistent()
            .get(&key)
            .unwrap_or(0)
    }
}

Test Utilities

When the testutils feature is enabled, additional methods are available:
#[cfg(test)]
mod test {
    use soroban_sdk::{Env, symbol_short, testutils::storage::Persistent};
    
    #[test]
    fn test_storage() {
        let env = Env::default();
        let storage = env.storage().persistent();
        
        // Get all persistent storage entries
        let all_data = storage.all();
        
        // Get TTL for a specific key
        let key = symbol_short!("balance");
        let ttl = storage.get_ttl(&key);
        
        assert!(ttl > 0);
    }
}

See Also

Source Reference

Implementation: soroban-sdk/src/storage.rs:301-388

Build docs developers (and LLMs) love