This document describes the current FlowPay contract architecture, module responsibilities, storage layout, event flow, and frontend integration.
FlowPay has two runtime pieces:
- The Soroban contract in
contract/src/, which owns subscription state and all on-chain policy. - The React frontend in
frontend/, which builds transactions and submits them through Freighter.
A keeper process is the only off-chain service required for recurring billing. It calls charge() or batch_charge() on schedule.
graph TD
user[User / Merchant]
ui[Frontend React app]
wallet[Freighter wallet]
keeper[Keeper service]
contract[FlowPay contract]
token[SAC token contract]
user --> ui --> wallet --> contract
keeper --> contract
contract --> token
contract --> token
| Module | Responsibility |
|---|---|
lib.rs |
Public entry points, contract data types, storage keys, and cross-module orchestration. |
admin.rs |
Admin initialization, auth checks, and two-step admin transfer. |
batch.rs |
batch_charge() processing and per-user result reporting. |
bench.rs |
Benchmark-only instruction counting for core flows. |
charge_exec.rs |
Charge scheduling helpers and charge execution internals. |
errors.rs |
Contract error codes. |
events.rs |
All event emission helpers. |
fee.rs |
Protocol fee proposal, commit, and fee calculation logic. |
grace.rs |
Grace-period proposal, commit, and lookup helpers. |
merchant_stats.rs |
Merchant revenue, subscriber counts, and daily revenue buckets. |
migration.rs |
Schema version tracking and storage migration. |
min_interval.rs |
Minimum billing interval floor. |
referral.rs |
Referral storage and lookup. |
spending_limit.rs |
Temporary daily spending limits for pay_per_use(). |
storage.rs |
Shared storage read/write helpers. |
subscription_count.rs |
Active subscription count and append-only subscriber index. |
subscription_history.rs |
Charge history recording and paging. |
subscription_metadata.rs |
Short subscription labels. |
test.rs |
Contract unit tests. |
trial.rs |
Trial end computation and trial helpers. |
upgrade.rs |
WASM upgrade wrapper and upgrade event emission. |
validation.rs |
Shared validation helpers for amounts, intervals, and allowance checks. |
whitelist.rs |
Merchant whitelist and freeze state helpers. |
subscribe()orsubscribe_with_metadata()validates auth, whitelist state, minimum interval, and token allowance.storage.rswrites theSubscriptionrecord.subscription_count.rsupdates the active count and subscriber index when needed.referral.rsstores an optional referrer.subscription_metadata.rsstores an optional label for the metadata path.events.rsemits the subscription event.
charge()loads the subscription and checks pause, interval, and grace period state.fee.rscalculates any protocol fee split.- The token contract performs
transfer_from(). merchant_stats.rsrecords merchant revenue.subscription_history.rsrecords the successful charge timestamp.events.rsemitscharged.
batch_charge()iterates over a list of subscriber addresses.batch.rsreuses the same charge eligibility checks as the single-charge path.- Each user produces a
ChargeResultinstead of aborting the whole transaction.
merchant_stats.rsstores cumulative revenue and daily revenue buckets.- Read helpers expose total revenue, per-day revenue, and subscriber counts.
- Administrative reset helpers clear or zero selected counters without affecting subscription state.
subscription_metadata.rsstores short labels.subscription_history.rsstores charge timestamps and supports paging and clearing.referral.rsstores the original referrer, if any.
FlowPay uses Soroban instance, persistent, and temporary storage deliberately.
| DataKey | Purpose | Storage type |
|---|---|---|
Token |
Default payment token | instance |
Admin |
Current admin | instance |
PendingAdmin |
Two-step admin transfer target | instance |
ContractPaused |
Global pause flag | instance |
GracePeriod |
Charge grace window | instance |
WhitelistEnabled |
Merchant whitelist flag | instance |
FeeCollector / FeeBps |
Protocol fee configuration | instance |
PendingFee |
Pending fee proposal | temporary |
PendingGracePeriod |
Pending grace-period proposal | temporary |
MinInterval |
Minimum allowed subscription interval | instance |
SchemaVersion |
Storage schema version | instance |
ActiveCount |
Active subscription count | instance |
SubscriberIndexSize |
Append-only subscriber count | instance |
Subscription(user) |
Subscriber subscription record | persistent |
MerchantWhitelist(merchant) |
Whitelisted merchant flag | persistent |
MerchantFrozen(merchant) |
Frozen merchant flag | persistent |
MerchantRevenue(merchant) |
Cumulative merchant revenue | persistent |
MerchantRevenueDay(merchant, day) |
Daily revenue bucket | persistent |
MerchantRevenueHistory(merchant) |
History vector for revenue reads | persistent |
MerchantSubCount(merchant) |
Active subscriber count per merchant | persistent |
DailyLimit(user) |
Temporary pay-per-use limit | temporary |
DailySpent(user) |
Temporary pay-per-use spend counter | temporary |
Referral(user) |
Referrer for a subscriber | persistent |
SubscriptionMeta(user) |
Short subscription label | persistent |
ChargeHistory(user) |
Charge timestamps | persistent |
SubscriberIndex(i) |
Append-only subscriber list entry | persistent |
GlobalVolumeWindow |
Rolling volume cap state | instance |
Persistent entries that must remain available are refreshed with TTL extensions where needed, most importantly subscription records and selected merchant-revenue data. Temporary entries are used for short-lived proposals and daily spending caps.
Events are emitted from events.rs and kept separate from storage mutation so the public contract methods remain small.
| Event | Trigger |
|---|---|
subscribed |
New or replaced subscription created |
charged |
Successful recurring charge |
pay_per_use |
Successful one-time charge |
cancelled |
Subscription cancelled |
paused / resumed |
Subscription pause state changed |
admin_transferred |
Two-step admin transfer completed |
fee_proposed / fee_committed |
Fee configuration changed |
merchant_added / merchant_removed |
Whitelist updated |
merchant_frozen / merchant_unfrozen |
Merchant freeze state changed |
grace_period_proposed / grace_period_committed |
Grace period updated |
subscription_amount_updated / subscription_interval_updated |
Admin adjusted a subscription |
merchant_withdrawal |
Merchant withdrew revenue |
daily_limit_set / daily_limit_removed |
Daily limit updated |
subscription_transferred |
Subscription ownership moved |
upgraded |
Contract WASM upgraded |
Events are the main off-chain integration surface for analytics, indexers, and the keeper workflow.
The frontend does not talk to the contract directly. frontend/src/stellar.ts builds and simulates Soroban transactions, then Freighter signs them.
Typical flows:
- Subscribe: build transaction, simulate, sign, submit.
- Charge or pay-per-use: same transaction pipeline, but the user or keeper supplies the target address.
- Dashboard reads: call read-only entry points like
get_subscription(),get_protocol_stats(), andget_charge_history().
The frontend is intentionally thin. It should remain a transaction builder and state viewer, not a source of business logic.
contract/src/bench.rs contains instruction-count benchmarks for subscribe(), charge(), pay_per_use(), and a 10-user batch_charge() scenario. These are separate from unit tests and should be used to catch cost regressions.
The benchmark file prints CPU and memory costs at runtime and compares them against budget thresholds. If a change increases cost intentionally, update both the printed baseline comment and the threshold constant together.