Hartii developer docs

Subscriptions

Contract reference

Addresses (Quai mainnet, Cyprus-1)

ContractAddressNotes
SubscriptionFactory0x007420f8fDfBa7048bB04c8B87DE12Df49176665deployed 2026-11-03, block 10957967, 11,043-byte runtime
SubscriptionPlan "Hartii Membership"0x0033c5A37dcB57ca4e614DF4c2E66c95DAA77c88500 QUAI / 30 days, created 2026-11-03, block 10957970

Both were confirmed live with quai_getCode and read back after deployment. solc 0.8.20, optimizer 200 runs, viaIR. No proxy, no upgrade, no rescue function.

SubscriptionFactory

Write

solidity
function createPlan(string name, uint256 price, uint256 period, address payout)
    external payable returns (address plan); // msg.value == creationFee
function withdrawPending() external;          // pull anything booked to the caller

Read

FunctionLaunch valueOwner can change?Hard cap
platformFeeBps()300 (3%)yes, new plans onlyMAX_PLATFORM_FEE_BPS = 500
creationFee()20 QUAIyesMAX_CREATION_FEE = 100 QUAI
maxRefundWei()10 QUAIyesMAX_REFUND_WEI = 50 QUAI
maxRefundGasPrice()100,000 gweiyesMAX_REFUND_GAS_PRICE = 1,000,000 gwei
refundOverheadGas()45,000yesMAX_REFUND_OVERHEAD_GAS = 100,000
paused()falseyes—
treasury(), owner(), pendingOwner()—two-step ownership—
MIN_PERIOD / MAX_PERIOD1 day / 366 daysno—
MAX_NAME_BYTES64no—
allPlans(i), allPlansLength(), isPlan(a), plansOf(creator), pending(a)———

refundOverheadGas is the gas a plan cannot measure from inside the call (the 21,000 intrinsic cost, the access list, calldata, the event and the final refund transfer). It is calibrated on Orchard and tunable so it can follow Quai's gas accounting without a redeploy.

Events

PlanCreated(plan, creator, name, price, period, payout, platformFeeBps), PaymentBooked(to, amount), PendingWithdrawn(to, amount), PlatformFeeUpdated, CreationFeeUpdated, MaxRefundUpdated, MaxRefundGasPriceUpdated, RefundOverheadGasUpdated, TreasuryUpdated, PausedSet, OwnershipTransferStarted, OwnershipTransferred.

SubscriptionPlan

Write

solidity
function subscribe(uint32 periods) external payable; // msg.value == price * periods, 1 <= periods <= 12
function withdrawPending() external;                  // works while paused
function setPrice(uint256 newPrice) external;         // creator only; future payments only
function setPayout(address newPayout) external;       // creator only

Read

factory(), creator(), name(), price(), period(), payout(), platformFeeBps(), paidUntil(address) (unix seconds), isActive(address), pending(address), MAX_PERIODS (12), PUSH_GAS (30,000).

Events

Subscribed(subscriber, periods, paidUntil, fee, refund, creatorShare) — fee + refund + creatorShare always equals the payment. PaymentBooked(to, amount) when an amount is booked instead of sent. PendingWithdrawn, PriceUpdated(oldPrice, newPrice), PayoutUpdated(oldPayout, newPayout).

Reverts

MessageCause
Exact creation fee requiredmsg.value differs from creationFee()
Bad nameempty or longer than 64 bytes
Zero priceprice is 0
Period out of rangeperiod below 1 day or above 366 days
Zero payout / Bad payout / Payout not Cyprus-1payout is zero, the plan, the factory, a precompile, another zone or a Qi address
Exact price requiredmsg.value differs from price × periods
Periods out of rangeperiods is 0 or above 12
Pausedthe factory is paused (new plans and payments only)
Nothing pending / Withdraw failednothing booked to the caller, or the caller refused the QUAI
Not creator / Not owner / Not pending owneraccess control

Example: subscribe with quais

js
import { quais } from 'quais';

const provider = new quais.BrowserProvider(window.pelagus);
const signer = await provider.getSigner();
const plan = new quais.Contract(
  quais.getAddress('0x0033c5a37dcb57ca4e614df4c2e66c95daa77c88'),
  [
    'function price() view returns (uint256)',
    'function subscribe(uint32 periods) payable',
    'function isActive(address) view returns (bool)',
  ],
  signer,
);

const periods = 1;
const price = await plan.price();
// quais estimates gas and builds the access list per transaction (required on Quai).
const tx = await plan.subscribe(periods, { value: price * BigInt(periods) });
await tx.wait();
console.log(await plan.isActive(await signer.getAddress())); // true

Trust model

  • The plan never holds QUAI except amounts booked to pending for a contract recipient or a failed transfer. Value is sent only to normal Cyprus-1 Quai addresses with no code, with a fixed gas budget and without copying return data.
  • The owner can change the platform fee for future plans only, within the caps above, and can pause new plans and payments. The owner cannot touch paid time, move a plan's funds, or block a withdrawal.
  • The creator can change the price for future payments and the payout address. The creator cannot shorten anyone's paid time.
Quai Network mainnet · chain 9 · Cyprus-1. Figures marked "read on" a date were read from the chain that day; re-read before relying on them.