Skip to content
 
 

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

🔁 SoroSubs

Set it once. Pay forever. Decentralized subscriptions on Stellar.

SoroSubs is the first permissionless, on-chain recurring payment protocol built on Stellar/Soroban. Businesses, creators, and DAOs register subscription plans on-chain. Subscribers connect their Freighter wallet, approve a one-time USDC allowance, and the protocol autonomously charges each billing cycle — no credit cards, no bank approvals, no intermediaries.

Stellar License: MIT Network: Testnet


screenshot

image image image image

Why SoroSubs?

The $1.5 Trillion Problem

The global subscription economy is worth over $1.5 trillion — and nearly all of it flows through Visa, Mastercard, and Stripe, which extract 2–4% per transaction. For a SaaS business processing $1M/year in subscriptions, that's $20,000–$40,000 in fees paid to intermediaries who add no product value. For creators in emerging markets, these payment rails are simply unavailable.

Every Netflix charge. Every Spotify payment. Every SaaS subscription renewal. It all flows through a small oligopoly of payment networks built in the 1970s.

Why Stellar is the Ideal Settlement Network

Stellar was purpose-built for programmable money transfers:

Property Stellar Ethereum Traditional Rails
Finality ~5 seconds ~12 minutes 1–3 business days
Transaction fee <$0.001 $1–$50+ 2–4%
USDC support Native (Circle-issued) ERC-20 USD only
Throughput 1,000+ TPS ~15 TPS N/A
Access Permissionless Permissionless KYC required

Stellar's near-zero fees and 5-second finality make it the ideal settlement network for recurring micro-payments. A $9.99/month subscription processed on Stellar costs less than $0.001 in fees — vs. $0.40 with Stripe.

SoroSubs: The Missing Primitive

Until now, Stellar had no native protocol for programmable recurring payments. This gap meant:

  • Builders had to create ad-hoc off-chain billing logic for every project
  • No composable standard for subscriptions meant fragmented, non-interoperable implementations
  • Stellar Anchors and payment apps couldn't offer subscription features to users

SoroSubs fills this gap by building directly on Soroban's token primitive: the SEP-41 approve + transfer_from mechanic. Subscribers grant a one-time USDC allowance; from that point forward, the protocol can pull payments without requiring a new signature each cycle.

Ecosystem Impact

SoroSubs directly enables:

  • Subscription SaaS — Teams building on Stellar can offer recurring billing natively on-chain
  • On-chain memberships — DAOs and communities can gate content behind verifiable, on-chain subscriptions
  • Streaming payroll — Employers can create recurring salary disbursements using the same primitives
  • DAO treasury automation — DAOs can automate recurring vendor payments and grants on-chain
  • Stellar Anchors — Anchors (e.g., MoneyGram On-Ramp) can integrate subscription billing for fiat→USDC on-ramps
  • Payment apps — LOBSTR, StellarX, and other wallets can surface subscription management natively

Architecture

┌─────────────────────────────────────────────────────────┐
│                    SoroSubs Protocol                    │
│                                                         │
│  Merchant registers Plan (price + period)               │
│         │                                               │
│         ▼                                               │
│  Subscriber approves USDC allowance to contract         │
│         │                                               │
│         ▼                                               │
│  Subscriber calls subscribe() → first charge fires      │
│         │                                               │
│         ▼                                               │
│  Keeper/bot calls charge() each cycle → transfer_from   │
│         │                                               │
│         ▼                                               │
│  USDC flows: Subscriber → SoroSubs → Merchant           │
│                                    └→ Treasury (fee)    │
└─────────────────────────────────────────────────────────┘

Key Design Decisions

approve + transfer_from as the primitive
Subscribers grant the SoroSubs contract a USDC allowance. The contract uses transfer_from to pull payments on schedule. Subscribers retain custody of their funds — the contract can only pull up to the approved amount.

Permissionless charge() function
Anyone can trigger a billing cycle once it's due. This enables keeper bots (cron services, keeper networks) to process charges at scale without centralized infrastructure. The charge_batch() function processes up to thousands of subscriptions per transaction.

Protocol fee in basis points
A configurable protocol fee (default: 50 bps = 0.5%) is deducted from each charge and forwarded to the treasury. This is transparent and auditable on-chain.

Non-custodial
SoroSubs never holds subscriber funds. The token flow is atomic: subscriber → contract → merchant + treasury within a single transaction.


