Sitowise
Nodes

Limits

One wallet may hold at most 25 nodes. The cap is in the contract, not in the website, and this page explains what it is for, what bounds it, and what it honestly cannot do.

The cap

SitowiseFactory.mintFor
if (_owned[to].length >= maxPerWallet) revert WalletLimit();

The check runs before any state is written, so an over-cap mint costs the relayer gas and changes nothing. It is checked against the address the node would be created for, which is the address that sent the payment. The current value is a public read:

cast
cast call $FACTORY "maxPerWallet()(uint256)"
cast call $FACTORY "MAX_PER_WALLET_CEILING()(uint256)"
cast call $FACTORY "nodeCountOf(address)(uint256)" $WALLET

The owner can change maxPerWallet with setMaxPerWallet, but only within a hard constant the contract carries and no call can move:

SitowiseFactory.setMaxPerWallet
function setMaxPerWallet(uint256 v) external onlyOwner {
    if (v == 0 || v > MAX_PER_WALLET_CEILING) revert BadInput();
    maxPerWallet = v;
    emit MaxPerWalletChanged(v);
}

MAX_PER_WALLET_CEILING is 100. Raising maxPerWallet affects future mints only; lowering it below what a wallet already holds does not remove anything, it simply stops that wallet getting more. Nodes are never destroyed by an admin setting, and every change emits MaxPerWalletChanged.

Why there is a cap at all

Three reasons, in order of how much they actually matter.

  1. withdrawAll has to fit in a block. The sweep loops over every node the caller owns, so the cost of that one transaction grows with the cap. That is why the ceiling exists as a constant rather than a setting: an unbounded cap could push the sweep past the block gas limit and strand a wallet that had accumulated too many nodes. Per-node withdraw always works regardless, so even then nothing would be lost, but a holder should never have to discover that.
  2. Distribution cost scales with node count. Every active node is an entry in every round, and during the launch period every node is funded by Sitowise. An unbounded number of nodes behind a single wallet turns a launch into a drain.
  3. It is a speed bump. It raises the cost of trivially accumulating a large share of nodes from one address.

Other limits

LimitValueEnforced by
Nodes per wallet25The contract, in mintFor
Ceiling on that setting100, and the owner cannot exceed itThe contract, MAX_PER_WALLET_CEILING
Node priceExactly 0.02 ETH per node, no more and no lessThe watcher. Payment happens outside the contract, so anything but the exact amount is held for review rather than turned into a node.
One payment, one nodeA payment transaction hash can back exactly one nodeThe contract, paymentRefUsed, reverting RefAlreadyUsed
Single credit amountBelow 2128 wei, because a node balance is a uint128The contract, in creditBatch, reverting AmountTooLarge
Credit backingA round’s msg.value must equal the sum of its amountsThe contract, reverting ValueMismatch
Withdrawal amountThe node’s whole balance. There is no partial withdrawal and no amount argument.The contract
Public API requestsPer IP, per minute: 120 for /api/stats and /api/me, 60 for the other public reads, 20 for /api/auth/nonce, /api/auth/verify and /api/nodes/syncThe application. See the API overview.

The rate limiter counts in the memory of the process that serves the request, so the effective quota is per instance and a cold start resets it. It exists to stop one machine scraping the whole ledger, not as a security boundary. Every response carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset, so a client never has to guess where it stands.

Pausing

The owner can pause node creation with setPaused(true). While paused, mintFor reverts with IsPaused and nothing else changes.

Withdrawals have no pause switch. No code path in the contract lets anyone stop an owner withdrawing their own balance, which is the asymmetry described on Security model. A withdrawal can only fail in ways that name themselves: NotNodeOwner if the caller does not own the node, BadInput on a zero destination, NothingToWithdraw when the balance is zero, and TransferFailed if the destination rejects the ETH.