Back to experience

Conflux · DApp Hub · DDD and Derived State

Conflux Hub

Organize state by domain so Stake, Vote, and Portfolio share the same facts

Conflux Hub is an onchain tools portal: users stake and lock CFX to gain voting power, join governance votes, and review assets and staking in Portfolio. I organized shared state by business domain so these connected features use consistent data while evolving independently.

5 domains
Account / Chain / Governance / Token / Assets
Separated
shared facts live in services; page interactions stay in routes
Derived
asset value, Stake, voting power, and rounds come from facts
3 pages
Stake / Vote / Portfolio compose shared domains

9 chapters

01

Stake, Vote, and Portfolio share one foundation

Users stake CFX and lock it for voting power in Stake, choose a governance round and vote on chain parameter changes in Vote, then review holdings, staking, and voting power in Portfolio. A staking change affects both voting power and totals, so these pages cannot calculate independently. I organized their shared account, network, asset, and governance data together, preserving existing connections and data context across pages. Hub also includes tools such as bridging; this case study focuses on three connected jobs.

  • Page changes preserve the current account and network context
  • Core Space Base32 and eSpace Hex normalize at domain boundaries
  • Authorization, transaction feedback, and error handling use shared capabilities

02

DDD: domains own facts; pages compose

src/service/account, chain, governance, token, and myAssets each own business facts and operations that several pages can use. Forms, filters, and modal state that belong to one page stay in its route/-service. Stake, Vote, and Portfolio consume domain capabilities through hooks and actions instead of keeping separate asset, Stake, or voting copies.

  • Shared domains: account / chain / governance / token / myAssets
  • Page-local state: forms, filters, and interaction flows stay in route/-service
  • Page tree: the route reads accounts and governance totals; MyAssets reads assets

03

myAssets derives; Portfolio composes

A shared domain is layered internally. Query atoms acquire facts through stable network, space, and account keys; derived atoms calculate from those facts; hooks and actions expose capabilities to pages. In myAssets, assetsFamily fetches assets and assetsValueFamily derives value and proportions. The Portfolio route reads accounts and governance totals for its account, staking, and voting components. The MyAssets child calls asset hooks directly. The page tree composes domains without centralizing every query in one route. atomFamily compares parameter values to avoid duplicate state caused by different object references.

  • assetsFamily → query and cache the asset list
  • assetsValueFamily → derive value, total, and proportions
  • MyAssets → useAssets / useAssetsValue
  • Portfolio route → account / governance → account, staking, and voting components

04

DDD: one truth, reusable capabilities, bounded change

Each business fact has one owner, and Stake, Vote, and Portfolio reuse the same domain results. When Portfolio switches from Core Space to eSpace, the current account, asset list, and voting totals recalculate through their dependencies. Unrelated state such as historical Governance cache and a Stake form draft does not need to reset with them.

  • One truth: pages do not keep separate account, asset, or voting copies
  • Reuse: new pages compose existing queries, derived atoms, and actions
  • Isolation: only state that depends on account or space is updated

05

Stable loading: cache owns data, skeletons own layout

When a staking page opens, the amount may still be loading. Its placeholder reserves the amount row and action area; the amount replaces it without pushing the action down. The film illustrates this with a fixed-size example. Cache solves a separate problem: compareCacheAndUseRightData races persisted and remote data, uses what arrives first, and replaces cached data only when the remote result differs. persist-* queries use BigInt-safe JSON and include network IDs in their keys. Round selectors, Stake cards, and asset rows each share dimensions with their placeholders: cache reduces waiting, while skeletons preserve layout.

  • Cache and remote run in parallel instead of forcing a blank cold load
  • Only persist-* queries enter persistence
  • Skeleton, resolved content, and container share stable dimensions

06

Governance: one state, three pages

Governance does not expose one total object for every page. Stake reads staking details and calls domain actions to stake, unstake, and lock. Vote reads voting power and rounds before submitting votes. Portfolio reads derived staking, lock, and voting totals. Core combines PoW and PoS; eSpace aggregates PoS. Sharing the domain means these different views originate from the same facts, not that three pages synchronize their own copies.

  • Stake: staking details + stake / unstake / lock actions
  • Vote: voting power + rounds + voting operations
  • Portfolio: derived Stake, Lock, and Voting power totals

07

Governance: one round requires several onchain facts

The page shows Current Value, Coming Effective, and any selected historical round, but the chain exposes no ready-made complete round object. The system reads currentRound, derives the round start and end blocks from network configuration, converts historical blocks into epoch tags, fetches Increase / UnChange / Decrease totals, loads the PoS voting-stake base, and uses cfx_getParamsFromVote only as the current executed-parameter anchor. The card is a projection calculated from all of these sources.

  • New parameter topics appear after rounds 6 and 12, so historical shapes differ
  • When valid votes stay below five percent of PoS stake, the parameter remains unchanged
  • The current round refreshes every 30 seconds while historical rounds use persisted query keys

08

Governance History: calculate from an anchor and votes

Selecting an old round starts from an executed-parameter anchor and combines historical votes and PoS stake to calculate toward the target round. Intermediate parameters and historical queries enter execParamsCache and persist-* caches, so revisiting a round reuses prior work instead of repeating every query and calculation.

09

Complexity remains, but every concern has an owner

Concentrating domain atoms does not erase complexity. The frontend still needs to understand Core and eSpace call semantics, Base32 and Hex conversion, cache freshness, historical block queries, governance math, and transaction boundaries. The gain is that each concern has a clear owner and data path: domains maintain facts, pages compose capabilities, and asset, lock, voting, and historical-round results derive and recover on demand. New features can extend those boundaries instead of reinterpreting the protocol inside each page.

  • Shared facts have one owner
  • Product pages keep local interactions and compose domain capabilities
  • User results can be derived, cached, and restored

Code and evidence

Evidence boundary: this page uses the local conflux-hub source and key commit diffs. It proves architecture, data flow, and implementation boundaries; it does not present unperformed live chain writes, traffic, user scale, or commercial outcomes as verified results.