SMART CONTRACTS
TabSubscriptionRouter
Recurring stablecoin payments. Creator publishes a Plan, subscriber approves the router once and calls subscribe, anyone (e.g. a cron) calls charge each period. Backs the Subscriptions product.
Surface
function createPlan(
string calldata planId,
uint256 amount,
uint64 period
) external returns (bytes32 planKey);
function deactivatePlan(bytes32 planKey) external;
function reactivatePlan(bytes32 planKey) external;
function subscribe(bytes32 planKey) external; // charges the first period
function cancel(bytes32 planKey) external;
// permissionless — schedule from any cron
function charge(bytes32 planKey, address subscriber) external;
function planKeyOf(address creator, string calldata planId)
external pure returns (bytes32);
function plans(bytes32 planKey) external view returns (
address creator,
uint256 amount,
uint64 period,
bool active
);
function subs(address subscriber, bytes32 planKey) external view returns (
bool active,
uint64 startedAt,
uint64 lastChargedAt,
uint64 cancelledAt,
uint256 chargesPaid
);
function isChargeable(bytes32 planKey, address subscriber)
external view returns (bool);
function feeOf(uint256 amount) external view returns (uint256);Plan key derivation
planKey = keccak256(abi.encode(creator, planId)). Two different creators can use the same planId string without colliding.
Charge schedule
charge reverts with TooEarly unless block.timestamp >= lastChargedAt + period. Two cron runs in the same period are safe — only one settles, the rest revert. The contract tracks chargesPaid for telemetry.
Constraints
MIN_PERIOD = 1 hour,MAX_PERIOD = 365 days.MAX_FEE_BPS = 500(5% absolute cap, owner can adjust within it).- Plan
amountandperiodare immutable — to change pricing, publish a new plan.
Trust model
- Subscribers retain custody. Cancelling, revoking the allowance, or letting the balance fall below
amountall halt charges. There is no escrow. chargeis permissionless.The contract enforces the schedule, allowance, and active-sub check — a malicious caller can't over-charge.- Fee. 1% default, split to
platformTreasury. Net goes to the creator.
Events
event PlanCreated(bytes32 indexed planKey, address indexed creator,
string planId, uint256 amount, uint64 period);
event Subscribed(bytes32 indexed planKey, address indexed subscriber);
event Cancelled(bytes32 indexed planKey, address indexed subscriber);
event Charged(bytes32 indexed planKey, address indexed subscriber,
address indexed creator, uint256 amount, uint256 fee,
uint64 newLastChargedAt);Source + tests
Tab/src/TabSubscriptionRouter.sol with a Foundry test suite covering plan lifecycle, charge scheduling, allowance revocation, force-cancel, and fuzz on the fee math.