Skip to main content
Vowena is built on a small set of composable primitives. Understanding these concepts will help you build on the protocol, integrate it into your application, and reason about edge cases.

Plans

Subscriptions

Token Allowance

Billing Cycle

Price Ceiling

Grace Periods


Plans

A plan is an immutable billing template created by a merchant on-chain. It defines everything about how a subscription will be billed.
FieldTypeDescription
tokenAddressThe SEP-41 token used for billing (e.g., USDC)
amounti128Amount charged per billing period (in stroops, 7 decimals)
periodu64Billing period length in seconds (e.g., 2,592,000 for 30 days)
trial_periodsu32Number of billing periods before the first charge (0 = no trial)
max_periodsu32Maximum number of chargeable periods (0 = unlimited)
grace_periodu64Time in seconds after a failed charge before the subscription pauses
price_ceilingi128Maximum amount the plan can be adjusted to without subscriber re-auth
merchantAddressThe merchant’s Stellar address that receives payments
Plans are immutable once created. This is a deliberate design choice:
  • Subscribers know exactly what they signed up for - the terms can never change silently.
  • Price changes require creating a new plan and using the migration flow.
  • Subscribers must explicitly accept any migration, preserving full consent.
The one exception: merchants can call update_plan_amount() to adjust the billing amount, but only up to the price_ceiling. This allows minor price adjustments (e.g., rounding changes) without requiring a full migration.
const tx = await client.buildCreatePlan({
  merchant: "GMERCHANT...ADDR",
  token: NETWORKS.testnet.usdcAddress,
  amount: toStroops("9.99"),
  period: 2_592_000,        // 30 days
  priceCeiling: toStroops("14.99"),
  trialPeriods: 1,
  maxPeriods: 0,            // Unlimited
  gracePeriod: 259_200,     // 3 days
});
The returned planId is a sequential integer assigned by the contract. Use it to reference this plan in all subsequent operations.

Subscriptions

A subscription is an on-chain record that links a subscriber to a plan. It tracks the current state of the billing relationship.
A subscription moves through a defined set of states:
┌──────────┐     subscribe()     ┌──────────┐
│          │ ──────────────────→ │          │
│  (none)  │                     │  Active  │ ←─── reactivate()
│          │                     │          │ ←─── (grace retry succeeds)
└──────────┘                     └────┬─────┘

                          ┌───────────┼───────────┐
                          │           │           │
                     cancel()    grace expires  max_periods
                          │       or failed      reached
                          │           │           │
                          ▼           ▼           ▼
                    ┌──────────┐ ┌──────────┐ ┌──────────┐
                    │Cancelled │ │  Paused  │ │ Expired  │
                    └──────────┘ └────┬─────┘ └──────────┘

                                one more period
                                with no retry


                                ┌──────────┐
                                │Cancelled │
                                └──────────┘
  • Active - The subscription is live. Charges can be triggered each period.
  • Paused - A charge failed, the grace period expired, and the subscription is on hold. It can be reactivated.
  • Cancelled - Permanently ended. Either the subscriber/merchant cancelled, or a paused subscription was not reactivated in time.
  • Expired - The max_periods limit was reached. The subscription completed naturally.
Each subscription stores:
FieldDescription
subscriberThe subscriber’s Stellar address
plan_idThe plan this subscription is linked to
statusCurrent status (Active, Paused, Cancelled, Expired)
created_atTimestamp when the subscription was created
last_charged_atTimestamp of the most recent successful charge
periods_chargedNumber of billing periods successfully charged
failed_atTimestamp of the most recent failed charge attempt (if any)
Both the subscriber and the merchant can cancel a subscription at any time by calling cancel(). The subscriber does not need the merchant’s permission, and the merchant does not need the subscriber’s permission.Cancellation is an on-chain transaction - no frontend, API, or dashboard is required. Any tool that can invoke a Soroban contract can cancel a subscription.

Token Allowance (Pull Billing)

This is the core mechanism that makes Vowena possible. Understanding token allowances is essential to understanding the protocol.
Vowena uses the SEP-41 token standard on Stellar, which includes an approve / transfer_from pattern - the same concept as ERC-20 allowances on Ethereum.
  1. When a subscriber calls subscribe(), the transaction includes a token.approve() call that grants the Vowena contract permission to transfer USDC from the subscriber’s account.
  2. The approval specifies:
    • Spender: The Vowena contract address
    • Amount: The plan’s price_ceiling multiplied by remaining periods (or a large value for unlimited plans)
    • Expiry: A ledger-based expiration
  3. Each billing period, when charge() is called, the contract calls transfer_from() to move USDC from the subscriber to the merchant.
  4. No new signature is needed for each charge - the initial allowance covers all future payments.
When the subscriber’s wallet prompts for the signature, it displays the full authorization tree:
  • Contract call: vowena.subscribe(subscriber, plan_id)
  • Token approval: usdc.approve(subscriber, vowena_contract, amount, expiry_ledger)
The subscriber sees exactly which token, how much spending authority, and for how long. Nothing is hidden.
Soroban’s authorization framework allows a single transaction to carry multiple authorization entries. The subscribe() call bundles:
  1. Authorization to invoke subscribe() on the Vowena contract
  2. Authorization to invoke approve() on the USDC token contract
The subscriber signs once, and both authorizations are included in the same transaction. This is what makes the UX seamless - no multi-step approval flow.
  • The allowance is granted only to the Vowena contract, not to the merchant.
  • The contract enforces billing rules (period timing, amount limits, status checks) before calling transfer_from().
  • The subscriber can revoke the allowance at any time by calling cancel() or by directly calling token.approve() with a zero amount.
  • Allowances have a ledger-based expiration on Soroban - they don’t last forever.

Billing Cycle

The billing cycle is the heartbeat of every subscription. Here’s what happens each period.
When charge() is called, the contract performs these checks in order:
  1. Status check - Is the subscription Active?
  2. Time check - Has at least one full period elapsed since last_charged_at?
  3. Trial check - Has the trial period ended? (If not, the period counter increments but no transfer occurs.)
  4. Allowance check - Does the subscriber have sufficient token allowance for the Vowena contract?
  5. Balance check - Does the subscriber have enough USDC balance?
  6. Max periods check - Has max_periods been reached? (If so, mark as Expired.)
If all checks pass, the contract calls transfer_from() to move amount USDC from the subscriber to the merchant.
The charge() function is permissionless - literally anyone can call it. The caller address parameter is simply used for attribution; it does not need to be the merchant or the subscriber.This design enables:
  • Merchant self-service: Merchants run their own keeper bots
  • Dashboard keepers: The Vowena dashboard offers a hosted keeper service
  • Third-party keepers: Independent services that charge subscriptions for a fee
  • Subscriber-initiated: Subscribers can even trigger their own charges
The contract enforces all rules regardless of who calls charge(). There is no trust assumption on the caller.
The contract uses Soroban’s ledger.timestamp() for timing. A charge is eligible when:
current_timestamp >= last_charged_at + period
For the first charge after subscribing (and after any trial periods), last_charged_at is set to the subscription creation time plus trial periods.

Price Ceiling

The price ceiling is a subscriber protection mechanism built directly into the protocol.
When a merchant creates a plan, they set a price_ceiling - the maximum amount the plan’s billing amount can ever be adjusted to.
  • A merchant can call update_plan_amount() to change the billing amount, but only up to the price_ceiling.
  • If the merchant needs to charge more than the ceiling, they must create a new plan and use the migration flow.
  • The subscriber must explicitly accept any migration.
A merchant creates a plan:
  • amount: 9.99 USDC
  • price_ceiling: 14.99 USDC
Later, the merchant can adjust the amount up to 14.99 USDC without any action from the subscriber. But to charge 19.99 USDC, the merchant would need to:
  1. Create a new plan with the new pricing
  2. Request a migration for each subscriber
  3. Each subscriber individually accepts (or rejects) the migration
In traditional SaaS billing, companies can silently raise prices and charge your card the new amount. Vowena makes this impossible:
  • The price ceiling is set at plan creation and is immutable.
  • The subscriber’s token allowance is based on the price ceiling, not the current amount.
  • Any change above the ceiling requires explicit subscriber consent via the migration flow.

Grace Periods

Grace periods provide a safety net for failed charges, preventing immediate subscription loss due to temporary balance issues.
  1. A charge() attempt fails (insufficient balance or allowance).
  2. The contract records failed_at with the current timestamp.
  3. The subscription remains Active during the grace window.
  4. During the grace period, charge() can be retried.
  5. If a retry succeeds, failed_at is cleared and billing continues normally.
  6. If the grace period expires without a successful charge, the subscription moves to Paused.
  7. A Paused subscription gets one more billing period for reactivation.
  8. If not reactivated, the subscription moves to Cancelled.
The grace_period is set per plan, in seconds:
ValueDuration
86_4001 day
259_2003 days
604_8007 days
0No grace period (fail immediately pauses)
Choose a grace period that balances subscriber convenience with cash flow needs. 3 days is a common default.
  • Merchants: Set a reasonable grace period (1-7 days). Too short and you lose subscribers to temporary balance dips. Too long and you delay revenue.
  • Subscribers: If you receive a “charge failed” notification, top up your USDC balance before the grace period expires.
  • Keepers: Implement retry logic during the grace window. A charge that fails on day 1 may succeed on day 2 if the subscriber tops up.

Keepers

A keeper is any service or bot that calls charge() on subscriptions when they become due. The term comes from the DeFi concept of “keeper bots” that maintain protocol health.
Smart contracts on Soroban (and most blockchains) cannot execute automatically. Someone must submit a transaction to trigger the charge() function each billing period.Keepers solve this problem. They monitor subscriptions and call charge() at the right time.

Merchant Self-Hosted

Run the SDK’s keeper module on your own infrastructure. Full control, no dependencies.

Dashboard Keeper

The Vowena Dashboard includes a hosted keeper service. Toggle it on per plan - no code required.

Third-Party Keepers

Independent keeper services that monitor and charge subscriptions across the network. Permissionless and competitive.
The SDK includes a keeper module:
import { VowenaKeeper, NETWORKS } from "vowena";

const keeper = new VowenaKeeper({
  contractId: NETWORKS.testnet.contractId,
  rpcUrl: NETWORKS.testnet.rpcUrl,
  networkPassphrase: NETWORKS.testnet.networkPassphrase,
  signerSecretKey: "SKEEPER...SECRET",
});

// Start monitoring and charging
await keeper.start({
  planIds: [1, 2, 3],       // Plans to monitor
  pollInterval: 60_000,      // Check every 60 seconds
});
See the Keeper SDK docs for the full API.

Next Steps

How It Works

See the full subscription flow in a visual step-by-step walkthrough.

Quickstart

Build your first subscription in 5 minutes with the SDK.

Protocol Deep Dive

Explore the contract architecture, storage model, and advanced features.

FAQ

Get answers to common questions about Vowena.