Skip to main content

Single Contract, Shared by All

Vowena is a single smart contract deployed once on Stellar’s Soroban platform. Every merchant, every plan, and every subscription lives inside the same contract instance. There are no separate deployments per merchant - plans and subscriptions are differentiated by their on-chain data, not by contract addresses.
This design means one contract address to integrate, one set of events to index, and one deployment to maintain. Merchants share infrastructure while retaining full isolation of their data.

Storage Strategy

Soroban offers multiple storage types with different cost and lifetime characteristics. Vowena uses two:

Instance Storage

Shared across the contract. Stores global state that every invocation may need: the admin address and auto-incrementing counters (NextPlanId, NextSubId). Instance storage lives as long as the contract instance itself.

Persistent Storage

One entry per entity. Each plan, subscription, and index is its own persistent ledger entry. This is critical for Soroban’s parallel execution model - transactions touching different plans or subscriptions never contend on the same storage key.
Because each plan and subscription is a separate persistent entry, Soroban can execute charges for different subscriptions in parallel across validators. This is a deliberate design choice for scalability.

Data Models

DataKey Enum

Every storage entry is keyed by a variant of the DataKey enum:
#[contracttype]
pub enum DataKey {
    Admin,                          // Instance - admin address
    NextPlanId,                     // Instance - u64 counter
    NextSubId,                      // Instance - u64 counter
    Plan(u64),                      // Persistent - one per plan
    Sub(u64),                       // Persistent - one per subscription
    MerchantPlans(Address),         // Persistent - Vec<u64> of plan IDs
    SubscriberSubs(Address),        // Persistent - Vec<u64> of sub IDs
    PlanSubs(u64),                  // Persistent - Vec<u64> of sub IDs per plan
}

Plan Struct

A plan defines the billing terms that a merchant offers. Once created, plans are immutable except for the amount field (within the ceiling).
#[contracttype]
#[derive(Clone, Debug)]
pub struct Plan {
    pub id: u64,              // Unique auto-incremented identifier
    pub merchant: Address,    // Merchant who created the plan
    pub token: Address,       // SAC token address (e.g., USDC)
    pub amount: i128,         // Amount charged per period
    pub period: u64,          // Billing period in seconds
    pub trial_periods: u32,   // Number of free trial periods (0 = no trial)
    pub max_periods: u32,     // Maximum billing periods (0 = unlimited)
    pub grace_period: u64,    // Grace window in seconds after failed charge
    pub price_ceiling: i128,  // Maximum amount the merchant can ever set
    pub created_at: u64,      // Ledger timestamp at creation
    pub active: bool,         // Whether the plan accepts new subscribers
}

Subscription Struct

A subscription links a subscriber to a plan and tracks billing state.
#[contracttype]
#[derive(Clone, Debug)]
pub struct Subscription {
    pub id: u64,                     // Unique auto-incremented identifier
    pub plan_id: u64,                // The plan this subscription belongs to
    pub subscriber: Address,         // Subscriber's wallet address
    pub status: SubscriptionStatus,  // Current lifecycle state
    pub created_at: u64,             // Ledger timestamp at creation
    pub periods_billed: u32,         // Number of periods successfully charged
    pub next_billing_time: u64,      // Timestamp when next charge is due
    pub failed_at: u64,              // Timestamp of last failed charge (0 = none)
    pub migration_target: u64,       // Target plan ID for pending migration (0 = none)
    pub cancelled_at: u64,           // Timestamp of cancellation (0 = not cancelled)
}

SubscriptionStatus Enum

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub enum SubscriptionStatus {
    Active,     // Subscription is live and billable
    Paused,     // Grace period expired - charge failed, awaiting resolution
    Cancelled,  // Permanently cancelled by subscriber or system
    Expired,    // Max periods reached - natural end of subscription
}

TTL Management

Soroban ledger entries have a time-to-live (TTL) and can be archived if not extended. Vowena manages TTLs to keep data available:
Storage TypeThresholdExtend ToApproximate Duration
Persistent (plans, subs, indexes)~30 days~120 daysEntries are extended when accessed; archived entries can be restored
Instance (admin, counters)~30 days~90 daysExtended on every contract invocation
If a persistent entry is archived (e.g., a very old subscription no one has touched), it can be restored by anyone calling extend_ttl. The data is never deleted - just temporarily inaccessible until restored.
The contract exposes an extend_ttl function that can be called by anyone to proactively extend the TTL of any plan or subscription.

Events

Vowena emits 13 event types covering every state change in the protocol. All events are indexable by Soroban event listeners and the Vowena SDK.
EventTopicsData
PlanCreatedplan_idFull Plan struct
PlanUpdatedplan_idNew amount
PlanDeactivatedplan_id-
SubscriptionCreatedsub_id, plan_idFull Subscription struct
ChargeBilledsub_id, plan_idamount, periods_billed
ChargeFailedsub_id, plan_idfailed_at timestamp
SubscriptionPausedsub_id, plan_idfailed_at timestamp
SubscriptionCancelledsub_id, plan_idcancelled_at timestamp
SubscriptionExpiredsub_id, plan_idperiods_billed
SubscriptionReactivatedsub_id, plan_id-
MigrationRequestedold_plan_id, new_plan_idCount of affected subs
MigrationAcceptedold_sub_id, new_sub_idnew_plan_id
RefundIssuedsub_id, plan_idamount
Use the SDK event listener to subscribe to these events in real time and build dashboards, notifications, or analytics pipelines.

What’s Next

Plans

Learn how merchants create and configure subscription plans.

Subscriptions

Understand the subscription lifecycle and state machine.

Billing

Deep dive into the permissionless charge flow.

Security

Explore the consumer protection guarantees.