Skip to main content

Subscriptions System & Service Registry

The Shamwari Subscriptions System (nxt.subscriptions.SubscriptionsHome) provides a protocol-native, decentralized recurring revenue infrastructure built directly into the core Shamwari ledger runtime.

By executing subscription management natively within nxt.subscriptions—rather than relying on third-party smart contracts or centralized billing aggregators—Shamwari eliminates payment processor fees, chargeback vulnerabilities, card-token storage risks, and virtual machine execution overhead. The system enables providers to establish flexible multi-tier subscription plans, receive recurring payments in any native holding asset (fiat stablecoin, platform coin, or custom totem asset), and grant tamper-proof, block-height-verified access entitlements to subscriber accounts across BetaChains.


Core Capabilities & Architecture

  • Protocol-Native Recurring Revenue: Managed directly by nxt.subscriptions, bypassing VM smart contract execution to eliminate reentrancy risks and gas friction.
  • Multi-Holding Asset Settlement: Supports payments denominated in any HoldingType—including CURRENCY (ShamwariPay fiat/stablecoins), COIN (BetaChain native coins), or TOTEM (custom digital assets/tokens).
  • JSON-Serialized Multi-Tier Plans: Providers can define unlimited billing tiers (e.g., daily, monthly, annual, or custom block spans) within a single service registration using JSON-serialized SubscriptionPlan arrays.
  • Strict Payload Validation: Enforces a hard 32 KB32 \text{ KB} ceiling (MAX_PLANS_BYTE_SIZE) on serialized plan specifications via SubscriptionPlanValidator to protect ledger state.
  • Block-Height Entitlement Engine: Entitlements are tracked on-chain in AccountSubscription records, evaluating subscription validity directly against blockchain block height (expirationHeight>currentHeight\text{expirationHeight} > \text{currentHeight}).
  • Non-Loss Auto-Renewal Logic: Renewal transactions extend active subscriptions seamlessly from their existing expiry height (currentExpirationHeight+duration\text{currentExpirationHeight} + \text{duration}) rather than resetting from the transaction block height, ensuring early renewals lose zero paid access time.
  • Integrated Fiscal Tax Withholding: Payment execution automatically calculates and credits protocol taxes via CurrencyTaxRecord for CURRENCY holding payments before disbursing net revenue to provider accounts.
  • Full-Text Searchable Service Registry: Services are indexed in public.subscription_service using full-text search capabilities (ft.score), enabling native service discovery by keywords and provider IDs.
  • Immutable Payment Audit Trail: Every subscription settlement generates an append-only, versioned record in public.subscription_payment for compliance and financial auditing.

Data Model & Relational Schema

The Subscriptions System maintains two primary versioned entity tables per BetaChain schema, indexed by transaction IDs, provider IDs, subscriber IDs, block height, and timestamp.

1. Primary Subscription Entities

TablePrimary Key / IndexEntity ClassDescription
public.subscription_serviceid, heightSubscriptionServiceMaster registry table tracking service definitions, provider accounts, holding parameters, JSON plans, and active status.
public.subscription_paymentid, heightSubscriptionPaymentAppend-only transaction audit log recording subscriber payments, selected plans, amounts, and tax deductions.

2. Service Definition Record (public.subscription_service)

FieldTypeDescription
idlongTransaction ID of the initial service registration (globally unique handle).
full_hashbyte[]Full 32-byte cryptographic hash of the registration transaction.
provider_idlongAccount ID of the service provider receiving recurring payments.
nameStringHuman-readable name of the subscription service (full-text indexed).
descriptionStringDetailed service description and capabilities (full-text indexed).
holding_idlongSpecific ID of the currency or totem asset accepted for payment (00 for native chain coin).
holding_typebyteEnum code for accepted payment asset: CURRENCY (11), COIN (22), or TOTEM (33).
plansVARCHAR (JSON)Serialized JSON array of available SubscriptionPlan tiers.
timestampintBlockchain timestamp (seconds since epoch) when the service was registered.
is_deletedbooleanFlag set to true if the provider decommissions/discontinues the service.
heightintBlock height of the latest entity modification.
latestbooleanVersioning flag denoting the current active record state.

