Hartii Subscriptions
Hartii Subscriptions lets anyone sell a time-based subscription in native QUAI. A creator deploys a
plan through the factory. A subscriber prepays one or more periods and the plan records how long
they are paid up. Any app can then check plan.isActive(address).
creator ──createPlan + 20 QUAI──▶ SubscriptionFactory ──deploys──▶ SubscriptionPlan
subscriber ──subscribe(n) + price × n──▶ SubscriptionPlan ──same transaction──▶ fee · refund · creator shareWhere to use it
- hartiipay.app/hpay/subscriptions (HPAY 1.45.0) is the
tool: subscribe to a plan, create your own, see your paid time and every plan. It works with Pelagus
and Blip. Your wallet only signs: the page estimates gas on its own node, sends no
chainId, checks chain 9 and your account right before the request, shows the exact split and the raw transaction in a review, and blocks a second payment while an earlier one has an unknown outcome. Deep links:?plan=<address>opens a plan,?mode=createopens the create form. - hartii.org/subscriptions (hartii.org 1.52.0) shows the live terms, the Hartii Membership plan, every plan and a "is this wallet paid up?" check. It is read only: it has no wallet, and its buttons open the HPAY tool. Payments stay on hartiipay.app because Blip pays from a separate wallet for each website, so one site keeps all of a subscriber's paid time in one wallet.
- Developers can call the contracts directly (see Contract reference).
How a payment splits
Every subscribe call splits the payment inside the same transaction, and the plan keeps nothing:
| Leg | Amount | Goes to |
|---|---|---|
| Platform fee | 3% of the payment (the plan's platformFeeBps, fixed when the plan was created) | the treasury (factory.treasury()) |
| Gas refund | the gas the subscriber spent on this call, priced at min(tx.gasprice, maxRefundGasPrice) | back to the subscriber |
| Creator share | everything left | the plan's payout address |
The gas refund is what makes the subscription gasless for the subscriber: they pay exactly the price, and the creator's share covers the gas. Measured on the Orchard testnet rehearsal on 2026-11-03, a subscribe used 89,876 gas and the refund returned 100.28% of what the subscriber actually paid for it.
The refund has three limits, so it can never become a blank cheque:
- It is capped by
factory.maxRefundWei()(10 QUAI at launch) and by what is left after the fee. - It is priced at most at
factory.maxRefundGasPrice()(100,000 gwei at launch), so inflating the gas price earns nothing. - Only a wallet that signs the transaction itself is refunded (
msg.sender == tx.origin). A contract that callssubscribegets no refund, because it could batch calls and collect the fixed overhead more than once.
Creating a plan
Anyone with a wallet can create a plan. The creator pays an exact 20 QUAI creation fee, which goes to the treasury. A plan has:
- a name of 1 to 64 bytes,
- a price per period in QUAI (above zero),
- a period of 1 to 366 days,
- a payout address on Cyprus-1 that is a normal Quai address (not the plan, not the factory, not a precompile).
After creation the creator can change the price (only future payments are affected, never time already paid) and the payout address. Nothing else about a plan can change.
Subscribing
A subscriber calls subscribe(periods) with exactly price × periods attached, for 1 to 12 periods.
Paid time stacks: if the subscriber is still active, the new periods start where the old ones end;
if they lapsed, they start now. There is no cancellation and no refund of prepaid time, because the
creator is paid immediately. Prepay only what you intend to use.
The Hartii Membership plan
Hartii's own membership is a plan on the same factory: 500 QUAI per 30 days, address
0x0033c5A37dcB57ca4e614DF4c2E66c95DAA77c88,
payout the treasury. The perks it unlocks across Hartii products are proposed, not live.
Gotchas
- Access lists are required. Quai aborts a value transfer to an address that is missing from the
transaction's access list. Build the access list per transaction (
quai_createAccessList, whichquaisdoes for you when it estimates gas). A cached or hand-written list that leaves out the treasury or the current payout makes the whole subscribe fail. - Contracts are never called during a payment. If the treasury, the payout or a refund
recipient is a contract, its amount is booked to
pendinginstead of being sent, and it pulls it later withwithdrawPending(). This keeps a hostile or broken recipient from blocking subscribers. - Pause (owner) stops new plans and new payments. It never stops
withdrawPending().