This document describes the high-level storage, durability, and authorization design shared by the Lily Protocol Soroban contracts. It is intended for contributors, auditors, and integrators who need to understand where state lives, how long it lasts, and who can change it.
Soroban provides two storage kinds that the contracts use deliberately:
- Instance storage (
env.storage().instance()): small, frequently-accessed state that is tied to the contract deployment. Used for global config, admin addresses, and one-time initialization flags. Bumped on every entrypoint call to keep the instance alive. - Persistent storage (
env.storage().persistent()): per-entity state that must survive for the lifetime of the protocol. Used for profiles, intents, and wallet bindings.
Both kinds are keyed by typed DataKey enums local to each contract crate. There is no shared DataKey across contracts.
Shared TTL constants live in crates/lily-common/src/lib.rs:
pub const INSTANCE_BUMP_THRESHOLD: u32 = 17_280; // ~1 day of ledgers
pub const INSTANCE_BUMP_AMOUNT: u32 = 172_800; // ~10 days of ledgersThe helper bump_instance(env) extends the instance storage TTL by INSTANCE_BUMP_AMOUNT whenever it is called. Every contract entrypoint that reads or writes state calls bump_instance at the end of the happy path, ensuring the instance does not expire due to inactivity.
Persistent storage entries do not currently call extend_ttl explicitly. In a production deployment, long-lived per-entity records (profiles, intents, bindings) should be bumped explicitly or the protocol should rely on periodic keeper transactions to keep critical records alive.
Every contract follows the same initialization pattern:
- Check that
DataKey::Initializedis not already set. - Require auth from the actor that will become the admin.
- Write initial config and set
DataKey::Initialized = true. - Emit an
initevent.
Re-initialization is rejected with ProtocolError::AlreadyInitialized.
Three categories of actors appear across the contracts:
- Admin: Set at initialization. Can change global config, transfer admin rights, and perform privileged actions such as deactivating profiles or settling payment intents.
- Self-authorized actor: The agent or payer that owns a specific record. Must sign operations that affect their own profile, wallet binding, or payment intent.
- Dual authorization: Some operations require both the agent and a related party to sign. For example,
wallet::bind_walletrequires auth from bothagentandwallet.
Auth is always explicit via Address::require_auth(); there are no implicit or delegated authorization paths.
Global protocol configuration.
| Key | Type | Durability | Description |
|---|---|---|---|
Admin |
Address |
Instance | Protocol admin address. |
Treasury |
Address |
Instance | Treasury address for fee collection. |
FeeBps |
u32 |
Instance | Fee in basis points. |
Initialized |
bool |
Instance | One-time initialization flag. |
initializeset_fee_bpsset_treasurytransfer_admin
Agent identity registry.
| Key | Type | Durability | Description |
|---|---|---|---|
Admin |
Address |
Instance | Registry admin address. |
Initialized |
bool |
Instance | One-time initialization flag. |
Profile(Address) |
AgentProfile |
Persistent | Per-agent profile record. |
initializedeactivate
register(agent signs)update_profile(current controller signs)
Wallet policy registry.
| Key | Type | Durability | Description |
|---|---|---|---|
Admin |
Address |
Instance | Wallet registry admin address. |
Initialized |
bool |
Instance | One-time initialization flag. |
Binding(Address) |
WalletBinding |
Persistent | Per-agent wallet binding. |
initialize
update_spend_limit(agent signs)set_enabled(agent signs)
bind_wallet(agent and wallet both sign)
Payment intent and settlement.
| Key | Type | Durability | Description |
|---|---|---|---|
Admin |
Address |
Instance | Settlement admin address. |
Treasury |
Address |
Instance | Treasury address for fee collection. |
FeeBps |
u32 |
Instance | Fee in basis points. |
NextIntentId |
u64 |
Instance | Monotonically increasing intent ID counter. |
Initialized |
bool |
Instance | One-time initialization flag. |
Intent(u64) |
PaymentIntent |
Persistent | Per-intent payment record. |
initializesettle_intent
create_intent(payer signs)cancel_intent(payer signs)
ProtocolError: typed errors used across all contracts.PaymentStatus: enum used bypayments(and potentially future settlement contracts).MAX_BPS: basis-point ceiling.bump_instance: TTL refresh helper.
Test-only helpers; no runtime storage.
- Instance storage is versioned implicitly by the contract wasm hash.
- A future upgrade path should introduce an explicit
StorageVersionkey; seedocs/UPGRADABILITY.md.