3. Payment Settlement Record (public.subscription_payment)

FieldTypeDescription
idlongTransaction ID of the payment transaction.
subscriber_idlongAccount ID of the subscriber making the payment.
service_idlongTarget SubscriptionService ID being purchased/renewed.
provider_idlongReceiving provider account ID.
holding_idlongAsset ID utilized for payment settlement.
holding_typebyteAsset type code (CURRENCY, COIN, or TOTEM).
planStringSpecific name of the plan tier selected by the subscriber.
amountlongTotal price paid in quantum units (QNTQNT).
taxlongTax amount (QNTQNT) withheld and credited to the BetaChain tax collector.
timestampintTimestamp of payment execution.
heightintBlock height at which payment was confirmed and entitlement extended.

4. Subscription Plan Object Structure (SubscriptionPlan)

Each billing tier within a service is represented by an immutable SubscriptionPlan object and serialized into JSON for database storage:

FieldData TypeDescription
nameStringUnique tier identifier (e.g., "Monthly-Pro", "Annual-Enterprise").
descriptionStringFeature summary or SLA description for the tier.
durationintService duration in blockchain blocks (7200 blocks24 hours7200 \text{ blocks} \approx 24 \text{ hours}).
amountQNTlongRequired payment amount in atomic units (QNTQNT) of the specified holding.
metadataMap<String, String>Custom key-value pairs for domain-specific parameters (e.g., claim limits, API quotas).

Subscription Lifecycle & State Machine

A subscription service transitions through operational states managed by provider transactions and subscriber interactions:

┌───────────────────────────────────────────────┐
│ addSubscriptionService() / Registration │
└───────────────────────┬───────────────────────┘


┌───────────────────────────────────────┐
│ ACTIVE SERVICE │
│ (is_deleted == false) │
└───────┬───────────────┬───────┬───────┘
│ │ │
updateServicePlans() │ │ │ discontinueService()
(Modifies JSON Array) │ │ │ (Decommissions Service)
▼ │ ▼
┌───────────────┐ │ ┌───────────────────────┐
│ Updated Tiers │ │ │ DISCONTINUED SERVICE │
└───────────────┘ │ │ (is_deleted == true) │
│ └───────────────────────┘
│ ▲
│ No Payments │
│ Allowed │
▼ │
┌───────────────────────────────────────┴┐
│ addSubscriptionPayment() │
│ (Subscriber Debited / Provider Paid) │
└───────────────────────┬────────────────┘


┌────────────────────────────────────────┐
│ AccountSubscription Entitlement │
│ expirationHeight = max(current, old) │
│ + plan.duration │
└────────────────────────────────────────┘

Validation Rules & Payload Limits (SubscriptionPlanValidator)

To maintain chain efficiency and protect nodes against memory exhaustion or storage inflation, all plan structures undergo byte-level validation during transaction submission and verification:

1. Payload Size Constraint

The total serialized byte size of a service's SubscriptionPlan[] array must not exceed the system threshold:

TotalPlansSizeMAX_PLANS_BYTE_SIZE=32,768 bytes (32 KB)\text{TotalPlansSize} \le \text{MAX\_PLANS\_BYTE\_SIZE} = 32,768 \text{ bytes (32 KB)}

If a provider attempts to register or update a service exceeding 32 KB32 \text{ KB}, SubscriptionPlanValidator.validatePlansByteSize() throws a NxtException.NotValidException.


2. Binary Serialization Calculation Formula

The byte calculation executed by SubscriptionPlanValidator.calculatePlansByteSize() measures the precise wire length of the plan array:

TotalSize=1+i=1N(1+Sname+2+Sdesc+4+8+1+k=1Mi(1+Kk+2+Vk))\text{TotalSize} = 1 + \sum_{i=1}^{N} \left( 1 + |S_{\text{name}}| + 2 + |S_{\text{desc}}| + 4 + 8 + 1 + \sum_{k=1}^{M_i} (1 + |K_k| + 2 + |V_k|) \right)

Where:

  • 1 byte1 \text{ byte} prefix for array length NN.
  • For each plan ii:
    • 1 byte1 \text{ byte} name length indicator +Sname+ |S_{\text{name}}| (UTF-8 bytes).
    • 2 bytes2 \text{ bytes} description length indicator +Sdesc+ |S_{\text{desc}}| (UTF-8 bytes).
    • 4 bytes4 \text{ bytes} for duration (32-bit integer).
    • 8 bytes8 \text{ bytes} for amountQNT (64-bit long integer).
    • 1 byte1 \text{ byte} metadata entry count MiM_i.
    • For each metadata entry kk: 1 byte1 \text{ byte} key length +Kk+2 bytes+ |K_k| + 2 \text{ bytes} value length +Vk+ |V_k|.

Mathematical Formulas & Entitlement Calculations

1. Access Entitlement Expiration Formula

When a subscriber purchases or renews a subscription tier, Account.addOrRenewSubscription() computes the new access expiration height (NewExpirationHeight\text{NewExpirationHeight}):

NewExpirationHeight=max(currentHeight,currentExpirationHeight)+planDuration\text{NewExpirationHeight} = \max(\text{currentHeight}, \text{currentExpirationHeight}) + \text{planDuration}

  • Expired/New Subscriptions: If currentExpirationHeight<currentHeight\text{currentExpirationHeight} < \text{currentHeight}, access begins at currentHeight\text{currentHeight} and extends by planDuration\text{planDuration}.
  • Active Renewals: If currentExpirationHeightcurrentHeight\text{currentExpirationHeight} \ge \text{currentHeight}, access is added directly to currentExpirationHeight\text{currentExpirationHeight}, ensuring subscribers retain 100%100\% of pre-paid access time.

2. Entitlement Status Evaluation

An application verifies a subscriber's access entitlement by querying their AccountSubscription record for a given serviceId:

IsEntitlementActive(height)={trueif expirationHeight>currentHeightfalseif expirationHeightcurrentHeight\text{IsEntitlementActive}(\text{height}) = \begin{cases} \text{true} & \text{if } \text{expirationHeight} > \text{currentHeight} \\ \text{false} & \text{if } \text{expirationHeight} \le \text{currentHeight} \end{cases}


3. Net Revenue & Fiscal Tax Deduction

When processing a CURRENCY subscription payment, protocol taxes are computed via TaxCalculator and processed prior to provider crediting:

TaxAmountQNT=TaxCalculator.computeTotalTax(amountQNT)\text{TaxAmountQNT} = \text{TaxCalculator.computeTotalTax}(\text{amountQNT})

NetProviderRevenueQNT=amountQNTTaxAmountQNT\text{NetProviderRevenueQNT} = \text{amountQNT} - \text{TaxAmountQNT}

The net funds are credited to the provider's balance, and a record is logged in public.currency_tax_record.


Detailed Operational Workflows

1. Registering a Subscription Service

A service provider invokes addSubscriptionService() via a SUBSCRIPTION_SERVICE transaction attachment:

  1. Validates that plan byte size satisfies 32 KB\le 32 \text{ KB}.
  2. Sets is_deleted = false and records creation timestamp.
  3. Serializes plan objects into JSON and inserts a row into public.subscription_service.
  4. Emits SubscriptionsHome.Event.NEW_SUBSCRIPTION_SERVICE.

2. Updating Service Tier Plans

A provider can modify billing tiers via updateServicePlans():

  1. Fetches active service record by serviceId.
  2. Validates that is_deleted == false (discontinued services cannot update plans).
  3. Validates new plan array byte size using SubscriptionPlanValidator.
  4. Replaces the plans JSON representation and inserts a new versioned row into public.subscription_service.
  5. Emits SubscriptionsHome.Event.SUBSCRIPTION_PLAN_UPDATE.

3. Subscribing and Paying (addSubscriptionPayment)

A subscriber submits a SUBSCRIPTION_PAYMENT transaction specifying serviceId and target plan:

  1. System validates that the service exists and is_deleted == false.
  2. Locates the requested plan name within the service's plans array.
  3. Debits amountQNT+taxAmountQNT\text{amountQNT} + \text{taxAmountQNT} from the subscriber's holding balance.
  4. Credits amountQNTtaxAmountQNT\text{amountQNT} - \text{taxAmountQNT} to the provider's account.
  5. Invokes Account.addOrRenewSubscription() to calculate and store the updated expirationHeight.
  6. Inserts a row into public.subscription_payment.
  7. Emits SubscriptionsHome.Event.SUBSCRIPTION_PAYMENT.

4. Discontinuing a Service

A provider can decommission an active offering via discontinueService():

  1. Queries the service entity by serviceId.
  2. Sets is_deleted = true.
  3. Inserts updated state into public.subscription_service.
  4. Emits SubscriptionsHome.Event.SUBSCRIPTION_SERVICE_ENDED.
  5. Note: Existing subscribers retain access until their expirationHeight passes, but no new subscriptions or renewals can be processed.

Event Listener Matrix

The Subscriptions System exposes strongly-typed cross-chain listeners via SubscriptionsHome:

Event EnumClass TargetTrigger Condition
NEW_SUBSCRIPTION_SERVICESubscriptionServiceNew service successfully validated and indexed in registry.
SUBSCRIPTION_PLAN_UPDATESubscriptionServiceService provider updated plan tiers, pricing, or metadata JSON.
SUBSCRIPTION_SERVICE_ENDEDSubscriptionServiceProvider decommissioned service (is_deleted set to true).
SUBSCRIPTION_PAYMENTSubscriptionPaymentSubscriber payment processed, revenue disbursed, and entitlement extended.
// Registering a global subscription payment listener
SubscriptionsHome.addSubscriptionPaymentListener(payment -> {
System.out.println("Payment received for Service: " + payment.getServiceId()
+ " by Subscriber: " + payment.getSubscriberId()
+ " Amount: " + payment.getAmountQNT());
}, SubscriptionsHome.Event.SUBSCRIPTION_PAYMENT);

APIDescription
CreateSubscriptionServiceCreate subscription service
UpdateSubscriptionPlansUpdate service plans
GetSubscriptionServiceGet specific service
GetSubscriptionServicesList all services
SearchSubscriptionServicesSearch services
GetSubscriptionServicesCountGet services count
DiscontinueSubscriptionServiceDiscontinue service
SubscriptionPaymentMake subscription payment
GetSubscriptionPaymentGet payment
GetSubscriptionPaymentsList payments
GetSubscriptionPaymentsCountGet payments count
GetAccountSubscriptionsGet account subscriptions

Primary Institutional Use Cases

  • SaaS & API Access Control: Software providers gate feature access and API rate limits behind block-height subscription tiers. Applications verify entitlement directly on-chain without maintaining external user databases.
  • Media, Streaming & Digital Content: Content creators, news agencies, and streaming platforms issue tiered subscriptions, maintaining immutable records of subscriber access rights.
  • Professional Associations & Licensing: Industry bodies and chambers of commerce track annual membership dues on-chain with auto-expiring block-height certificates.
  • Automated Insurance Premium Collection: Seamlessly integrates with the Shamwari Insurance Subsystem (nxt.insurance) to automate recurring policy premium collections (monthly, quarterly, annual).
  • Tokenized Loyalty & Membership Badges: Service providers accept custom TOTEM digital assets or native loyalty tokens as subscription fees, enabling staking-based and token-gated access tiers.