Repository Structure

sorosubs/
├── contracts/
│   └── sorosubs/
│       ├── Cargo.toml             # Soroban contract manifest
│       └── src/
│           ├── lib.rs             # Core contract logic
│           ├── types.rs           # Data types, errors, events
│           └── test.rs            # Full test suite (22 tests)
│
├── frontend/                      # Next.js 14 app
│   ├── src/
│   │   ├── app/
│   │   │   ├── layout.tsx         # Root layout
│   │   │   ├── page.tsx           # Landing page
│   │   │   ├── dashboard/         # Merchant dashboard
│   │   │   ├── subscribe/         # Browse & subscribe to plans
│   │   │   └── manage/            # Manage active subscriptions
│   │   ├── components/
│   │   │   ├── Navbar.tsx
│   │   │   ├── PlanCard.tsx
│   │   │   └── ui.tsx             # Shared UI primitives
│   │   ├── hooks/
│   │   │   └── useFreighter.ts    # Freighter wallet hook
│   │   └── lib/
│   │       ├── constants.ts       # Network config, formatters
│   │       └── sorosubs.ts        # Contract interaction layer
│   └── package.json
│
├── sdk/                           # sorosubs-sdk npm package
│   └── src/
│       ├── client.ts              # SoroSubsClient class
│       ├── types.ts               # TypeScript types
│       └── index.ts               # Public API barrel
│
├── scripts/
│   └── deploy.js                  # Deployment scripts
│
└── package.json                   # Workspace root

Smart Contract

Contract Functions

Admin

Function Description
initialize(admin, usdc_token, treasury, fee_bps) Deploy and configure the protocol (once)
set_fee(fee_bps) Update the protocol fee (admin only)
set_treasury(treasury) Update the fee treasury address (admin only)

Merchant

Function Description
register_plan(merchant, name, price, period) Create a new subscription plan
deactivate_plan(merchant, plan_id) Stop new subscriptions (existing unaffected)
update_plan(merchant, plan_id, price, period) Change price/period (next cycle)

Subscriber

Function Description
subscribe(subscriber, merchant, plan_id) Subscribe + charge first cycle
cancel(subscriber, merchant, plan_id) Cancel subscription (no refund)

Keeper / Billing

Function Description
charge(subscriber, merchant, plan_id) Charge one subscription when due
charge_batch(charges: Vec<(subscriber, merchant, plan_id)>) Batch charge, skips errors

Read

Function Description
get_plan(merchant, plan_id) Fetch plan details
get_subscription(subscriber, merchant, plan_id) Fetch subscription record
stats() Global: total_plans, total_subs, fee_bps
is_due(subscriber, merchant, plan_id) Check if billing is due

Error Codes

Code Name Description
1 Unauthorized Caller is not authorized
2 PlanNotFound Plan does not exist
3 PlanInactive Plan not accepting subscribers
4 SubscriptionNotFound No subscription found
5 SubscriptionInactive Subscription already cancelled
6 BillingNotDue Too early to charge
7 InsufficientAllowance USDC allowance too low
8 InsufficientBalance Subscriber USDC balance too low
9 InvalidPrice Price must be > 0
10 InvalidPeriod Period must be ≥ 60s
11 InvalidName Name must be 1–64 chars
12 InvalidFeeBps Fee cannot exceed 10%
13 AlreadyInitialized Contract already deployed
14 AlreadySubscribed Already has active subscription

Getting Started

Prerequisites

# Rust + Soroban target
rustup target add wasm32-unknown-unknown

# Stellar CLI
cargo install --locked stellar-cli

# Node.js 18+
node --version

1. Build and Test the Contract

# Run tests
cd contracts/sorosubs
cargo test --features testutils

# Build WASM
cargo build --target wasm32-unknown-unknown --release

# Or from repo root
npm run build:contract
npm run test:contract

2. Deploy to Testnet

# Create a deployer identity
stellar keys generate deployer --network testnet
stellar keys fund deployer --network testnet

# Deploy contract
stellar contract deploy \
  --wasm contracts/sorosubs/target/wasm32-unknown-unknown/release/sorosubs.wasm \
  --source deployer \
  --network testnet

