Node model
A node is a small, deliberately boring object: a struct in the factory holding an owner, a creation time, a balance and two running totals. Most of the questions people ask about nodes are answered by what the model leaves out.
What exists on chain
Creating a node writes one struct and one array entry, and nothing else.
struct Node {
address owner;
uint64 createdAt;
uint128 balance; // withdrawable right now
uint128 totalReceived; // credited over the node's whole life
uint128 totalWithdrawn;
}
mapping(uint256 => Node) private _node; // node id -> node
mapping(address => uint256[]) private _owned; // owner -> node ids
uint256 public totalNodes; // ids are 1-basedA node id is assigned by ++totalNodes, so ids are sequential from 1 and never reused. See Node numbering. There is no accrual rate in the struct and no status flag; the three numbers are simply what has been credited, what has been taken out, and the difference that is sitting there now.
All five fields come back from one call, which is what the explorer’s Read Contract tab needs:
cast call $FACTORY "nodeInfo(uint256)(address,uint64,uint256,uint256,uint256)" $NODE_ID
# -> owner, createdAt, balance, totalReceived, totalWithdrawnOwnership
The owner is the address the node was created for, which is the address that sent the payment. Every withdrawal path checks it, and the check is on the node itself, against msg.sender:
Node storage node = _node[id];
if (node.owner != msg.sender) revert NotNodeOwner();Because the check is against the caller, nothing can be presented to the contract on somebody else’s behalf. There is no signature to forge, no approval to grant and no server that could authorise a payout: a withdrawal is a transaction from the owning wallet or it does not happen. withdrawAll sweeps only the ids in _owned[msg.sender], so a batch cannot reach a node the caller does not own either. The destination is a separate question and is covered on Destination addresses.
State and lifecycle
On chain a node has no state to be in. It exists from the moment mintFor creates it, and there is no retirement, no expiry and no burn function in the contract. The active and retired distinction lives in the operator’s ledger and controls one thing: whether a node is included in new distribution rounds. It cannot affect a withdrawal, because the contract does not know it exists.
The full description of each state is on Node states.
What a node is not
| Not | Why it matters |
|---|---|
| Not a token | No ERC-721, no ownerOf, no transfer, no approvals, no marketplace listing. The owner field is written once and there is no function that changes it, so a node cannot be sold or moved to another address. |
| Not a deposit | The 0.02 ETH is a purchase, sent to the payments wallet outside the contract. It is not held for you, it never enters the factory, and it is not refundable. |
| Not a share of a fund | A node is not a claim on any pool of assets. It receives whatever is credited to it, and nothing more. |
| Not a validator | Nothing is run, staked, or secured by holding one. There is no uptime, no slashing, and no hardware. |
| Not a yield instrument | No rate is promised anywhere, and Sitowise publishes no projection of what a node will earn. |
Holding several
A wallet may hold up to 25 nodes, enforced in mintFor. Nodes held by the same wallet are independent objects: each has its own balance and its own pair of running totals. The dashboard sums them for display, and withdrawAll sweeps them in one transaction, but the contract still accounts for each id separately and emits one Withdrawn per node.
cast call $FACTORY "nodesOf(address)(uint256[])" $WALLET
cast call $FACTORY "nodeCountOf(address)(uint256)" $WALLET
cast call $FACTORY "balanceOfOwner(address)(uint256)" $WALLETThe same view over HTTP is GET /api/nodes/:address.