Core Concepts
Architecture Overview

Architecture Overview

Superhero is a set of Sophia contracts on æternity, an off-chain indexer that turns their events into a queryable API, and three clients that talk to both. Every write goes to the chain; every read that needs to be fast goes to the API.

The stack in one view

LayerWhat it isWhere it lives
Chainæternity mainnet (ae_mainnet) or testnet (ae_uat)Public nodes; see Networks & Endpoints
ContractsSophia contracts compiled to FATEOn-chain, addresses in Deployed Addresses
Indexer + APIEvent subscriber and REST/WebSocket serviceapi.superhero.com
Node / middlewareChain RPC and transaction historymdw.wordcraft.fun and mdw.wordcraft.fun/mdw (testnet: testnet.aeternity.io)
ClientsReact/Vite web app, React Native mobile apps, agent skillsuperhero.com (opens in a new tab), the app stores, and the agent skill

On-chain layer

LayerContractResponsibility
ContentTipping.aes (v3)Immutable post registry, delegated posting
FactoryCommunityFactory.aesCollection registry, community deployment, ProtocolDAO token issuance
Token saleAffiliationBondingCurveTokenSale.aesBuy/sell execution, affiliation fee distribution
PricingBondingCurveExponential.aesExponential price curve computation
GovernanceDAO.aes, DAOVote.aes, ReplaceableDAO.aesTreasury, proposals, voting, DAO migration
CommunityCommunityManagement.aesModerators, write thresholds, community metadata
AffiliationAffiliationTreasury.aesMulti-level referral accounting and payout
IdentityAddressLinkProvider links: preferred .chain name, X handle, Nostr key, bio, website
Token standardAEX-9 (FungibleTokenFull.aes)Community tokens and the ProtocolDAO token

The Tipping and factory contracts are singletons: one address per network. The Tipping address is in Deployed Addresses; the active factory is returned by GET /api/factory. Everything else is per community: creating a token deploys a fresh token, curve, sale, DAO and management contract in a single transaction.

The factory's deployment sequence:

  1. A new AEX-9 token contract for the community.
  2. A BondingCurveExponential contract with the community's pricing parameters.
  3. An AffiliationBondingCurveTokenSale referencing the token and the curve.
  4. A DAO contract, which becomes the sale's owner and beneficiary.
  5. A CommunityManagement contract with initial moderator and threshold settings.
  6. Registration of the community in the factory's on-chain registry.
  7. An optional initial buy, executed in the same transaction.

Off-chain layer