# Initialize (replace with actual addresses)
stellar contract invoke \
  --id $CONTRACT_ID \
  --source deployer \
  --network testnet \
  -- initialize \
  --admin $DEPLOYER_ADDRESS \
  --usdc_token $USDC_CONTRACT_ID \
  --treasury $TREASURY_ADDRESS \
  --fee_bps 50

3. Run the Frontend

# Copy env template
cp frontend/.env.example frontend/.env.local

# Edit .env.local with your contract ID
NEXT_PUBLIC_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
NEXT_PUBLIC_NETWORK=TESTNET

# Install and run
npm install
npm run dev:frontend

4. Use the SDK

npm install sorosubs-sdk @stellar/stellar-sdk
import { SoroSubsClient, TESTNET_CONFIG, usdcToStroops } from "sorosubs-sdk";

const client = new SoroSubsClient({
  ...TESTNET_CONFIG,
  contractId: "C...", // your deployed contract
});

// Merchant: register a plan ($9.99/month)
const planId = await client.registerPlan(
  merchantSigner,
  "Pro Monthly",
  usdcToStroops(9.99),
  30 * 24 * 60 * 60 // 30 days
);

// Subscriber: approve 12 months of USDC and subscribe
await client.approveUsdc(
  subscriberSigner,
  usdcToStroops(9.99) * 12n,
  6_312_000 // expiry ledger ~1 year
);
await client.subscribe(subscriberSigner, merchantAddress, planId);

// Keeper: batch charge all due subscriptions
await client.chargeBatch(keeperSigner, [
  { subscriber: "G...", merchant: "G...", planId: 0 },
  { subscriber: "G...", merchant: "G...", planId: 0 },
]);

// Check stats
const stats = await client.getStats();
console.log(`${stats.total_subs} active subscriptions`);

Frontend Environment Variables

Create frontend/.env.local:

NEXT_PUBLIC_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
NEXT_PUBLIC_USDC_CONTRACT_ID=CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA
NEXT_PUBLIC_NETWORK=TESTNET
NEXT_PUBLIC_RPC_URL=https://soroban-testnet.stellar.org

USDC Token Amounts

SoroSubs uses USDC with 7 decimal places (Stellar standard):

Human amount Stroops
$1.00 USDC 10_000_000
$9.99 USDC 99_900_000
$100.00 USDC 1_000_000_000

Use the SDK helpers:

import { usdcToStroops, stroopsToUsdc } from "sorosubs-sdk";

usdcToStroops(9.99)  // → 99_900_000n
stroopsToUsdc(99_900_000n)  // → 9.99

Security Considerations

  • Allowance exposure: Subscribers should approve only the amount they expect to pay. Over-approving (e.g., i128::MAX) is inadvisable. The protocol reads the allowance before each charge.
  • Inactive subscriptions: Cancelled subscriptions are permanently inactive. A new subscription must be created to resume billing.
  • Keeper trust: charge() is permissionless, but it can only execute when now >= next_bill_at. Early charges are rejected with BillingNotDue.
  • Plan changes: Merchants can update price/period, but changes only apply to the next billing cycle. Subscribers are never silently charged a different amount mid-cycle.
  • Admin key: The admin address can only update fee settings and treasury. It cannot touch subscriber funds or cancel plans.
  • Auditing: All billing events are emitted on-chain (billed, subscribed, cancelled) and can be indexed by any observer.

Roadmap

  • Indexer: Graph-protocol-style indexer for querying plans and subscriptions by address
  • Keeper Network: Decentralized keeper bot with on-chain reward for triggering charge()
  • Free trial support: subscribe_with_trial(trial_period) — first charge deferred
  • Metered billing: charge_metered(amount) — variable-amount billing based on usage
  • Multi-token support: Accept any Stellar asset, not just USDC
  • Webhooks: Off-chain notification service for billing events
  • Discount codes: On-chain promo code support for reduced pricing

Contributing

Pull requests welcome. For major changes, please open an issue first to discuss what you'd like to change.

  1. Fork the repo
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Run the test suite (cargo test --features testutils)
  4. Submit a PR

License

MIT © SoroSubs Contributors

About

a decentralized, permissionless subscription and recurring payment protocol on Stellar. SoroSubs allows businesses, creators, and DAOs to register subscription plans on-chain. Subscribers connect their Freighter wallet, approve a one-time USDC allowance, and the protocol autonomously pulls the recurring payment on each billing cycle —

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages