This document tracks the current public contract surface in contract/src/lib.rs. For error codes, see ERROR-CODES.md, which contains the CONTRACT-34 table. For events, see EVENTS.md.
pub struct Subscription {
pub merchant: Address,
pub amount: i128,
pub interval: u64,
pub last_charged: u64,
pub active: bool,
pub paused: bool,
pub token: Address,
pub referrer: Option<Address>,
pub label: Symbol,
pub trial_duration: u64,
}pub enum ChargeResult {
Charged,
Skipped,
NoSubscription,
Inactive,
Paused,
GracePeriodElapsed,
}pub struct ProtocolStats {
pub active_count: u64,
pub fee_bps: u32,
pub fee_collector: Option<Address>,
pub grace_period: u64,
pub whitelist_enabled: bool,
pub schema_version: u32,
pub contract_paused: bool,
}pub struct HealthReport {
pub is_healthy: bool,
pub contract_paused: bool,
pub token_configured: bool,
pub admin_configured: bool,
pub instance_ttl_ledgers: u32,
pub active_subscription_count: u64,
pub schema_version: u32,
}pub enum DataKey {
Subscription(Address),
Token,
Admin,
GracePeriod,
MerchantWhitelist(Address),
WhitelistEnabled,
MerchantFrozen(Address),
FeeCollector,
FeeBps,
PendingFee,
PendingAdmin,
ActiveCount,
MerchantRevenue(Address),
MerchantRevenueDay(Address, u64),
DailyLimit(Address),
DailySpent(Address),
Referral(Address),
SchemaVersion,
SubscriptionMeta(Address),
ChargeHistory(Address),
GlobalVolumeWindow,
ContractPaused,
MinInterval,
MerchantRevenueHistory(Address),
SubscriberIndex(u64),
SubscriberIndexSize,
MerchantSubCount(Address),
PendingGracePeriod,
}initialize(env: Env, token: Address, admin: Address)
| Name | Type | Description |
|---|---|---|
token |
Address |
SAC token used for subscription payments. |
admin |
Address |
Initial contract admin. |
Auth: none.
Returns: ().
Errors: ContractError::AlreadyInitialized.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source deployer --network testnet -- initialize --token <TOKEN_ADDRESS> --admin <ADMIN_ADDRESS>subscribe(env: Env, user: Address, merchant: Address, amount: i128, interval: u64, token: Address, trial_period: Option<u64>, referrer: Option<Address>)
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber and transaction signer. |
merchant |
Address |
Merchant receiving funds. |
amount |
i128 |
Recurring amount in stroops. |
interval |
u64 |
Billing interval in seconds. |
token |
Address |
Token contract used for this subscription. |
trial_period |
Option<u64> |
Optional delay before the first charge. |
referrer |
Option<Address> |
Optional referrer address. |
Auth: user.require_auth().
Returns: ().
Errors: ContractError::AmountMustBePositive, ContractError::IntervalMustBePositive, ContractError::MerchantNotWhitelisted, ContractError::ContractPausedError, ContractError::InvalidTokenAddress, ContractError::IntervalTooShort, ContractError::InsufficientAllowance.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- subscribe --user <USER_ADDRESS> --merchant <MERCHANT_ADDRESS> --amount 50000000 --interval 2592000 --token <TOKEN_ADDRESS>subscribe_with_metadata(env: Env, user: Address, merchant: Address, amount: i128, interval: u64, token: Address, trial_period: Option<u64>, referrer: Option<Address>, label: String)
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber and transaction signer. |
merchant |
Address |
Merchant receiving funds. |
amount |
i128 |
Recurring amount in stroops. |
interval |
u64 |
Billing interval in seconds. |
token |
Address |
Token contract used for this subscription. |
trial_period |
Option<u64> |
Optional delay before the first charge. |
referrer |
Option<Address> |
Optional referrer address. |
label |
String |
Subscription label, max 64 bytes. |
Auth: user.require_auth().
Returns: ().
Errors: same as subscribe(), plus ContractError::MetadataLabelTooLong.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- subscribe_with_metadata --user <USER_ADDRESS> --merchant <MERCHANT_ADDRESS> --amount 50000000 --interval 2592000 --token <TOKEN_ADDRESS> --label procharge(env: Env, user: Address)
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber to charge. |
Auth: none. This is permissionless for keeper use.
Returns: ().
Errors: ContractError::NoSubscriptionFound, ContractError::SubscriptionNotActive, ContractError::SubscriptionPaused, ContractError::IntervalNotElapsed, ContractError::GracePeriodElapsed, ContractError::NotInitialized.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <KEEPER_KEY> --network testnet -- charge --user <USER_ADDRESS>extend_subscription_ttl(env: Env, user: Address)
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber whose TTL should be refreshed. |
Auth: none.
Returns: ().
Errors: none beyond storage access.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- extend_subscription_ttl --user <USER_ADDRESS>pay_per_use(env: Env, user: Address, amount: i128)
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber and signer. |
amount |
i128 |
One-time payment amount in stroops. |
Auth: user.require_auth().
Returns: ().
Errors: ContractError::AmountMustBePositive, ContractError::AmountExceedsMaximum, ContractError::NoSubscriptionFound, ContractError::SubscriptionNotActive, ContractError::SubscriptionPaused.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- pay_per_use --user <USER_ADDRESS> --amount 1000000cancel(env: Env, user: Address)
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber and signer. |
Auth: user.require_auth().
Returns: ().
Errors: ContractError::NoSubscriptionFound.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- cancel --user <USER_ADDRESS>pause(env: Env, user: Address)
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber and signer. |
Auth: user.require_auth().
Returns: ().
Errors: ContractError::NoSubscriptionFound, ContractError::SubscriptionNotActive.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- pause --user <USER_ADDRESS>resume(env: Env, user: Address)
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber and signer. |
Auth: user.require_auth().
Returns: ().
Errors: ContractError::NoSubscriptionFound, ContractError::SubscriptionNotActive.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- resume --user <USER_ADDRESS>transfer_admin(env: Env, new_admin: Address)
| Name | Type | Description |
|---|---|---|
new_admin |
Address |
Proposed admin address. |
Auth: current admin only.
Returns: ().
Errors: none beyond auth.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- transfer_admin --new_admin <NEW_ADMIN_ADDRESS>accept_admin(env: Env)
Auth: proposed admin only.
Returns: ().
Errors: expect("no pending admin") if no proposal exists.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <NEW_ADMIN_KEY> --network testnet -- accept_adminis_contract_paused(env: Env) -> bool
Auth: none.
Returns: bool.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- is_contract_pausedget_admin(env: Env) -> Option<Address>
Auth: none.
Returns: Option<Address>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_adminget_token(env: Env) -> Option<Address>
Auth: none.
Returns: Option<Address>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_tokenupgrade(env: Env, new_wasm_hash: BytesN<32>)
| Name | Type | Description |
|---|---|---|
new_wasm_hash |
BytesN<32> |
New contract WASM hash. |
Auth: none in the current implementation.
Returns: ().
Errors: none beyond host/deployer failures.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- upgrade --new_wasm_hash <WASM_HASH>get_subscription(env: Env, user: Address) -> Option<Subscription>
| Name | Type | Description |
|---|---|---|
user |
Address |
Subscriber address to look up. |
Auth: none.
Returns: Option<Subscription>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_subscription --user <USER_ADDRESS>next_charge_at(env: Env, user: Address) -> Option<u64>
Auth: none.
Returns: Option<u64>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- next_charge_at --user <USER_ADDRESS>is_charge_due(env: Env, user: Address) -> bool
Auth: none.
Returns: bool.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- is_charge_due --user <USER_ADDRESS>get_trial_end(env: Env, user: Address) -> Option<u64>
Auth: none.
Returns: Option<u64>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_trial_end --user <USER_ADDRESS>propose_grace_period(env: Env, seconds: u64)
| Name | Type | Description |
|---|---|---|
seconds |
u64 |
Proposed grace period in seconds. |
Auth: admin only.
Returns: ().
Errors: ContractError::NoPendingProposal is not used here; seconds is validated in the helper.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- propose_grace_period --seconds 86400commit_grace_period(env: Env)
Auth: admin only.
Returns: ().
Errors: ContractError::NoPendingProposal.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- commit_grace_periodget_grace_period(env: Env) -> u64
Auth: none.
Returns: u64.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_grace_periodset_subscription_amount(env: Env, user: Address, new_amount: i128)
Auth: admin only.
Returns: ().
Errors: ContractError::NoSubscriptionFound, ContractError::AmountMustBePositive, ContractError::AmountExceedsMaximum, ContractError::ContractPausedError.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- set_subscription_amount --user <USER_ADDRESS> --new_amount 50000000set_subscription_interval(env: Env, user: Address, new_interval: u64)
Auth: admin only.
Returns: ().
Errors: ContractError::NoSubscriptionFound, ContractError::IntervalTooShort, ContractError::ContractPausedError.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- set_subscription_interval --user <USER_ADDRESS> --new_interval 604800set_min_interval(env: Env, seconds: u64)
Auth: admin only.
Returns: ().
Errors: panics if seconds == 0.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- set_min_interval --seconds 3600get_min_interval(env: Env) -> u64
Auth: none.
Returns: u64.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_min_intervaladd_merchant(env: Env, merchant: Address)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- add_merchant --merchant <MERCHANT_ADDRESS>remove_merchant(env: Env, merchant: Address)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- remove_merchant --merchant <MERCHANT_ADDRESS>set_whitelist_enabled(env: Env, enabled: bool)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- set_whitelist_enabled --enabled trueis_whitelist_enabled(env: Env) -> bool
Auth: none.
Returns: bool.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- is_whitelist_enabledis_merchant_whitelisted(env: Env, merchant: Address) -> bool
Auth: none.
Returns: bool.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- is_merchant_whitelisted --merchant <MERCHANT_ADDRESS>freeze_merchant(env: Env, merchant: Address)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- freeze_merchant --merchant <MERCHANT_ADDRESS>unfreeze_merchant(env: Env, merchant: Address)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- unfreeze_merchant --merchant <MERCHANT_ADDRESS>bump_merchant_revenue_day(env: Env, merchant: Address, day: u64)
Auth: none.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- bump_merchant_revenue_day --merchant <MERCHANT_ADDRESS> --day 20000prune_merchant_revenue_days(env: Env, merchant: Address, days: Vec<u64>)
Auth: none in the wrapper.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- prune_merchant_revenue_days --merchant <MERCHANT_ADDRESS> --days '[20000,20001]'get_merchant_revenue_day(env: Env, merchant: Address, day: u64) -> i128
Auth: none.
Returns: i128.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_merchant_revenue_day --merchant <MERCHANT_ADDRESS> --day 20000is_merchant_frozen(env: Env, merchant: Address) -> bool
Auth: none.
Returns: bool.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- is_merchant_frozen --merchant <MERCHANT_ADDRESS>get_fee(env: Env) -> Option<(Address, u32)>
Auth: none.
Returns: Option<(Address, u32)>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_feepropose_fee(env: Env, collector: Address, bps: u32)
Auth: admin only.
Returns: ().
Errors: ContractError::InvalidFeeBps.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- propose_fee --collector <COLLECTOR_ADDRESS> --bps 100commit_fee(env: Env)
Auth: admin only.
Returns: ().
Errors: ContractError::NoPendingProposal.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- commit_feebatch_charge(env: Env, users: Vec<Address>) -> Vec<ChargeResult>
Auth: none.
Returns: Vec<ChargeResult>.
Errors: the function returns per-user results instead of aborting on ordinary charge failures.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <KEEPER_KEY> --network testnet -- batch_charge --users '["<USER_A>","<USER_B>"]'get_active_count(env: Env) -> u64
Auth: none.
Returns: u64.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_active_countget_subscriber_count(env: Env) -> u64
Auth: none.
Returns: u64.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_subscriber_countget_subscriber_at(env: Env, index: u64) -> Option<Address>
Auth: none.
Returns: Option<Address>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_subscriber_at --index 0get_subscriber_page(env: Env, offset: u64, limit: u32) -> Vec<Address>
Auth: none.
Returns: Vec<Address>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_subscriber_page --offset 0 --limit 10get_merchant_revenue(env: Env, merchant: Address) -> i128
Auth: none.
Returns: i128.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_merchant_revenue --merchant <MERCHANT_ADDRESS>get_merchant_revenue_history(env: Env, merchant: Address, days: u32) -> Vec<i128>
Auth: none.
Returns: Vec<i128>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_merchant_revenue_history --merchant <MERCHANT_ADDRESS> --days 7clear_merchant_revenue_history(env: Env, merchant: Address)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- clear_merchant_revenue_history --merchant <MERCHANT_ADDRESS>get_merchant_subscriber_count(env: Env, merchant: Address) -> u64
Auth: none.
Returns: u64.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_merchant_subscriber_count --merchant <MERCHANT_ADDRESS>reset_merchant_revenue(env: Env, merchant: Address)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- reset_merchant_revenue --merchant <MERCHANT_ADDRESS>withdraw_merchant_revenue(env: Env, merchant: Address)
| Name | Type | Description |
|---|---|---|
merchant |
Address |
Merchant withdrawing accrued revenue. |
Auth: merchant.require_auth().
Returns: ().
Errors: ContractError::NotInitialized, ContractError::ZeroBalanceAvailable.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <MERCHANT_KEY> --network testnet -- withdraw_merchant_revenue --merchant <MERCHANT_ADDRESS>set_daily_limit(env: Env, user: Address, limit: i128)
Auth: user.require_auth().
Returns: ().
Errors: ContractError::AmountMustBePositive.
CLI example:
See also: Daily Spending Limits Guide for a conceptual overview of the pay_per_use spending cap.
For a complete list of all error codes returned by the contract, see ERROR-CODES.md.
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- set_daily_limit --user <USER_ADDRESS> --limit 50000000remove_daily_limit(env: Env, user: Address)
Auth: user.require_auth().
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- remove_daily_limit --user <USER_ADDRESS>get_daily_limit(env: Env, user: Address) -> Option<i128>
Auth: none.
Returns: Option<i128>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_daily_limit --user <USER_ADDRESS>get_daily_spent(env: Env, user: Address) -> i128
Auth: none.
Returns: i128.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_daily_spent --user <USER_ADDRESS>get_referrer(env: Env, user: Address) -> Option<Address>
Auth: none.
Returns: Option<Address>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_referrer --user <USER_ADDRESS>migrate(env: Env, users: Vec<Address>)
Auth: none.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- migrate --users '["<USER_ADDRESS>"]'get_schema_version(env: Env) -> u32
Auth: none.
Returns: u32.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_schema_versionset_metadata(env: Env, user: Address, label: String)
Auth: user.require_auth().
Returns: ().
Errors: ContractError::MetadataLabelTooLong.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- set_metadata --user <USER_ADDRESS> --label proget_metadata(env: Env, user: Address) -> Option<String>
Auth: none.
Returns: Option<String>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_metadata --user <USER_ADDRESS>get_subscription_label(env: Env, user: Address) -> Option<String>
Auth: none.
Returns: Option<String>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_subscription_label --user <USER_ADDRESS>clear_metadata(env: Env, user: Address)
Auth: user.require_auth().
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- clear_metadata --user <USER_ADDRESS>get_charge_history(env: Env, user: Address) -> Vec<u64>
Auth: none.
Returns: Vec<u64>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_charge_history --user <USER_ADDRESS>get_protocol_stats(env: Env) -> ProtocolStats
Auth: none.
Returns: ProtocolStats.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_protocol_statspause_contract(env: Env)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- pause_contractunpause_contract(env: Env)
Auth: admin only.
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <ADMIN_KEY> --network testnet -- unpause_contractset_initial_admin(env: Env, admin: Address)
Auth: none.
Returns: ().
Errors: panics if the admin is already set.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- set_initial_admin --admin <ADMIN_ADDRESS>contract_health_check(env: Env) -> HealthReport
Auth: none.
Returns: HealthReport.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- contract_health_checkclear_charge_history(env: Env, user: Address)
Auth: user.require_auth().
Returns: ().
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- clear_charge_history --user <USER_ADDRESS>get_charge_history_page(env: Env, user: Address, offset: u32, limit: u32) -> Vec<u64>
Auth: none.
Returns: Vec<u64>.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --network testnet -- get_charge_history_page --user <USER_ADDRESS> --offset 0 --limit 12transfer_subscription(env: Env, user: Address, new_user: Address)
Auth: user.require_auth().
Returns: ().
Errors: ContractError::ContractPausedError, ContractError::NoSubscriptionFound, ContractError::SubscriptionAlreadyActive.
CLI example:
soroban contract invoke --id <CONTRACT_ID> --source <USER_KEY> --network testnet -- transfer_subscription --user <USER_ADDRESS> --new_user <NEW_USER_ADDRESS>All amounts are in stroops. 1 XLM = 10,000,000 stroops. Intervals are
See EVENTS.md for the complete event schema reference.
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The payer. Must match the transaction signer. |
amount |
i128 |
Stroops to transfer. Must be > 0. |
Auth: user.require_auth().
What it does:
- Loads the subscription for
user - Asserts
active == true - Calls
transfer_from(contract, user, merchant, amount)on the token contract
Events emitted
topic: ("pay_per_use", user)
data: (merchant, amount)
Errors
| Condition | Panic message |
|---|---|
amount <= 0 |
"amount must be positive" |
| No subscription exists | "no subscription found" |
| Subscription is cancelled | "subscription is not active" |
| Subscription is paused | "subscription is paused" |
| Insufficient allowance | Host error from token contract |
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <USER_KEY> \
--network testnet \
-- pay_per_use \
--user <USER_ADDRESS> \
--amount 1000000Temporarily halts charges for a subscription. The subscription record is preserved and can be resumed at any time. Both charge() and pay_per_use() will panic while paused.
pause(env: Env, user: Address)
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber. Must match the transaction signer. |
Auth: user.require_auth().
Events emitted
topic: ("paused", user)
data: ()
Errors
| Condition | Panic message |
|---|---|
| No subscription exists | "no subscription found" |
| Subscription is cancelled | "subscription is not active" |
| Subscription already paused | "subscription is already paused" |
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <USER_KEY> \
--network testnet \
-- pause \
--user <USER_ADDRESS>Resumes a paused subscription, re-enabling charge() and pay_per_use().
resume(env: Env, user: Address)
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber. Must match the transaction signer. |
Auth: user.require_auth().
Events emitted
topic: ("resumed", user)
data: ()
Errors
| Condition | Panic message |
|---|---|
| No subscription exists | "no subscription found" |
| Subscription is cancelled | "subscription is not active" |
| Subscription is not paused | "subscription is not paused" |
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <USER_KEY> \
--network testnet \
-- resume \
--user <USER_ADDRESS>Deactivates a subscription. The subscription record remains in storage with active = false. No further charges can be made.
cancel(env: Env, user: Address)
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber. Must match the transaction signer. |
Auth: user.require_auth().
Events emitted
topic: ("cancelled", user)
data: ()
Errors
| Condition | Panic message |
|---|---|
| No subscription exists | "no subscription found" |
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <USER_KEY> \
--network testnet \
-- cancel \
--user <USER_ADDRESS>Read-only view function. Returns the subscription for a given user, or None if none exists.
get_subscription(env: Env, user: Address) -> Option<Subscription>
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to look up. |
Auth: None.
Returns: Option<Subscription> — None if no subscription exists for this address.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_subscription \
--user <USER_ADDRESS>Read-only view function. Returns the Unix timestamp of the next scheduled charge for a user.
next_charge_at(env: Env, user: Address) -> Option<u64>
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to look up. |
Auth: None.
Returns: Option<u64> — Returns None if:
- No subscription exists for the user
- The subscription is inactive (cancelled)
Returns Some(last_charged + interval) if the subscription is active.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- next_charge_at \
--user <USER_ADDRESS>Charges multiple subscribers in a single transaction. Individual failures do not abort the batch — every address is processed and its outcome is returned.
batch_charge(env: Env, users: Vec<Address>) -> Vec<ChargeResult>
Parameters
| Name | Type | Description |
|---|---|---|
users |
Vec<Address> |
List of subscriber addresses to attempt charging. |
Auth: None. Same permissionless model as charge().
Returns: Vec<ChargeResult> — one entry per input address, in order.
pub enum ChargeResult {
Charged, // funds transferred successfully
Skipped, // interval has not elapsed yet
NoSubscription, // no subscription found for this address
Inactive, // subscription is cancelled
Paused, // subscription is paused
GracePeriodElapsed, // charge window has closed
}Storage written: DataKey::Subscription(user) updated for each Charged result. DataKey::MerchantRevenue(merchant) incremented for each Charged result.
Events emitted: ("charged", user) for each successfully charged user.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <KEEPER_KEY> \
--network testnet \
-- batch_charge \
--users '["<USER_A>","<USER_B>","<USER_C>"]'Returns the current number of active subscriptions. Incremented by subscribe(), decremented by cancel().
get_active_count(env: Env) -> u64
Auth: None.
Returns: u64 — total active subscriptions.
Storage read: DataKey::ActiveCount in instance storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_active_countReturns the cumulative amount charged to a merchant's subscribers across all charge() and pay_per_use() calls.
get_merchant_revenue(env: Env, merchant: Address) -> i128
Parameters
| Name | Type | Description |
|---|---|---|
merchant |
Address |
The merchant address to query. |
Auth: None.
Returns: i128 — total stroops received by this merchant. Returns 0 if no charges have occurred.
Storage read: DataKey::MerchantRevenue(merchant) in persistent storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_merchant_revenue \
--merchant <MERCHANT_ADDRESS>Sets a daily spending cap for pay_per_use() for the calling user. The limit is stored in temporary storage and resets automatically after approximately one day (~17,280 ledgers at 5 s/ledger).
For a detailed conceptual guide on how limits and TTL expirations work, see Daily Spending Limits.
set_daily_limit(env: Env, user: Address, limit: i128)
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber. Must match the transaction signer. |
limit |
i128 |
Maximum stroops spendable via pay_per_use() per day. Must be > 0. |
Auth: user.require_auth().
Storage written: DataKey::DailyLimit(user) in temporary storage with TTL of ~1 day.
Enforcement: Every pay_per_use() call checks DailySpent(user) + amount <= DailyLimit(user) before transferring. The running total is tracked in DataKey::DailySpent(user) (also temporary, same TTL).
Errors
| Condition | Panic message |
|---|---|
limit <= 0 |
"limit must be positive" |
| Spend would exceed limit | "daily spending limit exceeded" |
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <USER_KEY> \
--network testnet \
-- set_daily_limit \
--user <USER_ADDRESS> \
--limit 50000000Returns the current daily spending limit for the calling user, or None if no limit is set.
get_daily_limit(env: Env, user: Address) -> Option<i128>
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to query. |
Auth: None.
Returns: Option<i128> — current daily limit in stroops, or None if unset.
Storage read: DataKey::DailyLimit(user) in temporary storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_daily_limit \
--user <USER_ADDRESS>Returns the amount spent today by the calling user via pay_per_use().
get_daily_spent(env: Env, user: Address) -> i128
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to query. |
Auth: None.
Returns: i128 — amount spent today in stroops. Returns 0 if no spend is recorded.
Storage read: DataKey::DailySpent(user) in temporary storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_daily_spent \
--user <USER_ADDRESS>Extends the TTL of a user's subscription record in persistent storage.
extend_subscription_ttl(env: Env, user: Address)
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to extend TTL for. |
Auth: None.
Storage written: Extends TTL of DataKey::Subscription(user) in persistent storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- extend_subscription_ttl \
--user <USER_ADDRESS>Returns the trial end timestamp if the user is in a trial period.
get_trial_end(env: Env, user: Address) -> Option<u64>
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to query. |
Auth: None.
Returns: Option<u64> — Unix timestamp when trial ends, or None if no trial or no subscription.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_trial_end \
--user <USER_ADDRESS>Adds a merchant to the whitelist. Only the contract admin can call this.
add_merchant(env: Env, merchant: Address)
Parameters
| Name | Type | Description |
|---|---|---|
merchant |
Address |
The merchant address to whitelist. |
Auth: Admin only.
Storage written: DataKey::MerchantWhitelist(merchant) in persistent storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <ADMIN_KEY> \
--network testnet \
-- add_merchant \
--merchant <MERCHANT_ADDRESS>Removes a merchant from the whitelist. Only the contract admin can call this.
remove_merchant(env: Env, merchant: Address)
Parameters
| Name | Type | Description |
|---|---|---|
merchant |
Address |
The merchant address to remove from the whitelist. |
Auth: Admin only.
Storage written: Removes DataKey::MerchantWhitelist(merchant) from persistent storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <ADMIN_KEY> \
--network testnet \
-- remove_merchant \
--merchant <MERCHANT_ADDRESS>Enables or disables the merchant whitelist. Only the contract admin can call this.
set_whitelist_enabled(env: Env, enabled: bool)
Parameters
| Name | Type | Description |
|---|---|---|
enabled |
bool |
True to enable the whitelist, false to disable. |
Auth: Admin only.
Storage written: DataKey::WhitelistEnabled in instance storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <ADMIN_KEY> \
--network testnet \
-- set_whitelist_enabled \
--enabled trueReturns per-day revenue for the given merchant for the last days days, oldest to newest.
get_merchant_revenue_history(env: Env, merchant: Address, days: u32) -> Vec<i128>
Parameters
| Name | Type | Description |
|---|---|---|
merchant |
Address |
The merchant address to query. |
days |
u32 |
The number of days of history to retrieve. |
Auth: None.
Returns: Vec<i128> — Daily revenue in stroops, ordered oldest to newest.
Storage read: DataKey::MerchantRevenueDay(merchant, day) in persistent storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_merchant_revenue_history \
--merchant <MERCHANT_ADDRESS> \
--days 7Returns the referrer address recorded for a subscriber.
get_referrer(env: Env, user: Address) -> Option<Address>
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to query. |
Auth: None.
Returns: Option<Address> — None if no referrer was recorded.
Storage read: DataKey::Referral(user) in persistent storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_referrer \
--user <USER_ADDRESS>Upgrades contract storage to the latest schema version. Safe to call multiple times.
migrate(env: Env)
Auth: None (admin restriction can be added in future versions).
Storage written: DataKey::SchemaVersion in instance storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- migrateReturns the current storage schema version.
get_schema_version(env: Env) -> u32
Auth: None.
Returns: u32 — defaults to 1 before the first migrate() call.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_schema_versionAttaches a short label string (e.g. plan name) to the caller's subscription.
set_metadata(env: Env, user: Address, label: String)
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber. Must match the transaction signer. |
label |
String |
Short display label (e.g. "pro", "basic"). |
Auth: user.require_auth().
Storage written: DataKey::SubscriptionMeta(user) in persistent storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--source <USER_KEY> \
--network testnet \
-- set_metadata \
--user <USER_ADDRESS> \
--label proReturns the metadata label for a subscriber.
get_metadata(env: Env, user: Address) -> Option<String>
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to query. |
Auth: None.
Returns: Option<String> — None if no label has been set.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_metadata \
--user <USER_ADDRESS>Returns the last (up to 12) charge timestamps for a subscriber, ordered oldest → newest.
get_charge_history(env: Env, user: Address) -> Vec<u64>
Parameters
| Name | Type | Description |
|---|---|---|
user |
Address |
The subscriber address to query. |
Auth: None.
Returns: Vec<u64> — UNIX timestamps of successful charge() calls. Empty if no charges have occurred.
Storage read: DataKey::ChargeHistory(user) in persistent storage.
CLI example
soroban contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- get_charge_history \
--user <USER_ADDRESS>Administrative utilities for detecting and repairing corrupted subscription records after migrations or contract upgrades.
Read-only integrity check for a subscriber address.
validate_subscription(env: Env, user: Address) -> SubscriptionValidationReport
Returns SubscriptionValidationReport
| Field | Type | Description |
|---|---|---|
is_valid |
bool |
true when no inconsistencies are detected |
violations |
Vec<String> |
General integrity violations |
missing_records |
Vec<String> |
Missing auxiliary records (history, metadata, etc.) |
invalid_state_transitions |
Vec<String> |
Illegal active/paused/cancelled state combinations |
corrupted_references |
Vec<String> |
Broken merchant/token/referrer references |
Auth: None (read-only simulation).
Frontend: Exposed in the Admin Dashboard → Subscription Repair panel.
Repairs detected subscription inconsistencies for a user.
repair_subscription(env: Env, user: Address) -> u32
Auth: Contract admin only (require_admin).
Returns: Count of fixed inconsistencies (also emitted in the subscription_repaired event).
Event emitted
| Event name | Topic | Data |
|---|---|---|
subscription_repaired |
("subscription_repaired", user_address) |
fixed_inconsistencies: u32 |
Frontend authorization: The repair button is enabled only when the connected Freighter wallet matches the on-chain admin returned by get_admin.
All amounts are in stroops — the smallest unit of a Stellar token.
| Amount | Stroops |
|---|---|
| 1 XLM | 10,000,000 |
| 0.5 XLM | 5,000,000 |
| 0.0000001 XLM | 1 |
All intervals are in seconds.
| Interval | Seconds |
|---|---|
| 1 day | 86,400 |
| 1 week | 604,800 |
| 30 days | 2,592,000 |
All events can be indexed by listening to the Stellar RPC event stream for the FlowPay contract ID.
For a complete reference of all events with detailed schemas and examples, see EVENTS.md.
| Event name | Topic | Data |
|---|---|---|
subscribed |
("subscribed", user_address) |
(merchant, amount, interval) |
charged |
("charged", user_address) |
(merchant, amount, timestamp) |
pay_per_use |
("pay_per_use", user_address) |
(merchant, amount) |
cancelled |
("cancelled", user_address) |
() |
paused |
("paused", user_address) |
() |
resumed |
("resumed", user_address) |
() |
referred |
("referred", user_address) |
referrer_address |
subscription_repaired |
("subscription_repaired", user_address) |
fixed_inconsistencies: u32 |
All error conditions are returned as ContractError values. Client SDKs can decode these programmatically. Each variant is identified by its u32 discriminant.
| Code | Variant | Description |
|---|---|---|
| 1 | AlreadyInitialized |
initialize() was called on an already-initialized contract. |
| 2 | AmountMustBePositive |
A payment or subscription amount was zero or negative. |
| 3 | IntervalMustBePositive |
A subscription interval was zero. |
| 4 | NoSubscriptionFound |
No subscription record exists for the given user. |
| 5 | SubscriptionInactive |
The subscription exists but is cancelled or paused. |
| 6 | IntervalNotElapsed |
charge() was called before the billing interval elapsed. |
| 7 | NotInitialized |
A contract function was called before initialize(). |
| 8 | InsufficientAllowance |
The user's token allowance is below the subscription amount. |
| 9 | GracePeriodElapsed |
The charge grace period has passed; the subscription cannot be charged. |
| 10 | MerchantNotWhitelisted |
The merchant is not on the whitelist (when whitelist is enabled). |
| 11 | ContractPaused |
The contract is paused; all user-facing write operations are blocked. |
| 24 | DailyLimitExceeded |
A pay_per_use() call would exceed the user's configured daily spending limit. |