Skip to content

Condenser — Reference ​

The complete reference for the web front-end: its architecture, how it talks to the stack, the main views, configuration, and how signing works. For a gentle introduction, start with the Quick guide.

Condenser (blurt/condenser) is the React.js web interface to Blurt. It is a server-side-rendered single-page app: a Koa server renders the first response, and the browser hydrates and takes over.

Architecture ​

text
  browser  ◄──►  Koa SSR server  ──►  RPC proxy  ──►  nexus-go / blurtd
   React            (first paint,        (one URL)      (social / consensus)
   Redux + saga      sessions)
  • React renders the UI. Redux holds application state; redux-saga runs all side effects — the RPC reads, transaction signing, and broadcasts.
  • react-router resolves routes on both server and client (ResolveRoute, RootRoute).
  • Koa is the SSR server: it renders the initial HTML, serves assets, and manages server sessions (keyed by SDC_SESSION_SECRETKEY).
  • blurtjs (@blurtfoundation/blurtjs) is the chain client and crypto library — it builds, signs, and broadcasts transactions.
  • i18n: the UI is translated into many locales through Translator and the locales/ catalogs.

The app code lives under src/app (components/, redux/, utils/, client_config.js, routing, and locales).

How it talks to the stack ​

Condenser never talks to a database or holds chain state; it uses one RPC endpoint and lets the RPC proxy fan the calls out:

  • Browser calls go to SDC_CLIENT_BLURTD_URL; SSR server calls go to SDC_SERVER_BLURTD_URL. Both point at the RPC proxy.
  • Social reads (feeds, communities, profiles, discussions) resolve to nexus-go; consensus reads and broadcasts go to blurtd.
  • Drafts use a draft API endpoint (SDC_DRAFT_API_ENDPOINTS), served by nexus-go.
  • Images are fetched through an image proxy (SDC_IMAGE_PROXY_PREFIX) and uploaded to an image service (SDC_UPLOAD_IMAGE_URL).
  • Drafts, post templates and deletes are fanned out to every node in SDC_DRAFT_API_ENDPOINTS, because each nexus-go node keeps those rows in its own database and nodes do not replicate them (see Publishing).

Feeds are corroborated across verified nodes ​

The nexus-go nodes behind a public endpoint do not always agree: one that has not caught up still serves a post its author has just deleted, or has not yet indexed one just published. A feed read therefore does not trust whichever single node answers. Two rules apply, on the server render and in the browser alike:

  1. Only verified nodes are asked. Every node in SDC_ALT_API_ENDPOINTS is probed with bridge.get_nexus_version; only nodes at or above min_nexus_version (SDC_MIN_NEXUS_VERSION, default 2.2.0) take part. Development builds are accepted; a node that cannot be verified is not asked. Verdicts are sticky, so a node that answered once is not dropped over a single missed probe, and a node that never answered is retried with backoff.
  2. A post needs two nodes. A post is shown when two verified nodes that answered both report it. A deleted post is simply absent from a feed (there is no tombstone to merge), so corroboration rather than union is what keeps it from coming back. If only one verified node answers, its answer is used rather than rendering an empty feed; if none answers, the caller falls back instead of treating silence as an empty feed.

The order is taken verbatim from the responding node that carries the most of the surviving posts; anything it lacks is placed by recency. A post the viewer has just published counts their own accepted broadcast as the second corroboration, so it appears at once from a single verified node. The fan-out is bounded (two responses are enough to proceed, with a short grace period and a hard deadline), and the first version pass in the browser waits at most a short budget, so a node that is down never holds up a reader's first feed.

Verified failover ​

blurtjs switches to a backup RPC node after failover_threshold transport errors (default 2) and never switches back on its own. Because a couple of client-side blips (a phone changing towers, a suspended tab) can trip that counter against a healthy node, condenser verifies the switch before saying anything: it probes the preferred node directly with a short-timeout request outside blurtjs. If the node answers, the app switches back quietly; only a node that really does not answer shows the failover banner, and the app heals itself and removes the banner when the node recovers.

Signing and accounts ​

Keys never leave the user's device. Condenser signs transactions client-side with blurtjs and then broadcasts them through the proxy to blurtd. Users sign in with a posting key or through a keychain extension (e.g. WhaleVault); the front-end never sees or stores private keys server-side. The worst a compromised front-end can do is show wrong data — it cannot move funds without the user's key.

  • What is signed is only the chain operation. Condenser attaches its own bookkeeping to an operation while it is in flight (the dialog title, success hooks); that field is stripped from the copy handed to the signer on every broadcast path, so a signing extension serializes exactly what the node will.
  • Confirmation dialogs name the action, not the chain operation. Following, unfollowing, muting and unmuting an account, subscribing to a community, muting, flagging or pinning a post, changing a role or title, editing community settings, claiming rewards and deleting a post are each named, and the editor confirms a post, a comment or an edit according to what it is sending.
  • Logging out also clears the AI assistant's stored conversation and any provider key saved in this browser, alongside the editor's working copies.

Main views ​

  • Feeds — trending, hot, new, promoted, and per-community timelines (corroborated across nodes, see above).
  • Post & comments — the discussion view with threaded replies and voting. Under a post, and on reply cards in a profile's replies tab, an open "Post your reply" box opens the reply editor in place (or the login dialog when signed out). Clicking an image in a post or comment body opens a lightbox that loads the original, unproxied file, with a gallery for several images (arrows, keyboard, swipe), wheel / double-click / pinch zoom from 1x to 4x, and drag to pan. It closes with ×, Escape, a click outside, or the back button.
  • Editor — markdown post/comment composer, with community targeting, beneficiaries and templates (see Publishing). After publishing, the author lands on their own posts tab, which shows the new post at once.
  • Drafts and templates — /drafts and /templates, both in the account menu.
  • Communities — community home pages, subscriber and role views.
  • Profile — account pages (blog, posts, replies, wallet summary).
  • Wallet views — balances, history, and transfers (the standalone wallet app is the dedicated tool).
  • dApps directory — /dapps, a card grid read from DAPPS_URL. Each entry shows the logo its JSON names in logo (falling back to the Blurt mark when none is named or the image fails to load), and a notice explains how to get listed.
  • Brand guidelines — /brand.html, a help page with the logo usage rules and every interface colour with the stylesheet token that carries it. The accent is the brand orange #ff5a00, the colour of the logo and the application icons. The Terms of Service grant a limited licence to use the marks for non-commercial promotion and community merchandise under these guidelines.
  • Notifications, search, settings.

Voting after the payout window ​

A vote cast after a post's payout window has closed cannot pay out on its own, but it is not refused. When appreciator_account (SDC_APPRECIATOR_ACCOUNT) is set, voting on such a post first shows a notice: an appreciation service watches for these late votes and answers by publishing a comment under the post with the author as its main beneficiary. If that account's comment already exists it is offered too, since a vote there is the one that reaches the author. The reader then decides. Whether the window is closed is read from the node's is_paidout flag, then the scheduled payout_at / cashout_time; when nothing can answer, the window counts as open. With appreciator_account empty, votes go out exactly as before.

Publishing ​

Beneficiaries ​

The editor builds the comment_options beneficiary list from one place, and what the confirmation shows is that same list:

  • Automatic shares are fixed. Publishing into a community gives the community 3%, and an author who was referred gives their referrer 3%. These are never reduced or edited away in the editor (the convention is client-side; the chain does not enforce it).
  • Manual shares yield. Shares the author adds go on top; when the total does not fit, the author's own shares are trimmed, largest first.
  • One entry per account. An account earning several shares becomes one entry with their sum (the chain rejects a duplicate beneficiary), within the limit of eight routes and never over 100%.
  • Accounts must exist. Each beneficiary name is checked with lookup_account_names as it is typed and again on submit; a name that is not an account is marked invalid in its field, because the chain would reject the whole transaction. A lookup that fails leaves the name undecided rather than blocking the post.

Post templates ​

Templates belong to the account, not the browser. They are stored in nexus-go through the bridge.template_* methods (save, update, get, list, delete), each signed per operation with the posting key. Like drafts, writes fan out to every node in SDC_DRAFT_API_ENDPOINTS, reads merge what every node returns (last-write-wins, with tombstones for deletes), and a list that no node answered is reported as unknown rather than empty. The /templates page lists, opens and deletes them; a delete cannot be undone. Templates that older versions kept in localStorage are migrated once, and the browser copy is cleared only after every template is confirmed saved.

Pinning a post to your blog ​

The author can pin up to three of their own top-level posts to the top of their Blog with the blog pin, a blogcustom_json that nexus-go indexes. The control sits beside edit on the full post and in the corner of each card on the author's own Blog and Posts tabs; a pinned post wears the Pinned chip there, and in feeds or on other accounts' blogs neither the chip nor the control shows. A community post can be pinned too, and then appears on the Blog.

