A decentralized subscription payment system built on Ethereum that enables merchants to create subscription plans and receive recurring payments in ERC20 tokens.
- Merchant Management: Merchants can register and configure accepted payment tokens
- Subscription Plans: Create flexible subscription plans with custom amounts and periods
- Recurring Payments: Automated recurring payment processing with commission handling
- Multi-Token Support: Accept multiple ERC20 tokens for subscriptions
- Commission System: Configurable commission rates with min/max limits
- Subscription Control: Users can start and cancel subscriptions at any time
- ZeroPaySubscription: Main contract handling subscription logic
- Merchant: Stores merchant information and accepted tokens
- Plan: Subscription plan with amount, period, and merchant info
- Subscription: Active subscription with payment details and next claim time
The contract implements a flexible commission system:
- Commission rate (percentage)
- Minimum commission amount
- Maximum commission amount
This project uses Foundry.
forge install
Run the comprehensive test suite:
forge test
Run tests with verbosity:
forge test -vvv
Run specific test:
forge test --match-test testCreateSubscription
Create a .env
file with the following variables:
PRIVATE_KEY=your_private_key
INITIAL_OWNER=0x... # Optional, defaults to deployer address
COMMISSION_RATE=5 # Optional, default 5%
COMMISSION_MIN=1000000000000000000 # Optional, default 1 token (18 decimals)
COMMISSION_MAX=100000000000000000000 # Optional, default 100 tokens (18 decimals)
RPC_URL=your_rpc_url
source .env
forge script script/Subscription.s.sol:SubscriptionScript --rpc-url $RPC_URL --broadcast --verify
Start Anvil:
anvil
Deploy:
forge script script/Subscription.s.sol:SubscriptionScript --rpc-url http://localhost:8545 --broadcast
-
Register as Merchant
subscription.merchant(receiverAddress);
-
Configure Accepted Tokens
address[] memory adds = [token1, token2]; address[] memory dels = []; subscription.tokens(adds, dels);
-
Create Subscription Plan
subscription.plan(amount, period);
-
Cancel Plan
subscription.unplan(planId);
-
Subscribe to Plan
token.approve(subscriptionAddress, amount); subscription.subscribe(planId, customerAddress, tokenAddress);
-
Cancel Subscription
subscription.unsubscribe(subscriptionId);
Claim Subscription Payment (after period expires):
subscription.claim(subscriptionId);
Claim Accumulated Fees:
address[] memory tokens = [token1, token2];
subscription.claimFee(tokens, payeeAddress);
PlanStarted(uint256 indexed id, address merchant, uint256 amount, uint256 period)
PlanCanceled(uint256 indexed id)
SubscriptionStarted(uint256 indexed id, uint256 plan, address customer, address payer, address token, uint256 nextTime)
SubscriptionCanceled(uint256 indexed id)
SubscriptionClaimed(uint256 indexed id)
M01
: Invalid receiver address (cannot be zero address) or merchant not registeredM02
: Unauthorized plan modificationM03
: Plan is not activeM04
: Token not supported by merchantM05
: Subscription is not activeM06
: Too early to claim (period not elapsed)
forge build
forge fmt
forge snapshot
- Uses OpenZeppelin's SafeERC20 for secure token transfers
- Ownable pattern for admin functions
- Input validation on all public functions
- Careful handling of commission calculations to prevent overflow
MIT
For more information on Foundry: