Skip to content

nexus-go — Reference ​

The complete reference for the social layer: the service architecture, the indexer/materializer/enrich model, communities and moderation, and the full bridge.* API surface. For a gentle introduction, start with the Quick guide.

The canonical source of truth for the running services lives in the repository (blurt/nexus-go); this page mirrors it for readers of the manual.

Architecture ​

nexus-go turns the chain into the social views a front-end needs. blurtd stays the source of truth; nexus-go derives feeds, communities, profiles, and notifications from it into PostgreSQL and serves them read-only.

text
  blurtd block stream
    -> nexus-go-indexer     (decode chain + custom_json into nexus_* tables)
    -> nexus-go-materializer (precompute hot views; rebuildable)
    -> nexus-enrich          (advance & validate derived datasets)
    -> PostgreSQL            (canonical nexus_* + materialized views)
    -> nexus-go-api          (read-only JSON-RPC: bridge.* + social condenser_api.*)
    -> front-ends

The four services ​

  • nexus-go-api — the JSON-RPC / bridge API service. It reads the store and serves social methods; it never signs, broadcasts, or holds keys.
  • nexus-go-indexer — the chain indexer and sync worker. It tails blurtd, decodes posts, votes, follows, reblogs, and community custom_json operations, and writes them into the canonical nexus_* tables.
  • nexus-go-materializer — a derived-view worker that precomputes expensive UI/API views out of the request path.
  • nexus-enrich — a post-sync job that advances and validates derived datasets after a baseline restore and catch-up. It is part of the normal path to a correct, current deployment — a data restore alone is not "done".

Data model ​

The canonical state lives in the nexus_* tables (posts, feeds, reblogs, roles, follows, …), written only by the indexer. Everything else is derived and rebuildable from them without changing chain truth.

The indexer ​

The indexer is the only writer of canonical state. It tails blurtd and normalizes chain activity into the nexus_* tables — including community moderation operations, which are emitted on-chain as custom_json with id: "community" (see Communities and moderation). Because a full historical index is large, deployments seed from a published data baseline and sync forward rather than indexing from genesis.

The materializer ​

The materializer builds disposable, rebuildable tables for expensive UI/API views. The canonical source remains the indexed nexus_* tables; materialized tables may be deleted and rebuilt at any time.

Risk controls keep it safe to run:

  • base tables stay canonical — the materializer never owns truth;
  • each materialized table records source_head_block, computed_at, and materializer_version;
  • API handlers read the materialized table first and keep a controlled fallback to the canonical query while a view is empty or missing;
  • refresh work happens outside the request path;
  • a single PostgreSQL advisory lock prevents two materializer processes from refreshing the same views at once.

Current modules:

  • Trending tags — nexus_materialized_trending_tags, derived from nexus_posts_cache, preserving bridge.get_trending_tags / condenser_api.get_trending_tags behavior without a large aggregation on cache misses.
  • Blog entries — nexus_materialized_blog_entries, derived from nexus_feed_cache, nexus_posts, and nexus_reblogs, preserving account blog semantics (top-level posts and reblogs stay in the blog; own posts inside a community stay out unless self-reblogged; undo-reblog removes the row on the next refresh). It keeps the newest entries per account; paging beyond the materialized window falls back to the canonical query. Posts the author has pinned to their blog lead the Blog timeline.

Accelerator, not authority

The materializer accelerates hot UI paths but never owns historical correctness: any miss falls back to the canonical query. Like the wallet indexer, it is an accelerator over a source of truth that is always reachable.

Blog pin ​

An author can pin up to three of their own top-level posts to the top of their Blog timeline. The pin is a custom_json with id: "blog", signed with the posting key by exactly one account, whose payload is {"account": "<author>", "permlink": "<permlink>", "action": "pin" | "unpin"} (account defaults to the signer and, if given, must be the signer). The indexer is the authority: the post must exist, belong to the signer, be top-level and not deleted, and a pin past the cap of three is ignored. The flag is stored in nexus_posts.is_blog_pinned and reported in the Bridge stats object as is_blog_pinned.

The pins are read from nexus_posts, not from the materialized timeline, so a pinned post leads the first page of the Blog wherever it was published: an author's own community post, which the Blog otherwise leaves out, appears there once pinned. The rest of the timeline never repeats a pin, and a page that ends inside the pins continues through the remaining ones before the timeline starts.

The blog pin is independent of the community pin (pinPost, is_pinned); neither clears the other. It changes only the Blog timeline: the account's Posts and Comments lists stay strictly chronological. Condenser offers the control to the author (see the condenser reference).

Communities and moderation ​

Communities are Blurt accounts (blurt-NNNNNN); their configuration and moderation are broadcast on-chain as custom_json with id: "community", and nexus-go indexes them into nexus_roles and related tables.

Moderation operations nexus-go understands include:

  • Post mute — mutePost / unmutePost mute a single post without muting the author. Stored in nexus_posts.is_muted; it does not write to nexus_roles.
  • Author role — setRole sets a community role for an account (e.g. a muted author is stored as nexus_roles.role_id = -2).

Effect on the API:

  • normal feeds (bridge.get_ranked_posts, bridge.get_forum_posts) exclude muted posts;
  • a direct post read still returns the post, but surfaces moderation state in the Bridge stats object (e.g. "hide": true).

Roles are UX, not a hard wall

Community post-permission types (Topic / Journal / Council) shape what front-ends show, but the backend does not hard-block a non-member's post, and neither does the chain. Treat community roles as a UX convention; any "only authorized authors" guarantee must come from the consuming view, not the community type.

Automatic beneficiary share audit ​

Publishing into a community gives the community a 3% beneficiary share, and a referred author gives their referrer 3%. These shares are a client-side convention that the chain does not enforce, so the indexer records whether each root post honours them. Once a block's operations are applied, every root post in it is judged against its final beneficiary list (an edit re-judges) and the verdict is written to nexus_posts_cache.honours_automatic_shares. "Owed" is a floor: paying more conforms, and an account that is both the community and the referrer is owed both shares as one route. The column is recorded for analysis only; nothing hides or penalizes a post on it, and it is not returned over RPC.

Account-owned data: drafts, templates, visibility ​

Some rows are not derived from the chain but written by an account through signed RPC calls: drafts, post templates, and the account-visibility preference. Each node keeps them in its own database and nodes do not replicate them, so clients fan writes out to every node and merge reads.

  • Signatures. Writes carry a per-operation signature verified against the account's posting key, with a burned nonce; the operation name (and, for visibility, the direction) is inside the signed message, so a signature cannot be replayed as another operation. Drafts may instead use a draft session grant meant for autosave; templates and visibility deliberately do not accept it.
  • Tombstones. draft_list and template_list return { ..., tombstones } so a client merging last-write-wins across nodes lets a delete win. A delete writes its tombstone even on a node that never held the row, so a delete made while the holding node is down cannot be undone when it returns. A repeated delete keeps its original deletion time.
  • Templates live in their own table, nexus_post_templates, keyed by a client-minted template_id. Unlike drafts (removed after 30 days untouched) they do not expire, and media referenced only by a template is not reclaimed as an orphan.

Account visibility ​

bridge.account_visibility_set lets an account ask the front-ends to withhold its blog and its profile; bridge.account_visibility_get reads the preference back and needs no signature (the answer is already observable). On a node that holds the preference:

  • bridge.get_profile answers as it does for an account with no profile, not with an error, and is checked ahead of the profile cache;
  • the account's posts are dropped from every post list (its blog, ranked feeds, search), judged by the post's author, so a reblog of a withheld post is dropped too;
  • a direct post read answers as it does for a post that does not exist, including through condenser_api.get_content.

This withholds, it does not delete: everything stays on the chain, and any client reading the chain directly still sees it. The table is created by migration v52→v53; a node whose schema is behind reports that rather than failing on a permission.

Media ​

Uploads go through the bridge.media_upload_* flow and are served at /m/<media_id>. Which node holds an upload is per-node bookkeeping (nexus_media_uploads) that cannot be rebuilt from the chain, but the object key in the shared bucket is deterministic (nexus/<image|video|other>/<author>/<YYYY>/<MM>/<media_id>).

The indexer therefore records every /m/ reference it sees in posts and comments, including media the node never hosted, in nexus_media_refs. When the local lookup finds nothing, the node reconstructs candidate keys from the author and the post's month (and its neighbours) and serves the first that exists, remembering the answer. Drafts and templates anchor the search for unpublished media. The fallback is bounded: an id that no post, draft or template references never reaches the storage backend, the candidate list and total time are capped, concurrent requests share one probe, and a complete miss backs off. So any node with the reference index can answer for any media id, whoever hosted it; the image proxy uses that to recover images whose original host no longer answers.

nexus_media_refs is derived from the chain and travels in a published dump; nexus_media_uploads is per-node custody and must not be restored from another node's dump.

Schema version ​

bridge.get_nexus_version returns version, commit, commit_time and db_version, the schema migration the node's database is at. The binary and the schema move separately (migrations are applied out of band), so db_version is what says whether a node can serve a feature that needs a given table. It is omitted, not zero, when it cannot be read. Recent migrations:

MigrationAdds
v47→v48the automatic-share audit column
v48→v49nexus_post_templates
v49→v50nexus_posts.is_blog_pinned and its partial index
v50→v51nexus_media_refs, backfilled from indexed posts
v51→v52idx_nexus_posts_created_global, the chronological-feed index
v52→v53the account-visibility table

The bridge API surface ​

Read-only JSON-RPC under the bridge. prefix, grouped by domain. (Method parameters and response shapes are defined by the service; this catalog is the map of what it exposes.)

Feeds and posts ​

text
bridge.get_ranked_posts        bridge.get_account_posts     bridge.get_forum_posts
bridge.get_post                bridge.get_post_header       bridge.get_discussion
bridge.get_comment_tree_metrics                             bridge.normalize_post

Communities ​

text
bridge.get_community           bridge.get_community_context
bridge.list_communities        bridge.list_pop_communities  bridge.list_top_communities
bridge.list_community_roles    bridge.list_community_titles
bridge.list_subscribers        bridge.list_all_subscriptions

Profiles and social graph ​

text
bridge.get_profile             bridge.get_followers         bridge.get_following
bridge.get_follow_count

Notifications ​

text
bridge.account_notifications   bridge.post_notifications    bridge.unread_notifications
text
bridge.search_posts            bridge.search_comments
bridge.search_accounts         bridge.search_tags

Drafts, templates and visibility ​

text
bridge.draft_save      bridge.draft_list      bridge.draft_get      bridge.draft_update      bridge.draft_delete
bridge.template_save   bridge.template_list   bridge.template_get   bridge.template_update   bridge.template_delete
bridge.account_visibility_set                 bridge.account_visibility_get

See Account-owned data.

Media ​

text
bridge.media_upload_init       bridge.media_upload_complete   bridge.media_upload_confirm
bridge.media_upload_abort      bridge.media_get

Witnesses ​

text
bridge.get_witness             bridge.list_witnesses        bridge.get_witness_history
bridge.get_witness_voters      bridge.get_account_witness_votes
bridge.get_account_witness_proxy

Analytics and discovery ​

text
bridge.get_trending_topics     bridge.get_trending_tags     bridge.get_top_influencers
bridge.get_top_commenters      bridge.get_engagement_metrics  bridge.get_reward_analytics
bridge.get_payout_stats        bridge.get_historical_stats

NFT, referrals, and version ​

text
bridge.nft_*                   bridge.referral_accounts     bridge.referral_accounts_count
bridge.get_version             bridge.get_nexus_version

condenser_api compatibility ​

nexus-go also answers the social subset of condenser_api.* — content and discussion reads (get_content, get_content_replies, the get_discussions_by_* family), account blogs (get_blog, get_blog_entries), and the follow API (get_followers, get_following, get_follow_count). This keeps the classic condenser API backed by the same social store, while consensus methods continue to the node.

In these responses payout_at is resolved from the post's payout_at, then cashout_time; a value at or before the post's creation is treated as absent, and when neither can answer the chain's "no such date" sentinel is returned, so a client can tell "unknown" from "already paid out".

The chronological feed ​

The first page of the global created feed (no start_author / start_permlink, not a community tag) is answered from the consensus node, as the source of truth against publish lag. Pagination of that feed, community created feeds, and every request while the consensus node is unreachable are served locally, from the idx_nexus_posts_created_global index on nexus_posts (created_at DESC, id DESC).

Routing through the RPC proxy ​

Front-ends use a single RPC endpoint. The RPC proxy routes by method prefix: bridge.* and the social condenser_api.* reads go to nexus-go-api; wallet_history.* goes to the wallet indexer; everything consensus goes to the node. From the browser it is one origin and one URL — the fusion happens server-side.

Configuration ​

nexus-go is configured through environment variables. The runtime essentials:

SettingPurpose
NEXUS_DATABASE_URLPostgreSQL connection for the services (a Docker-reachable variant is used inside containers).
BLURT_RPC_URLblurtd JSON-RPC endpoint the indexer reads from. A dedicated node is preferred for bootstrap and sync.
NEXUS_API_PORTPort the API service exposes (fronted by the RPC proxy).

Materializer toggles (defaults shown):

VariableDefaultPurpose
NEXUS_MATERIALIZER_ENABLEDtrueMaster switch for the materializer.
NEXUS_MATERIALIZER_REFRESH_SEC30Refresh cadence for materialized views.
NEXUS_MATERIALIZER_TAGS_ENABLED / NEXUS_MATERIALIZER_TAGS_LIMITtrue / 1000Trending-tags module and its cap.
NEXUS_MATERIALIZER_BLOG_ENABLED / NEXUS_MATERIALIZER_BLOG_REFRESH_SEC / NEXUS_MATERIALIZER_BLOG_LIMIT_PER_ACCOUNTtrue / 60 / 200Account-blog module, its cadence, and per-account window.

The full deployment configuration (image selection, data baselines, sync and enrichment orchestration) is operator-specific and documented in the repository; it is intentionally out of scope for this manual.

Deployment ​

Baseline restore, sync, enrichment, and post-enrichment validation are covered in the repository's deployment docs. This manual keeps the architecture and API surface; the repository keeps the operator procedures.

Released under the MIT License.