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 amount and period are immutable — to change pricing, publish a new plan.

Trust model

  • Subscribers retain custody. Cancelling, revoking the allowance, or letting the balance fall below amount all halt charges. There is no escrow.
  • charge is 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.