A fourth pin is refused with a notice before anything is broadcast, since the indexer would ignore it; if the count cannot be read the pin goes ahead and the indexer enforces the limit. Once the chain accepts a pin, the Blog list the page holds is updated at once, and the pin is remembered for a minute and applied again after every state or feed merge, so nodes that have not indexed it yet cannot undo it on screen (the same treatment as the viewer's own deletes and votes).

Deleting a post ​

A delete shows its progress, confirms when it is done, and moves the author off the deleted post's page. It is also remembered locally as a tombstone, so the post is dropped from whatever any node still serves until every node agrees.

Phishing list. Links whose address matches the phishing list are withheld: an anchor is replaced with a notice, and the same address written as plain text is removed rather than printed back, so it cannot be copied either. The list in effect is the union of two sources:

  • phishing_domains (SDC_PHISHING_DOMAINS) in the configuration, which is a floor that never shrinks;
  • the published list at phishing_url (SDC_PHISHING_URL), which the server fetches and folds in every five minutes, so a newly listed address reaches readers without a release. An empty, truncated or malformed document leaves the list as it was. The deployed site points phishing_url at the COAL service's /destinations list, which includes destinations its detector resolves on its own.

The merged list travels to the browser with the rest of the configuration, so server and client check the same thing. Matching is a lowercase substring test over the whole URL, so entries must be lowercase, and a shortened link is listed by its full path, never by the bare shortener host.

COAL. The abuse list (coal_url) marks accounts with a reason code. An account marked with the SCAM code has its content (profile and banner, full post, feed card, comment) placed behind a diagonal band warning never to enter keys, with a reveal control and, for signed-in readers, a mute control. All links in that account's posts and comments are removed, even after revealing. Its comments are taken out of the thread and grouped at the end behind one control, beside the group of low-rated comments.

Configuration ​

Condenser is configured with SDC_* environment variables (mapped in config/custom-environment-variables.json). The ones that wire it to a network:

VariablePurpose
SDC_SESSION_SECRETKEYserver session secret; must stay constant across restarts
SDC_CLIENT_BLURTD_URLRPC endpoint the browser uses (the RPC proxy)
SDC_SERVER_BLURTD_URLRPC endpoint the SSR server uses
SDC_USE_APPBASEuse the appbase JSON-RPC calling convention
SDC_CHAIN_IDchain id to sign against
SDC_ADDRESS_PREFIXpublic-key address prefix (BLT)
SDC_DRAFT_API_ENDPOINTSnexus-go nodes that drafts, templates and deletes fan out to
SDC_ALT_API_ENDPOINTSalternative RPC endpoints; also the candidate pool for feed corroboration
SDC_MIN_NEXUS_VERSIONminimum nexus-go release a node must report to answer feed reads (min_nexus_version, default 2.2.0)
SDC_APPRECIATOR_ACCOUNTaccount of the appreciation service behind the late-vote notice; empty disables it
SDC_IMAGE_PROXY_PREFIX / SDC_UPLOAD_IMAGE_URLimage proxy and upload service
SDC_PRICE_INFO_URLprice feed for wallet displays
SDC_COAL_URL / SDC_NSFW_URLabuse (COAL) and NSFW lists
SDC_PHISHING_URL / SDC_PHISHING_DOMAINSpublished phishing list (refreshed every five minutes) and the configured floor
WALLET_URL / DAPPS_URL / SPECIAL_POSTS_URLlinks to the wallet app and related surfaces

Base defaults live in config/default.json / config/production.json; the SDC_* variables override them at runtime. A few keys have no SDC_* variable and are set in the config files, such as failover_threshold (default 2).

Build and run ​

Condenser builds the React bundle and runs the Koa server (npm run build, then npm run production; npm start for development). It ships as a Docker image (registry.gitlab.com/blurt/blurt/condenser, plus an -arm64 variant). Provide a stable SDC_SESSION_SECRETKEY and point the SDC_*_BLURTD_URL variables at your RPC endpoint. See the repository for the current build and deployment details.

  • Tests. npx jest runs the suite with no extra flags; CI runs it as the test-unit job and publishes JUnit and coverage reports on the merge request.
  • Build identity. Every CI-built image serves its commit at /__version.json. The app uses it to notice a newer deployment, and scripts/check_environments.sh (run by a scheduled CI job) compares what each deployed environment serves against main and reports how far behind it is.

Where it fits ​

Condenser is the flagship read/write UI at the top of the stack. It consumes nexus-go and blurtd through the proxy — the same fusion every other page in this manual describes, seen from the browser's side: one URL in, the right backend serving each call.

Released under the MIT License.