@subscrypts/subscrypts-sdk-react
v1.6.0
Published
Official React SDK for Subscrypts - Decentralized subscriptions on Arbitrum
Maintainers
Readme
@subscrypts/subscrypts-sdk-react
Official React SDK for Subscrypts
Decentralized subscription payments on Arbitrum - Built for developers of all skill levels
Documentation • Quick Start • Examples • API Reference
📖 Table of Contents
- What is Subscrypts?
- Features
- Prerequisites
- Installation
- Quick Start Guide
- Core Concepts
- Complete Examples
- API Reference
- Advanced Usage
- Styling & Customization
- Troubleshooting
- FAQ
- Support
🚀 What is Subscrypts?
Subscrypts is a decentralized subscription protocol built on Arbitrum that enables Web3 applications to accept recurring payments in cryptocurrency. Think "Stripe for Web3" - but fully decentralized, with no intermediaries.
This React SDK makes it incredibly easy to:
- ✅ Add subscription-based access control to your app
- ✅ Accept payments in SUBS tokens or USDC
- ✅ Manage user subscriptions on the blockchain
- ✅ Build custom subscription UIs with headless hooks
Perfect for: Premium content sites, SaaS platforms, membership communities, paywalled APIs, and any application requiring recurring payments.
✨ Features
🎨 Pre-built UI Components
Drop-in React components that work out of the box - no blockchain expertise required.
<SubscriptionGuard planId="123">
<YourPremiumContent />
</SubscriptionGuard>🎯 Headless Hooks API
Complete control over UI with powerful, composable hooks.
const { status, isActive } = useSubscriptionStatus('1');🔐 Built-in Access Control
Automatically protect routes and components based on subscription status.
💳 Dual Payment Support
Accept payments in both SUBS tokens (native) and USDC (with auto-swap).
🎭 Three Wallet Modes
- Internal Mode: Built-in wallet connection (MetaMask, browser wallets)
- External Mode: Integrate with existing Wagmi/RainbowKit setups
- Connector Mode: Pluggable wallet architecture for custom providers (Privy, Web3Auth, etc.)
🔄 Session Persistence
Wallet connections are remembered across page reloads - no popup on return visits.
🛡️ Error Recovery
Human-readable error messages with retry actions for blockchain errors.
📱 Mobile Responsive
All components work perfectly on desktop and mobile devices.
🌳 Tree-Shakeable
Import only what you need - optimized bundle sizes.
📘 TypeScript First
Full type safety with comprehensive TypeScript definitions.
📋 Prerequisites
Before you start, make sure you have:
- Node.js 16+ installed (Download here)
- A React app (React 18+ or React 19+)
- Basic understanding of React (hooks, components, props)
- A MetaMask wallet (for testing) (Install here)
Don't have a React app yet? Create one:
# Using Create React App
npx create-react-app my-subscription-app
cd my-subscription-app
# Or using Vite (recommended)
npm create vite@latest my-subscription-app -- --template react-ts
cd my-subscription-app
npm install📦 Installation
Step 1: Install the SDK
npm install @subscrypts/subscrypts-sdk-react ethersWhy ethers? It's the library we use to interact with the blockchain. Don't worry - you won't need to use it directly!
Step 2: That's it!
You're ready to add subscriptions to your app. Let's go! 🎉
🎓 Quick Start Guide
1. Basic Setup (5 minutes)
First, wrap your app with the SubscryptsProvider. This gives all components access to wallet and subscription data.
src/App.tsx or src/App.jsx
import { SubscryptsProvider } from '@subscrypts/subscrypts-sdk-react';
import '@subscrypts/subscrypts-sdk-react/styles'; // Import default styles
function App() {
return (
<SubscryptsProvider
enableWalletManagement={true}
defaultNetwork={42161} // Arbitrum One mainnet
>
<YourAppContent />
</SubscryptsProvider>
);
}
export default App;What did we just do?
SubscryptsProvider: Makes subscription features available throughout your appenableWalletManagement={true}: Enables built-in wallet connection (MetaMask, etc.)- Import styles: Loads the pre-built CSS for all components
2. Protect Content with Subscriptions
Use SubscriptionGuard to protect any content. Only users with active subscriptions can see what's inside.
import { SubscriptionGuard } from '@subscrypts/subscrypts-sdk-react';
function MyApp() {
return (
<div>
<h1>Welcome to My Premium Site</h1>
{/* Anyone can see this */}
<p>This is free content everyone can see!</p>
{/* Only subscribers can see this */}
<SubscriptionGuard
planId="1"
fallbackUrl="/subscribe"
>
<div className="premium-content">
<h2>Premium Content 🔒</h2>
<p>This exclusive content is only visible to subscribers!</p>
<video src="/premium-video.mp4" controls />
</div>
</SubscriptionGuard>
</div>
);
}What's happening here?
- Users without a subscription see nothing (or get redirected to
/subscribe) - Users with an active subscription see the premium content
- The SDK automatically checks the blockchain for subscription status
Where do I get planId?
You'll receive the plan ID when you create a subscription plan on the Subscrypts contract. This is the unique identifier for your subscription plan on the blockchain.
Important: Plan IDs must be numeric strings representing the on-chain plan ID. The smart contract auto-increments plan IDs starting from 1. Use numeric strings like "1", "2", "42" - NOT descriptive names like "premium-plan".
Examples:
- ✅ Correct:
planId="1",planId="42" - ❌ Wrong:
planId="premium-plan",planId="basic"
3. Add a Subscribe Button
Let users subscribe with a single button click! The checkout flow is handled automatically.
import { SubscryptsButton } from '@subscrypts/subscrypts-sdk-react';
function SubscribePage() {
const handleSuccess = (subscriptionId) => {
console.log('Subscribed! ID:', subscriptionId);
// Redirect to premium content or show success message
window.location.href = '/dashboard';
};
return (
<div className="subscribe-page">
<h1>Subscribe to Premium</h1>
<p>Get access to exclusive content for only 10 SUBS/month!</p>
<SubscryptsButton
planId="2"
variant="primary"
size="lg"
onSuccess={handleSuccess}
>
Subscribe Now - 10 SUBS/month
</SubscryptsButton>
</div>
);
}What happens when users click the button?
- If wallet isn't connected → Connect wallet prompt appears
- If wallet is connected → Checkout modal opens
- User selects payment method (SUBS or USDC)
- User chooses subscription duration (12, 24, 36 months, or custom)
- User approves transactions in their wallet
onSuccesscallback fires with the subscription ID- User now has access to premium content! 🎉
⚡ Gas Optimization & Passive Collection
The Subscrypts contract includes an automatic subscription renewal feature called Passive Collection.
What is Passive Collection?
On every SUBS token transfer, the contract automatically processes up to subscriptionCollectPassiveMax expired subscriptions from the network. This ensures subscriptions are renewed regularly without requiring manual collection.
Impact on Your Transactions
Gas Costs: SUBS transfers may cost 20-30% more gas than standard ERC20 transfers due to passive collection processing.
Transaction Logs: You may see subscription renewal events unrelated to your transfer.
Best Practice: Always add gas buffer to SUBS transfer operations:
const estimatedGas = await subsContract.estimateGas.transfer(to, amount);
const gasLimit = (estimatedGas * 130n) / 100n; // 30% buffer for passive collection
await subsContract.transfer(to, amount, { gasLimit });Why It Exists
Passive collection is a form of "gas socialism" where all users help maintain the subscription network. Instead of requiring merchants or subscribers to manually trigger renewals, the network collectively processes them during normal token activity.
🧠 Core Concepts
Merchants and Plans
- Merchant: Your business/project on Subscrypts (you!)
- Plan: A subscription tier you offer (e.g., "Basic", "Premium", "Enterprise")
- Each plan has a unique
planIdand cost in SUBS tokens
Subscription Status
Subscriptions can be:
- Active: User has paid and can access content
- Expired: Subscription period ended
- Auto-renewing: Subscription automatically renews each cycle
- Manual: User must manually renew
Payment Methods
SUBS Tokens (native):
- Direct payment with SUBS tokens
- Lower gas fees
- Preferred method
USDC (with auto-swap):
- Pay with USDC stablecoin
- Automatically swapped to SUBS via Uniswap
- Great for users who prefer stablecoins
Wallet Modes
Internal Mode (Easiest):
<SubscryptsProvider enableWalletManagement={true}>SDK handles wallet connection for you using browser extensions (MetaMask, etc.)
External Mode (Wagmi/RainbowKit):
<SubscryptsProvider
enableWalletManagement={false}
externalProvider={yourWagmiSigner}
>Use when you already have wallet management (Wagmi, RainbowKit, etc.)
Connector Mode (Custom Providers):
import { InjectedConnector } from '@subscrypts/subscrypts-sdk-react';
<SubscryptsProvider connectors={[new InjectedConnector(), myPrivyConnector]}>Pluggable architecture - implement the WalletConnector interface for any provider.
📚 Complete Examples
Example 1: Simple Paywall
Protect a single page or component:
import { SubscriptionGuard, SubscryptsButton } from '@subscrypts/subscrypts-sdk-react';
function PremiumArticle() {
return (
<article>
<h1>The Future of Web3 Subscriptions</h1>
{/* Free preview */}
<p>
Decentralized subscriptions are revolutionizing how we think about
recurring payments in Web3...
</p>
{/* Premium content */}
<SubscriptionGuard
planId="1"
fallbackUrl="/subscribe"
>
<div className="premium-section">
<h2>Deep Dive Analysis</h2>
<p>
[Full article content only visible to subscribers...]
</p>
</div>
</SubscriptionGuard>
{/* Subscribe CTA */}
<div className="subscribe-cta">
<h3>Want to read more?</h3>
<SubscryptsButton planId="1">
Subscribe for $5/month
</SubscryptsButton>
</div>
</article>
);
}Example 2: Subscription Status Badge
Show users their current subscription status:
import { useSubscriptionStatus } from '@subscrypts/subscrypts-sdk-react';
function SubscriptionBadge() {
const { status, isLoading } = useSubscriptionStatus('1');
if (isLoading) {
return <div>Checking subscription...</div>;
}
if (!status || !status.isActive) {
return (
<div className="badge free">
<span>Free Tier</span>
<a href="/subscribe">Upgrade to Premium</a>
</div>
);
}
return (
<div className="badge premium">
<span>✓ Premium Member</span>
<p>
{status.isAutoRenewing
? `Renews ${status.expirationDate.toLocaleDateString()}`
: `Expires ${status.expirationDate.toLocaleDateString()}`
}
</p>
</div>
);
}Example 3: Custom Checkout Flow
Build your own checkout UI using hooks:
import { useState } from 'react';
import { useSubscribe, useWallet, useTokenBalance } from '@subscrypts/subscrypts-sdk-react';
function CustomCheckout({ planId }) {
const [cycles, setCycles] = useState(12);
const [autoRenew, setAutoRenew] = useState(true);
const [paymentMethod, setPaymentMethod] = useState('SUBS');
const { isConnected, connect } = useWallet();
const { subscribe, txState, error } = useSubscribe();
const { formatted: subsBalance } = useTokenBalance('SUBS');
const { formatted: usdcBalance } = useTokenBalance('USDC');
const handleSubscribe = async () => {
try {
const subscriptionId = await subscribe({
planId,
cycleLimit: cycles,
autoRenew,
paymentMethod
});
alert(`Success! Subscription ID: ${subscriptionId}`);
} catch (err) {
console.error('Subscription failed:', err);
}
};
if (!isConnected) {
return <button onClick={connect}>Connect Wallet</button>;
}
return (
<div className="custom-checkout">
<h2>Subscribe to Premium</h2>
{/* Duration selector */}
<div>
<label>Subscription Length:</label>
<select value={cycles} onChange={(e) => setCycles(Number(e.target.value))}>
<option value={12}>12 months</option>
<option value={24}>24 months</option>
<option value={36}>36 months</option>
</select>
</div>
{/* Auto-renewal toggle */}
<div>
<label>
<input
type="checkbox"
checked={autoRenew}
onChange={(e) => setAutoRenew(e.target.checked)}
/>
Auto-renew subscription
</label>
</div>
{/* Payment method */}
<div>
<label>Payment Method:</label>
<div>
<button
onClick={() => setPaymentMethod('SUBS')}
className={paymentMethod === 'SUBS' ? 'active' : ''}
>
SUBS (Balance: {subsBalance})
</button>
<button
onClick={() => setPaymentMethod('USDC')}
className={paymentMethod === 'USDC' ? 'active' : ''}
>
USDC (Balance: {usdcBalance})
</button>
</div>
</div>
{/* Subscribe button */}
<button
onClick={handleSubscribe}
disabled={txState !== 'idle'}
>
{txState === 'idle' && 'Subscribe Now'}
{txState === 'approving' && 'Approving...'}
{txState === 'subscribing' && 'Processing...'}
{txState === 'success' && 'Success!'}
</button>
{error && <div className="error">{error.message}</div>}
</div>
);
}Example 4: Multi-Tier Pricing
Offer multiple subscription tiers:
import { SubscryptsButton } from '@subscrypts/subscrypts-sdk-react';
function PricingPage() {
const plans = [
{
id: '1',
name: 'Basic',
price: '5 SUBS',
features: ['Access to articles', 'Email support']
},
{
id: '2',
name: 'Premium',
price: '10 SUBS',
features: ['Everything in Basic', 'Video content', 'Priority support']
},
{
id: '3',
name: 'Enterprise',
price: '20 SUBS',
features: ['Everything in Premium', 'API access', 'Custom integrations']
}
];
return (
<div className="pricing-grid">
{plans.map((plan) => (
<div key={plan.id} className="pricing-card">
<h3>{plan.name}</h3>
<div className="price">{plan.price}/month</div>
<ul>
{plan.features.map((feature) => (
<li key={feature}>✓ {feature}</li>
))}
</ul>
<SubscryptsButton
planId={plan.id}
variant="primary"
onSuccess={(id) => {
console.log(`Subscribed to ${plan.name}:`, id);
window.location.href = '/dashboard';
}}
>
Choose {plan.name}
</SubscryptsButton>
</div>
))}
</div>
);
}📖 API Reference
Components
<SubscryptsProvider>
Required wrapper for all Subscrypts components and hooks.
<SubscryptsProvider
enableWalletManagement={true}
defaultNetwork={42161}
externalProvider={yourProvider} // Optional
>
<YourApp />
</SubscryptsProvider>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| enableWalletManagement | boolean | No | true | Enable built-in wallet connection |
| externalProvider | ExternalWalletConfig | No | - | Use external wallet (Wagmi/RainbowKit) |
| connectors | WalletConnector[] | No | - | Custom wallet connectors (overrides enableWalletManagement) |
| persistSession | boolean | No | true | Remember wallet across page reloads |
| onAccountChange | (newAddr, oldAddr) => void | No | - | Callback when wallet account changes |
| onChainChange | (newChainId, oldChainId) => void | No | - | Callback when network changes |
| debug | 'silent' \| 'info' \| 'debug' | No | 'info' | Logging level |
| children | ReactNode | Yes | - | Your app components |
<SubscriptionGuard>
Protect content based on subscription status.
// Single plan
<SubscriptionGuard planId="1" fallbackUrl="/subscribe">
<PremiumContent />
</SubscriptionGuard>
// Multi-plan: any of these grants access
<SubscriptionGuard planIds={['1', '2', '3']}>
<PremiumContent />
</SubscriptionGuard>
// Multi-plan: require ALL plans
<SubscriptionGuard planIds={['1', '2']} requireAll>
<BundleContent />
</SubscriptionGuard>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| planId | string | No | - | Single plan ID to check |
| planIds | string[] | No | - | Multiple plan IDs to check |
| requireAll | boolean | No | false | Require ALL plans (true) or ANY plan (false) |
| fallbackUrl | string | No | - | Redirect URL when subscription is inactive |
| loadingComponent | ReactNode | No | - | Custom loading indicator |
| onAccessDenied | () => void | No | - | Callback when access is denied |
| children | ReactNode | Yes | - | Content to protect |
<SubscryptsButton>
One-click subscribe button with built-in checkout flow.
<SubscryptsButton
planId="2"
variant="primary"
size="lg"
referralAddress="0x..."
onSuccess={(id) => console.log('Subscribed:', id)}
onError={(err) => console.error(err)}
>
Subscribe Now
</SubscryptsButton>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| planId | string | Yes | - | The plan ID to subscribe to |
| variant | 'primary' | 'secondary' | 'outline' | No | 'primary' | Button style variant |
| size | 'sm' | 'md' | 'lg' | No | 'md' | Button size |
| referralAddress | string | No | - | Ethereum address for referral rewards |
| onSuccess | (id: string) => void | No | - | Callback when subscription succeeds |
| onError | (error: Error) => void | No | - | Callback when subscription fails |
| children | ReactNode | No | 'Subscribe' | Button text |
<CheckoutWizard>
Full checkout modal with multi-step flow. Usually used via SubscryptsButton, but can be used standalone.
const [isOpen, setIsOpen] = useState(false);
<CheckoutWizard
planId="2"
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onSuccess={(id) => alert('Success!')}
/>Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| planId | string | Yes | The plan ID to subscribe to |
| isOpen | boolean | Yes | Modal open state |
| onClose | () => void | Yes | Close modal callback |
| referralAddress | string | No | Referral address |
| onSuccess | (id: string) => void | No | Success callback |
| onError | (error: Error) => void | No | Error callback |
<PricingTable>
Display multiple subscription plans in a responsive grid with built-in checkout.
<PricingTable
plans={[
{ planId: '1', title: 'Basic', subscribeLabel: 'Start Free' },
{ planId: '2', title: 'Pro', featured: true, subscribeLabel: 'Go Pro' },
{ planId: '3', title: 'Enterprise', subscribeLabel: 'Contact Us' }
]}
currency="SUBS"
showFields={['description', 'amount', 'frequency', 'subscribers']}
columns={3}
/>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| plans | (string \| PlanConfig)[] | Yes | - | Array of plan IDs or configurations |
| currency | 'SUBS' \| 'USDC' | No | 'SUBS' | Currency for prices |
| showFields | PlanField[] | No | ['description', 'amount', 'frequency'] | Fields to display |
| columns | 1 \| 2 \| 3 \| 4 | No | auto | Grid columns |
| onSubscribe | (planId: string) => void | No | - | Custom subscribe handler |
| referralAddress | string | No | - | Referral for all subscriptions |
<PlanCard>
Single plan display card with configurable fields.
<PlanCard
plan={plan}
currency="SUBS"
showFields={['description', 'amount', 'frequency']}
onSubscribe={(id) => openCheckout(id)}
featured={true}
title="Premium Plan"
/>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| plan | Plan | Yes | - | Plan data from smart contract |
| currency | 'SUBS' \| 'USDC' | No | 'SUBS' | Currency for price display |
| showFields | PlanField[] | No | ['description', 'amount', 'frequency'] | Fields to show |
| onSubscribe | (planId: string) => void | No | - | Subscribe click handler |
| featured | boolean | No | false | Highlight this plan |
| title | string | No | - | Custom title (overrides description) |
Available PlanField values: 'description', 'amount', 'frequency', 'subscribers', 'merchant', 'referralBonus', 'attributes'
<ErrorDisplay>
Human-readable error messages for blockchain errors with retry support.
import { ErrorDisplay } from '@subscrypts/subscrypts-sdk-react';
<ErrorDisplay
error={transactionError}
onRetry={() => retryTransaction()}
onDismiss={() => clearError()}
/>
// Compact variant for inline use
<ErrorDisplay error={error} compact />Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| error | Error \| null | Yes | - | Error to display (renders nothing if null) |
| onRetry | () => void | No | - | Retry callback (shows retry button if provided) |
| onDismiss | () => void | No | - | Dismiss callback (shows dismiss button if provided) |
| compact | boolean | No | false | Compact inline display |
| className | string | No | - | Additional CSS class |
<NetworkSwitchPrompt>
Prompt users to switch to Arbitrum One when on the wrong network.
import { NetworkSwitchPrompt } from '@subscrypts/subscrypts-sdk-react';
<NetworkSwitchPrompt
currentChainId={chainId}
onSwitch={() => switchToArbitrum()}
/>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| currentChainId | number \| null | Yes | - | User's current chain ID |
| onSwitch | () => void | Yes | - | Callback to trigger network switch |
| onDismiss | () => void | No | - | Dismiss callback |
| className | string | No | - | Additional CSS class |
<SubscryptsErrorBoundary>
Catch and display React errors with reset capability.
import { SubscryptsErrorBoundary } from '@subscrypts/subscrypts-sdk-react';
<SubscryptsErrorBoundary
onError={(error) => logToService(error)}
fallback={(error, reset) => (
<div>
<p>Something went wrong: {error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
)}
>
<YourComponents />
</SubscryptsErrorBoundary>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| children | ReactNode | Yes | - | Components to protect |
| fallback | ReactNode \| (error, reset) => ReactNode | No | Default error UI | Custom error display |
| onError | (error: Error) => void | No | - | Error logging callback |
<ConnectWalletModal>
Wallet selection modal that lists available connectors.
import { ConnectWalletModal } from '@subscrypts/subscrypts-sdk-react';
<ConnectWalletModal
isOpen={showWalletModal}
onClose={() => setShowWalletModal(false)}
connectors={connectors}
onConnect={connectWith}
/>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| isOpen | boolean | Yes | - | Modal visibility |
| onClose | () => void | Yes | - | Close callback |
| connectors | WalletConnector[] | Yes | - | Available wallet connectors |
| onConnect | (connectorId: string) => Promise<void> | Yes | - | Connect handler |
| className | string | No | - | Additional CSS class |
<ManageSubscriptionModal>
Manage an existing subscription with cancel, auto-renewal toggle, and cycle updates.
import { ManageSubscriptionModal } from '@subscrypts/subscrypts-sdk-react';
<ManageSubscriptionModal
isOpen={showManage}
onClose={() => setShowManage(false)}
subscriptionId="42"
subscription={subscriptionData}
onCancelled={() => refetchSubscriptions()}
onUpdated={() => refetchSubscriptions()}
/>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| isOpen | boolean | Yes | - | Modal visibility |
| onClose | () => void | Yes | - | Close callback |
| subscriptionId | string | Yes | - | Subscription ID to manage |
| subscription | Subscription | No | - | Pre-loaded subscription data |
| onCancelled | () => void | No | - | Called after successful cancellation |
| onUpdated | () => void | No | - | Called after successful update |
<ConfirmDialog>
Reusable confirmation dialog for destructive or important actions.
import { ConfirmDialog } from '@subscrypts/subscrypts-sdk-react';
<ConfirmDialog
isOpen={showConfirm}
title="Cancel Subscription?"
message="Your subscription will remain active until the end of the current period."
variant="danger"
confirmLabel="Cancel Subscription"
onConfirm={handleCancel}
onCancel={() => setShowConfirm(false)}
/>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| isOpen | boolean | Yes | - | Dialog visibility |
| title | string | Yes | - | Dialog title |
| message | string | Yes | - | Dialog message |
| confirmLabel | string | No | 'Confirm' | Confirm button text |
| cancelLabel | string | No | 'Cancel' | Cancel button text |
| variant | 'danger' \| 'default' | No | 'default' | Visual variant (danger = red button) |
| onConfirm | () => void | Yes | - | Confirm callback |
| onCancel | () => void | Yes | - | Cancel callback |
<SubscriptionCard>
Display subscription details with status badge and manage button.
import { SubscriptionCard } from '@subscrypts/subscrypts-sdk-react';
<SubscriptionCard
subscription={subscription}
showManageButton={true}
onCancelled={() => refetchSubscriptions()}
/>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| subscription | Subscription | Yes | - | Subscription to display |
| showManageButton | boolean | No | true | Show manage button |
| showFiatPrice | boolean | No | false | Show fiat price |
| onManage | (id: string) => void | No | - | Custom manage handler |
| onCancelled | () => void | No | - | Called after cancellation |
| onUpdated | () => void | No | - | Called after update |
| className | string | No | - | Additional CSS class |
<SubscriptionDashboard>
Complete subscription management dashboard with pagination.
import { SubscriptionDashboard } from '@subscrypts/subscrypts-sdk-react';
<SubscriptionDashboard
pageSize={10}
showFiatPrices={true}
onSubscriptionCancelled={(id) => console.log('Cancelled:', id)}
/>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| address | string | No | Connected wallet | Address to fetch subscriptions for |
| pageSize | number | No | 10 | Subscriptions per page |
| showFiatPrices | boolean | No | false | Show fiat prices on cards |
| emptyComponent | React.ReactNode | No | Default message | Custom empty state |
| loadingComponent | React.ReactNode | No | Spinner | Custom loading state |
| className | string | No | - | Additional CSS class |
| onSubscriptionCancelled | (id: string) => void | No | - | Called after cancellation |
| onSubscriptionUpdated | (id: string) => void | No | - | Called after update |
<MerchantDashboard>
Complete merchant dashboard with revenue, plans, and subscribers.
import { MerchantDashboard } from '@subscrypts/subscrypts-sdk-react';
<MerchantDashboard />Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| merchantAddress | string | No | Connected wallet | Merchant address |
| className | string | No | - | Additional CSS class |
Features:
- Revenue overview card (MRR in SUBS/USD, active vs total subscribers)
- Grid of merchant's plans with subscriber counts
- Click plan to view subscriber details
- Empty state for new merchants
Hooks
useSubscriptionStatus
Check if user has an active subscription. This is your primary access control hook.
const { status, isLoading, error, refetch } = useSubscriptionStatus(
'1', // Plan ID
'0x...' // Optional: check different address
);
if (status?.isActive) {
// User has active subscription
}Returns:
{
status: {
isActive: boolean; // Whether subscription is active
expirationDate: Date | null; // When subscription expires
isAutoRenewing: boolean; // Auto-renewal enabled
remainingCycles: number; // Cycles remaining
subscriptionId: string | null; // Blockchain subscription ID
} | null;
isLoading: boolean; // Loading state
error: Error | null; // Error if any
refetch: () => Promise<void>; // Manually refresh status
}useSubscribe
Execute subscription transactions. Creates new subscriptions with SUBS or USDC.
const { subscribe, isSubscribing, txState, error, subscriptionId } = useSubscribe();
const handleSubscribe = async () => {
const subId = await subscribe({
planId: '2',
cycleLimit: 12,
autoRenew: true,
paymentMethod: 'SUBS'
});
console.log('Subscription ID:', subId);
};Returns:
{
subscribe: (params: SubscribeParams) => Promise<string>;
isSubscribing: boolean; // Transaction in progress
txState: 'idle' | 'approving' | 'subscribing' | 'success' | 'error';
error: Error | null; // Error if any
txHash: string | null; // Transaction hash
subscriptionId: string | null; // Created subscription ID
}Subscribe Params:
{
planId: string; // Plan to subscribe to
cycleLimit: number; // Number of payment cycles
autoRenew: boolean; // Enable auto-renewal
paymentMethod: 'SUBS' | 'USDC'; // Payment token
referralAddress?: string; // Optional referral
}useWallet
Access wallet connection state, actions, and connector information.
const {
isConnected,
address,
chainId,
connect,
disconnect,
connectors,
activeConnector,
connectWith
} = useWallet();
if (!isConnected) {
return <button onClick={connect}>Connect Wallet</button>;
}Returns:
{
isConnected: boolean; // Wallet connected
address: string | null; // User's wallet address
chainId: number | null; // Connected network ID
connect?: () => Promise<void>; // Connect wallet
disconnect?: () => Promise<void>; // Disconnect wallet
switchNetwork: (chainId: number) => Promise<void>; // Switch network
connectors: WalletConnector[]; // Available wallet connectors
activeConnector: WalletConnector | null; // Currently active connector
connectWith: (connectorId: string) => Promise<void>; // Connect with specific connector
}useTokenBalance
Get SUBS or USDC balance for connected wallet.
const { balance, formatted, isLoading, refetch } = useTokenBalance('SUBS');
console.log(`Balance: ${formatted} SUBS`); // "Balance: 100.50 SUBS"Returns:
{
balance: bigint | null; // Raw balance (wei)
formatted: string; // Human-readable balance
isLoading: boolean; // Loading state
refetch: () => Promise<void>; // Refresh balance
}usePlan
Fetch a single plan from the smart contract.
const { plan, isLoading, error, refetch } = usePlan('1');
if (plan) {
console.log(`Plan: ${plan.description}, Amount: ${plan.subscriptionAmount}`);
}Returns:
{
plan: Plan | null; // Plan data
isLoading: boolean; // Loading state
error: Error | null; // Error if any
refetch: () => Promise<void>; // Refresh plan
}usePlans
Fetch multiple plans in parallel from the smart contract.
const { plans, isLoading, error, refetch } = usePlans(['1', '2', '3']);
plans.forEach(plan => {
console.log(plan.description);
});Returns:
{
plans: Plan[]; // Array of plans
isLoading: boolean; // Loading state
error: Error | null; // Error if any
refetch: () => Promise<void>; // Refresh all plans
}useSUBSPrice
Fetch the current SUBS/USD price from the on-chain oracle.
const { priceUsd, isLoading, refetch } = useSUBSPrice();
if (priceUsd) {
console.log(`1 SUBS = $${priceUsd.toFixed(4)}`);
}Returns:
{
priceUsd: number | null; // 1 SUBS = X USD
rawPrice: bigint | null; // Raw 18-decimal value
isLoading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}usePlanPrice
Get comprehensive price info for a plan (SUBS, USDC, USD).
const { price, isLoading } = usePlanPrice('1');
if (price) {
console.log(`${price.subsFormatted} SUBS / ${price.frequency}`);
if (price.usdValue) console.log(`≈ ${formatFiatPrice(price.usdValue)}`);
}Returns:
{
price: {
subsAmount: bigint; // Price in SUBS (18 decimals)
subsFormatted: string; // e.g. "10.5000"
usdcAmount: bigint | null; // USDC equivalent (6 decimals)
usdcFormatted: string | null; // e.g. "5.25"
usdValue: number | null; // USD display value
frequency: string; // "Monthly", "Weekly", etc.
isUsdDenominated: boolean;
} | null;
isLoading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}useManageSubscription
Manage an existing subscription: cancel, toggle auto-renewal, update cycles.
const {
cancelSubscription,
toggleAutoRenew,
updateCycles,
isProcessing,
txState
} = useManageSubscription('42');
// Cancel
await cancelSubscription();
// Toggle auto-renewal
await toggleAutoRenew(false);
// Set remaining cycles
await updateCycles(12);Returns:
{
cancelSubscription: () => Promise<void>;
toggleAutoRenew: (enabled: boolean) => Promise<void>;
updateCycles: (cycles: number) => Promise<void>;
updateAttributes: (attributes: string) => Promise<void>;
txState: TransactionState;
error: Error | null;
isProcessing: boolean;
}usePlansByMerchant
Fetch all plans created by a specific merchant address.
const { plans, total, isLoading } = usePlansByMerchant('0x1234...');
plans.forEach(plan => {
console.log(plan.description, plan.subscriberCount.toString());
});Returns:
{
plans: Plan[];
total: number;
isLoading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}useMySubscriptions
Fetch paginated subscriptions for the connected wallet.
// Get all subscriptions (up to 100)
const {
subscriptions,
page,
hasMore,
nextPage,
prevPage,
isLoading
} = useMySubscriptions();
// Filter to specific plans (recommended for reliability)
const {
subscriptions,
isLoading
} = useMySubscriptions(undefined, 10, ['1', '2', '3']);
return (
<div>
{subscriptions.map(sub => (
<SubscriptionCard key={sub.id} subscription={sub} />
))}
<button onClick={prevPage} disabled={page === 1}>Previous</button>
<button onClick={nextPage} disabled={!hasMore}>Next</button>
</div>
);Parameters:
address?: string- Optional wallet address (defaults to connected wallet)pageSize?: number- Subscriptions per page (default: 10)planIds?: string[]- Optional plan IDs to filter (v1.5.2+, enables automatic fallback if contract returns empty)
Returns:
{
subscriptions: Subscription[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
isLoading: boolean;
error: Error | null;
nextPage: () => void;
prevPage: () => void;
refetch: () => Promise<void>;
}useSubscryptsEvents
Subscribe to real-time protocol events for live updates.
useSubscryptsEvents({
onSubscriptionCreated: (event) => {
console.log('New subscription:', event.subscriptionId);
refetchDashboard();
},
onSubscriptionPaid: (event) => {
console.log('Payment made:', event.amount);
},
onSubscriptionStopped: (event) => {
console.log('Subscription stopped:', event.subscriptionId);
}
});Returns:
{
isListening: boolean;
error: Error | null;
}useMerchantPlans
Fetch all plans owned by the connected wallet (merchant).
const { plans, total, isLoading } = useMerchantPlans();
plans.forEach(plan => {
console.log(`${plan.description}: ${plan.subscriberCount.toString()} subscribers`);
});Returns: Same as usePlansByMerchant - wrapper using connected wallet address.
useMerchantSubscribers
Fetch paginated subscribers for a specific plan.
const {
subscribers,
total,
activeCount,
page,
hasMore,
nextPage,
prevPage,
isLoading
} = useMerchantSubscribers('1');
console.log(`${activeCount} active out of ${total} subscribers`);Returns:
{
subscribers: Subscription[];
total: number;
activeCount: number;
page: number;
pageSize: number;
hasMore: boolean;
isLoading: boolean;
error: Error | null;
nextPage: () => void;
prevPage: () => void;
refetch: () => Promise<void>;
}useMerchantRevenue
Calculate Monthly Recurring Revenue (MRR) from active subscriptions.
const { revenue, isLoading } = useMerchantRevenue();
if (revenue) {
console.log(`MRR: ${revenue.mrrFormatted} SUBS`);
console.log(`≈ $${revenue.mrrUsdEstimate?.toFixed(2)}`);
console.log(`${revenue.activeSubscribers} / ${revenue.totalSubscribers} active`);
}Returns:
{
revenue: {
totalSubscribers: number;
activeSubscribers: number;
monthlyRecurringRevenue: bigint;
mrrFormatted: string;
mrrUsdEstimate: number | null;
} | null;
isLoading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}useSubscrypts
Access full SDK context. Advanced use cases only.
const {
wallet,
signer,
provider,
subscryptsContract,
subsTokenContract,
usdcTokenContract,
subsBalance,
usdcBalance,
refreshBalances,
network
} = useSubscrypts();Types
Plan
The Plan type represents a subscription plan created by a merchant on the Subscrypts smart contract.
interface Plan {
id: bigint; // Plan ID (auto-incremented by contract)
merchantAddress: string; // Creator's wallet address
currencyCode: bigint; // 0 = SUBS, 1 = USD
subscriptionAmount: bigint; // Price (18 decimals for SUBS)
paymentFrequency: bigint; // Seconds between payments
referralBonus: bigint; // Referral reward amount
commission: bigint; // Protocol commission
description: string; // Plan name/description (bytes32)
defaultAttributes: string; // Default subscription attributes
verificationExpiryDate: bigint; // Expiry timestamp
subscriberCount: bigint; // Total subscriber count
isActive: boolean; // Whether plan accepts subscriptions
}Note: When passing planId to components and hooks, use numeric strings (e.g., "1", "42"). These are automatically converted to bigint for blockchain calls.
Subscription
The Subscription type represents an active or past subscription from a wallet to a specific plan. This is the full subscription data returned by useMySubscriptions(), useMerchantSubscribers(), and used by SubscriptionCard.
interface Subscription {
id: bigint; // Subscription ID (auto-incremented by contract)
merchantAddress: string; // Plan owner's wallet address
planId: bigint; // Associated plan ID
subscriberAddress: string; // Subscriber's wallet address
currencyCode: bigint; // 0 = SUBS, 1 = USD
subscriptionAmount: bigint; // Subscription price (copied from plan)
paymentFrequency: bigint; // Payment interval in seconds
isRecurring: boolean; // Auto-renewal enabled
remainingCycles: number; // Cycles left before expiration
customAttributes: string; // Custom metadata (bytes32)
lastPaymentDate: bigint; // Timestamp of last payment
nextPaymentDate: bigint; // Timestamp when next payment is due
}Important: useSubscriptionStatus() returns a simplified SubscriptionStatus object (see below) rather than the full Subscription struct. The SubscriptionStatus is derived from the full subscription data for easier access control decisions.
SubscriptionStatus
interface SubscriptionStatus {
isActive: boolean;
expirationDate: Date | null;
isAutoRenewing: boolean;
remainingCycles: number;
subscriptionId: string | null;
}PaymentMethod
type PaymentMethod = 'SUBS' | 'USDC';TransactionState
type TransactionState =
| 'idle'
| 'approving'
| 'waiting_approval'
| 'subscribing'
| 'waiting_subscribe'
| 'success'
| 'error';PlanPriceInfo
interface PlanPriceInfo {
subsAmount: bigint; // Price in SUBS (18 decimals)
subsFormatted: string; // e.g. "10.5000"
usdcAmount: bigint | null; // USDC equivalent (6 decimals)
usdcFormatted: string | null; // e.g. "5.25"
usdValue: number | null; // USD display value
frequency: string; // "Monthly", "Weekly", etc.
isUsdDenominated: boolean; // Whether plan is USD-denominated
}SubscriptionHealth
interface SubscriptionHealth {
state: SubscriptionState; // 'active' | 'expired' | 'expiring-soon' | ...
isPaymentDue: boolean;
shouldRenew: boolean;
daysUntilExpiry: number | null;
cyclesRemaining: number;
}🔧 Advanced Usage
Using with Wagmi / RainbowKit
If you already have wallet management with Wagmi:
import { SubscryptsProvider } from '@subscrypts/subscrypts-sdk-react';
import { useWalletClient } from 'wagmi';
function App() {
const { data: walletClient } = useWalletClient();
return (
<SubscryptsProvider
enableWalletManagement={false}
externalProvider={walletClient}
>
<YourApp />
</SubscryptsProvider>
);
}Referral Program Integration
Earn rewards by referring users:
<SubscryptsButton
planId="premium"
referralAddress="0x1234..." // Your referral address
onSuccess={(id) => console.log('Referral subscription:', id)}
>
Subscribe with My Referral
</SubscryptsButton>Custom Transaction Handling
Monitor transaction states:
const { subscribe, txState, txHash } = useSubscribe();
useEffect(() => {
if (txState === 'approving') {
console.log('User is approving token spend...');
} else if (txState === 'subscribing') {
console.log('Creating subscription...');
} else if (txState === 'success') {
console.log('Success! Transaction:', txHash);
}
}, [txState, txHash]);Error Handling
Option 1: Use getErrorMessage for user-friendly error strings:
import { getErrorMessage } from '@subscrypts/subscrypts-sdk-react';
try {
await subscribe({ /* ... */ });
} catch (error) {
const errorInfo = getErrorMessage(error);
// errorInfo.title: "Transaction Rejected"
// errorInfo.message: "You rejected the transaction in your wallet."
// errorInfo.suggestion: "Please try again and confirm the transaction."
// errorInfo.isRetryable: true
}Option 2: Use ErrorDisplay component for automatic error rendering:
import { ErrorDisplay } from '@subscrypts/subscrypts-sdk-react';
<ErrorDisplay error={txError} onRetry={retryTransaction} />Option 3: Catch specific error classes:
import {
InsufficientBalanceError,
NetworkError,
TransactionError
} from '@subscrypts/subscrypts-sdk-react';
try {
await subscribe({ /* ... */ });
} catch (error) {
if (error instanceof InsufficientBalanceError) {
alert('Insufficient balance! Please add funds.');
} else if (error instanceof NetworkError) {
alert('Wrong network! Please switch to Arbitrum.');
} else if (error instanceof TransactionError) {
alert('Transaction failed: ' + error.message);
}
}Subscription Status Resolver
A pure function for normalizing subscription states. Works in React components, Node.js scripts, AI agents, and anywhere else:
import { resolveSubscriptionStatus } from '@subscrypts/subscrypts-sdk-react';
const status = resolveSubscriptionStatus({ subscription });
console.log(status.state); // 'active' | 'expired' | 'expiring-soon' | 'cancelled' | 'not-found'
console.log(status.isActive); // true/false
console.log(status.daysUntilExpiry); // number | null
if (status.state === 'expiring-soon') {
showRenewalReminder();
}Decision Helpers
Pure utility functions for subscription decisions. No blockchain calls - operate on existing data. Usable in React components, Node.js scripts, AI agents, cron jobs, and automation:
import {
canAccess,
isPaymentDue,
shouldRenew,
getSubscriptionHealth
} from '@subscrypts/subscrypts-sdk-react';
// Check if subscription grants access
if (canAccess(subscription)) {
showPremiumContent();
}
// Check if payment is past due
if (isPaymentDue(subscription)) {
triggerPaymentCollection();
}
// Check if subscription should auto-renew (due + auto-renewing + cycles remaining)
if (shouldRenew(subscription)) {
processRenewalPayment();
}
// Get comprehensive health summary
const health = getSubscriptionHealth(subscription);
console.log(health.state); // 'active' | 'expired' | 'expiring-soon' | ...
console.log(health.isPaymentDue); // false
console.log(health.shouldRenew); // false
console.log(health.daysUntilExpiry); // 25
console.log(health.cyclesRemaining); // 11🎨 Styling & Customization
Using Default Styles
Import the pre-built stylesheet:
import '@subscrypts/subscrypts-sdk-react/styles';This includes styles for all components with the subscrypts- prefix to avoid conflicts.
Customizing Colors
Override CSS variables in your own stylesheet:
:root {
/* Primary colors */
--subscrypts-color-primary: #3B82F6; /* Blue */
--subscrypts-color-secondary: #10B981; /* Green */
--subscrypts-color-error: #EF4444; /* Red */
--subscrypts-color-success: #10B981; /* Green */
/* Background colors */
--subscrypts-bg-primary: #FFFFFF;
--subscrypts-bg-secondary: #F3F4F6;
--subscrypts-bg-overlay: rgba(0, 0, 0, 0.5);
/* Text colors */
--subscrypts-text-primary: #1F2937;
--subscrypts-text-secondary: #6B7280;
--subscrypts-text-inverse: #FFFFFF;
/* Borders and spacing */
--subscrypts-border-radius: 0.5rem;
--subscrypts-spacing-unit: 0.25rem;
}Custom Button Styles
Target specific component classes:
/* Customize primary button */
.subscrypts-btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.subscrypts-btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}Building Custom Components
Use hooks to build completely custom UIs:
import { useSubscriptionStatus, useSubscribe } from '@subscrypts/subscrypts-sdk-react';
function MyCustomSubscriptionUI() {
const { status } = useSubscriptionStatus('merchant', 'plan');
const { subscribe } = useSubscribe();
// Your custom UI with your own styles
return (
<div className="my-custom-design">
{/* ... */}
</div>
);
}🐛 Troubleshooting
"Wallet not connected" error
Problem: Hooks return null or components don't work.
Solution: Make sure you've wrapped your app with <SubscryptsProvider>:
// ✅ Correct
<SubscryptsProvider enableWalletManagement={true}>
<App />
</SubscryptsProvider>
// ❌ Wrong - missing provider
<App />"Cannot read property 'isActive' of null"
Problem: Subscription status is null.
Solution: Always check loading state and handle null:
const { status, isLoading } = useSubscriptionStatus('merchant', 'plan');
if (isLoading) return <div>Loading...</div>;
if (!status) return <div>No subscription found</div>;
if (status.isActive) return <div>Active!</div>;Styles not appearing
Problem: Components have no styling.
Solution: Import the stylesheet:
import '@subscrypts/subscrypts-sdk-react/styles';"Wrong network" error
Problem: User is on wrong blockchain network.
Solution: SDK will auto-prompt to switch networks. Or manually check:
const { chainId } = useWallet();
if (chainId !== 42161) {
return <div>Please switch to Arbitrum One</div>;
}TypeScript errors
Problem: Type errors in your IDE.
Solution: Make sure you have @types installed:
npm install --save-dev @types/react @types/react-domAnd enable esModuleInterop in your tsconfig.json:
{
"compilerOptions": {
"esModuleInterop": true
}
}❓ FAQ
Q: Do I need to know blockchain development?
A: No! The SDK handles all blockchain interactions for you. If you know React, you can build with Subscrypts.
Q: What tokens can users pay with?
A: Users can pay with:
- SUBS tokens (native Subscrypts token)
- USDC (automatically swapped to SUBS)
Q: How much does it cost to use Subscrypts?
A: The SDK is free to use. You pay only blockchain gas fees (very low on Arbitrum - typically under $0.01) and Subscrypts protocol fees (small percentage of subscription revenue).
Q: Can I use this with Next.js?
A: Yes! Subscrypts works with:
- Create React App
- Next.js (App Router and Pages Router)
- Vite
- Remix
- Any React framework
For Next.js, make sure to mark components as client-side:
'use client'; // Add this at the top
import { SubscryptsButton } from '@subscrypts/subscrypts-sdk-react';Q: Can users pay with credit cards?
A: Not directly. Subscrypts is a blockchain-native protocol requiring cryptocurrency payments. However, users can buy crypto with credit cards through exchanges and then subscribe.
Q: How do I get SUBS tokens?
A:
- SUBS tokens are available on Arbitrum One
- You can acquire SUBS through decentralized exchanges
- Contact Subscrypts support for more information about acquiring SUBS tokens
Q: Is this production-ready?
A: Yes! The SDK is fully tested and used in production applications on Arbitrum One. Make sure to:
- Test thoroughly before deploying
- Audit your smart contract integrations
- Have error handling in place
- Monitor your subscriptions
Q: Can I customize the checkout flow?
A: Absolutely! You have three options:
- Use
<SubscryptsButton>with default checkout (easiest) - Use
<CheckoutWizard>directly for more control - Use hooks (
useSubscribe) to build completely custom UI
Q: What happens if a subscription expires?
A:
useSubscriptionStatusreturnsisActive: false<SubscriptionGuard>hides protected content- User is redirected to
fallbackUrl(if provided) - No automatic charges (unless auto-renewal is enabled)
Q: How do refunds work?
A: Subscrypts is blockchain-based, so refunds must be handled manually by sending tokens back to users. We recommend having clear refund policies and managing them through customer support.
🆘 Support
Documentation
- Full Docs: docs.subscrypts.com
- Video Tutorials: youtube.com/@subscrypts
Community
- Discord: discord.gg/subscrypts
- Twitter: @Subscrypts
- GitHub: github.com/subscrypts
Issues & Bug Reports
Found a bug? Open an issue on GitHub
📄 License
MIT License - see LICENSE file for details.
🙏 Acknowledgments
Built with:
Powered by Arbitrum
Made with ❤️ by the Subscrypts team