The service behind api.superhero.com is one NestJS process doing several jobs:

  • Indexer: a general-purpose chain indexer. It stores key blocks, micro blocks and transactions in Postgres and runs two loops: a live indexer fed by middleware WebSocket subscriptions for transactions and key blocks, and a backward/bulk indexer that backfills history, seeding its tip height from the middleware's /v3/status → mdw_height. The indexer reads the middleware, not the node directly, so the middleware's own lag is the floor on how fresh any API response can be.
  • Plugins: all domain decoding. Each plugin declares a startFromHeight() and a filters() list matching contract calls by contract id and entrypoint name (or spend transactions by predicate), then receives onTransactionsSaved(txs). The shipped set is bcl (token sales), bcl-affiliation, dex, governance, social (posts), social-tipping, social-graph and address-links.
  • Reorg handling: the chain is re-validated every 10 minutes. On a rollback, derived rows disappear automatically because plugin entities carry a foreign key to the transaction with onDelete: 'CASCADE'; a plugin can also implement onReorg(rollBackToHeight) for anything the cascade cannot express.
  • Trendminer: trending scores, charts and analytics, computed on a cron. In the web app this is the set of /trends/* views served from the same API. The formula is in Attention Markets.
  • Address-link provider wallet: the backend account that owns the x, nostr, bio, site and prefaens provider namespaces in AddressLink. It checks the off-chain proof (an OAuth session, a signed Nostr event, AENS ownership), then submits and pays for the link call the user signed. See Verifying Your X Account.
  • Clients: the React/Vite web app, the React Native mobile apps, and the agent skill for AI agents.

The backend reads the chain; it does not submit posts on a user's behalf. The post_without_tip_sig entrypoint exists for delegated posting; see Delegated & Gasless Posting.

The indexer reads the chain through its own endpoints:

Backend settingMainnet value
urlhttps://mainnet.aeternity.io
middlewareUrlhttps://mainnet.aeternity.io/mdw
websocketUrlwss://mainnet.aeternity.io/mdw/v2/websocket

Publishing a post

The web app calls post_without_tip directly from the connected wallet; the agent skill does the same from a MemoryAccount. The contract returns the new tip_id and emits PostWithoutTipReceived. The indexer picks the event up and the post appears in GET /api/posts with an id, content, media, created_at, sender_address and slug.

The relayed variant substitutes step 1: you Blake2b-hash the concatenated title and media, sign the prefixed digest off-chain, and hand (title, media, author, sig) to a submitter who calls post_without_tip_sig and pays the gas. The chain still records you as the author. See Delegated & Gasless Posting.

Buying a token

Step 1 is a dry call: price(count) on the sale contract, or the client-side estimator when the app wants an instant quote while you type. Step 2 is the only transaction. The app then decodes the receipt's events directly: the Buy event for the AE amount, the token contract's Mint event for the tokens you received, and a second Mint on a different contract address for the ProtocolDAO tokens. That is why the success screen can show your balance before the indexer has caught up.

Selling takes two transactions, because AEX-9 has no "transfer from" without prior approval:

  1. create_allowance (or change_allowance) on the community token, granting the sale contract permission to move count tokens.
  2. sell(count, minimal_return_required) on the sale contract. minimal_return_required is the slippage floor, and the call reverts rather than returning less.

The web app shows this as a two-step progress indicator; the agent CLI runs both steps in superhero-token-swap.mjs.

The live-update path

Token views do not poll. The web app opens a single socket.io connection to the websocketUrl from config (https://api.superhero.com on mainnet). The backend emits two channels:

ChannelEmitted when
token-updatedA token's aggregate state changes; the payload carries sale_address
token-historyA new chart point exists for a sale_address

Use token-updated as your signal to refresh token lists and transaction feeds from the REST endpoints.

Every emit comes from a server-side service that has already validated the payload, and every connected client receives it. The sale_address in each payload tells your client which token it is about, so it can update just the views that show that token.

An event is your cue to refetch, and the REST endpoints are the source of record. The web app reconnects on its own after 1000 ms; after a reconnect, refetch the views you have open so they pick up anything that changed in between.

Notifications ride a separate gateway, room-scoped rather than broadcast: server.to(address).emit('notification', …) and server.to(address).emit('unread-count', { count }).

Chat is neither: it is a Nostr connection to wss://relay.superhero.chat. If you self-host the web app, that origin must be in the server's CSP connect-src allowlist. See WebSocket.

Endpoints

The web app selects a network at build time with VITE_NETWORK (ae_uat for testnet; anything else is mainnet). A deployed container can override most of these at runtime through a window.__SUPERCONFIG__ object injected by the server.

KeyMainnetTestnet
BACKEND_URLhttps://api.superhero.comNot publicly documented
NODE_URLhttps://mdw.wordcraft.funhttps://testnet.aeternity.io
MIDDLEWARE_URLhttps://mdw.wordcraft.fun/mdwhttps://testnet.aeternity.io/mdw
EXPLORER_URLhttps://aescan.iohttps://testnet.aescan.io
compilerUrlhttps://v7.compiler.aepps.comhttps://v7.compiler.aepps.com
CONTRACT_V3_ADDRESS (Tipping)ct_2Hyt9ZxzXra5NAzhePkRsDPDWppoatVD7CtHnUoHVbuehwR8Nbct_2J1wuuw9urs9ADBh5QbvuPyUCLdKbW5YRkfhgPoN7rGjBbPiBW
GOVERNANCE_CONTRACT_ADDRESSct_ouZib4wT9cNwgRA1pxgA63XEUd8eQRrG8PcePDEYogBc1VYTqct_2nritSnqW6zooEL4g2SMW5pf12GUbrNyZ17osTLrap7wXiSSjf

The full address list, including the DEX contracts, is in Deployed Addresses.

Related