Skip to content

Economics

Ten dollars per Orbit slot, and the rules for dividing it. All of it lives in the contracts, and every figure can be read with a view call.

An Orbit slot costs 10 sUSDC, and an Orbit lasts 30 days. An Orbit has 1 to 5 slots, and one with k slots costs k prices — no discount for more. Everything below is worked out per slot. The clock starts at activation, not at purchase, so a bought token can sit unused for as long as you like.

The term runs in service time, which stands still while the protocol is paused, so a pause moves the end of a running subscription later by exactly its length. A subscription queued behind another moves with it and does not get any longer.

Payment is in USDC. Paying in ECHO is already in the contract but switched off: echoMintingEnabled is false, and an attempt returns EchoMintingDisabled.

You do not need ETH to buy. mintWithPermit takes an EIP-712 intent signature and a token permit and goes through GasStation. The wallet pays the gas of the purchase, the activation and joining the set in USDC together with the price; GasStation.gasFee says how much. That fee goes to GasStation and is not part of the split.

Live values: SubscriptionNFT.priceUSDC() and SUBSCRIPTION_DURATION. What a purchase costs right now, with a discount if there is one: mintCost(slots) and addSlotsCost(tokenId, extra).

The split can be changed: the contract owner rewrites it with setSplitBps, and it has been rewritten before. The six shares always add up to 10 000 basis points. SubscriptionNFT.getSplit() returns the current shares.

On chain today:

ShareAmountGoes to
Nodes70% — $7.00RewardDistributor, node pool
DAO10% — $1.00UsdcTreasury
Liquidity10% — $1.00LiquidityManager
Control Planes5% — $0.50RewardDistributor, CP pool
Founder5% — $0.50the founder’s wallet
Buyback0%—

UsdcTreasury is the DAO’s USDC. Only its owner can spend from it, and once control is handed over that is the timelock. It can, for example, pay for new Control Planes’ seats in advance: fundRewardReserve moves money into the CP pool, and the seats open with their whole term paid for. The buyback share comes into use after ECHO is listed.

Rounding dust goes to the node share. The split is always of the full price, a discounted purchase included (see promotions).

flowchart LR
    sub["Orbit slot<br/>10 sUSDC"]
    lp["LiquidityManager<br/>$1.00"]
    dao["UsdcTreasury<br/>$1.00"]
    fw["Founder<br/>$0.50"]
    rd["RewardDistributor"]
    npool["Node pool $7.00"]
    cpool["CP pool $0.50"]
    nodes["Nodes"]
    cps["Control Planes"]

    sub --> lp
    sub --> dao
    sub --> fw
    sub --> rd
    dao -.->|"fundRewardReserve"| cpool
    rd --> npool
    rd --> cpool
    npool -->|"claimNode · seconds × rate"| nodes
    cpool -->|"claimCp · seat pay per epoch"| cps

A node is paid a fixed rate for every second credited to it:

node rate = priceUSDC × node share / 10000 / T_SUB

At today’s 70%:

10 sUSDC × 0.70 / 2 592 000 s ≈ 0.0000027 sUSDC per second ≈ 0.2333 a day

One slot pays for one node-second per second.

The rate does not depend on how many nodes are working or how busy the network is. Only the price or the node share of the split changes it, and the contract lets such a change through only if the node pool can pay for all the time already sold at the new rate (see price changes).

RewardDistributor.nodeRate() returns the current rate in USDC’s smallest unit per second, multiplied by 10¹⁸: on a six-decimal USDC a second is worth less than the smallest unit.

An epoch is 24 hours. When it closes, its rate is fixed (epochRate(epoch)), and a node’s payout is its seconds in the epoch times that rate. An epoch’s rate is below the current rate in one case only: when more seconds were credited than all subscriptions’ paid time had run by the epoch’s end. Every node of that epoch then gets the same fraction of the rate, and the contract emits CapacityExceeded.

A Control Plane is paid a fixed amount for every epoch it holds a seat. seatPay() returns what one epoch of a seat earns.

A seat opens for a term in epochs, and the whole term’s pay is reserved in the CP pool at once. A seat cannot open unless the pool holds free money for the whole term. A Control Plane’s pay therefore does not depend on how many subscriptions sell after its seat opened.

  • seats(cpStarId) — the seat’s first epoch, the epoch after its last, and its pay per epoch.
  • claimCp(cpStarId, epoch) pays a closed epoch of the seat’s term, while the Star holds the CP role and its signer is not slashed.
  • After the term ends, the owner has 7 epochs to collect what is left. Then anyone may call settleSeat, and what was not paid goes back to the pool’s free money.

Seats are opened, extended and ended by seatManager(). During the test stage that is the deployer key; later it is the seat contract, which admits and retires Control Planes by itself.

A subscriber pays for a slot for 30 days and holds a session for a small part of each day. The money for time that passed with no session is owed to nobody. That is the free surplus.

RewardDistributor keeps books for each pool:

CallReturns
nodeAssets()everything the node pool holds
nodeLiabilities()what it owes: paid time still ahead, and credits not yet claimed
nodeAccrued()credits not yet claimed
freeNode()the difference, signed
cpAssets()everything the CP pool holds
seatReserved()reserved for seats’ remaining terms
spendableCp()what is free in the CP pool

Subscription time counts as past one epoch late: Control Planes report work in batches, and the seconds at the end of an epoch are credited in the next one. EpochAttestations credits no time older than the start of the previous epoch, so one epoch after an epoch closes, everything that could be credited for it has been. The surplus the contract shows is never more than the time that really went unused.

Free surplus leaves in three ways only:

  • promotions — automatically, see below;
  • release — releaseSurplus(to, nodeAmount, cpAmount), called by the owner, never more than is free;
  • CP seats — reserved from the CP pool’s free money.

Anyone can add to the pools with fundReserve(nodeAmount, cpAmount) after approving the USDC. That money is free at once. A plain USDC transfer to the contract reaches no pool.

Part of the free surplus goes back to subscribers as a discount. This happens by itself, with no vote and no manual decision.

A campaign lasts promoEpochs() epochs, 30 today. When it ends, the first epoch finalization opens the next:

  1. What the last campaign did not spend goes back to the pools it came from.
  2. The budget is promoAlphaBps() (15% today) of each pool’s free surplus, each pool giving in proportion to its own.
  3. The discount is the budget divided by the slot price and the sales forecast. The forecast is a moving average of slots sold in past campaigns, weighted promoEwmaBps() (50%), and never below promoMinForecastSlots() (10 slots).
  4. The discount is capped at discountCeilingBps(). Below promoMinDiscountBps() (5%) the campaign runs with no discount, and the money stays in the pools.

The ceiling is the lower of promoMaxDiscountBps() (30%) and 1 − node share − promoMarginBps() (a 5% margin), so 25% today. It is read from the live split on every purchase. A buyer who serves their own slot with their own node gets back at most the node share of the full price, so a discount under the ceiling is always less than the slot brings into the pool.

A discounted purchase is an ordinary full purchase with two payers: the buyer pays the discounted price, the campaign pays the rest, and the full price is split as always. The node rate, CP seats and the liquidity share do not change with a discount.

The budget is spent evenly: by the k-th epoch of a campaign no more than k/30 of it may be spent. When what is left does not cover a purchase’s discount, the purchase is at the full price. The wallet signs a price ceiling (maxCost), so the price it was shown cannot quietly rise: if the discount has run out, the purchase reverts and the client shows the new price.

Live state: campaign() (first epoch, epoch after the last, discount, budget, spent), currentDiscountBps() — the discount a one-slot purchase gets right now — and promoFromNode() and promoFromCp(), what is left of the budget by source.

One rate covers all time sold. So when the owner changes the price (setPriceUSDC) or the node share (setSplitBps), what the node pool owes is recounted at the new rate. SubscriptionNFT asks RewardDistributor.checkRate first, and the change reverts with RateUnfunded if the node pool cannot cover it. Lowering the price always goes through.

A two-hop chain is two sessions on two nodes, and each reports its own time. Each holds a slot of the Orbit, and each slot is paid for, so a two-hop route is paid for twice.

  • Concurrent sessions per Orbit. One per slot, network-wide. In the wallet tier each Control Plane adds what its peers report for the same Orbit before admitting another session. In the anonymous tier an Orbit has one identity per slot, and each gets one permission per window.
  • One source per session. One session is one device. If the same credentials are used from different addresses for three checks in a row, the session ends.

Neither is an operator setting. The slots are recorded on the Orbit itself (SubscriptionNFT.slotsOf). The network-wide ceiling is the directory parameter maxSessionsPerSub, read with SingulaProtocol.param; Control Planes re-read it every ten minutes and use 5 while it is unset. The one-source rule is a constant in the program.

Anyone can call claimNode(node, epoch) and claimCp(cpStarId, epoch). The payee is read from StarRegistry, so a stranger who makes the call spends their own gas paying your wallet.

For a node that is a necessity, not a convenience: its signer sends no transactions and holds no ETH, and the owner is meant to stay cold.

Any closed epoch can be claimed. If nobody has finalized it yet, claimNode finalizes it first. pendingNode(node, epoch) and pendingCp(cpStarId, epoch) show what a claim would pay; for a closed epoch not yet finalized, pendingNode uses the current rate.

At this stage everything is in USDC: Orbits are bought with it and Stars are paid in it. ECHO comes in at a later stage of the roadmap, for buying Orbits and for paying Stars. Buying with ECHO is already written into SubscriptionNFT and switched off until then, and the buyback share of the split stands at zero.

  • Payment for traffic. Volume is never priced; time is what is paid for.
  • A deadline for a node’s reward. Unclaimed node rewards from past epochs stay in the contract indefinitely and count as the pool’s debt. Only a CP seat’s pay has a deadline: 7 epochs after the seat’s term ends.