Kaspa Forge
Deep dive

Images, Moderation, and Tips: Inside a Kaspa Social dApp

20 Aug 2026 By OfficeForge's AI team · human-reviewed 12 min read
Kaspa Social dApp: Images, Moderation, Tips & Indexing

A Kaspa social dApp that stores every post on-chain faces a fundamental tension: the BlockDAG is immutable and neutral, but a front-end that serves images needs a way to keep the service usable and legally compliant. Kaspa Boards resolves this by splitting what is permanent (signed post text, image hashes, tips) from what is hosted (image bytes), then layering a zero-budget moderation pipeline over the hosted side only.

This article walks through that architecture: what lands on-chain, how content-addressed images work, the three-tier moderation system, how KAS tips attach to posts, and why the entire index remains rebuildable from the DAG alone.

The on-chain / off-chain split

Every Boards post is a signed Kaspa transaction. The transaction payload carries the post text, any reply-to reference (a parent post's transaction ID for threading), and — when an image is attached — the SHA-256 hash of the normalized image bytes. That hash is the only image-related data that touches the chain.

The actual image bytes never enter a Kaspa transaction. They are stored on KaspaForge's infrastructure, keyed by their SHA-256 digest. A viewer's browser fetches the image via an API endpoint, and the server checks whether that hash is currently servable before returning bytes.

Think of it like a postcard system: the message is written in permanent ink on a card that anyone can read (the on-chain transaction), while the photograph clipped to it is held in a filing cabinet that the post office controls (the hosted image store). The filing cabinet can remove a photo; it cannot alter what the card says.

This split has a concrete consequence: the chain is neutral by construction. No moderation decision — automated or human — can alter what the BlockDAG records. A hidden post's text remains readable by any node; only the hosted image can be withheld. This is not a policy aspiration — it is a structural property of where the data lives.

Definition

An image is stored and retrieved by the hash of its content (SHA-256), not by an arbitrary filename or database ID. Two uploads of identical bytes produce the same hash, enabling automatic deduplication and hash-based blocklisting.

Content-addressed images: dedup, blocklist, and GC

When a user uploads an image, the server normalizes it (format validation, size cap), computes its SHA-256, and checks the blocklist before writing anything to disk. If that hash is already blocklisted — meaning an operator permanently banned those exact bytes — the upload is rejected with a 403. The bytes never touch storage.

If the hash passes the blocklist check, the server applies content-addressed deduplication: if the exact bytes already exist on disk (from an earlier post by anyone), no second copy is stored. A staging row links the hash to the new post, and the upload succeeds. This is the same principle behind Git's object store or IPFS's content-addressed blocks — identity is derived from content, not from a name someone chose.

Over time, a garbage-collection sweep runs on a timer. Images no longer referenced by any visible post or staging row are eligible for deletion from disk. This reclaims storage without affecting the on-chain record. The post that originally referenced a garbage-collected image degrades gracefully to text-only — the same UX a reader sees when an image is hard-deleted by moderation.

The blocklist, by contrast, is never garbage-collected. A blocklisted SHA-256 stays banned permanently until an operator explicitly removes it. This is intentional: because the storage layer is content-addressed, deleting an image without blocklisting its hash would allow the exact same bytes to be re-uploaded and re-stored under the identical hash tomorrow. The blocklist is the one piece of state that outlives the content it describes.

Three-tier moderation: $0 per post by design

Boards earns nothing, so the moderation budget is zero dollars per post by construction. The system is built around that constraint: an automated classifier handles the common case for free, a report button surfaces edge cases, and a human operator makes final calls only when needed.

Tier 0 — local NSFW classifier at upload. Every uploaded image passes through a local inference step before the upload response is returned. The classifier is a pure-Rust implementation of GantMan's MobileNetV2 five-class model (categories: drawings, hentai, neutral, porn, sexy), running via the nsfw crate with a tract engine — no C++ dependencies, no external runtime downloads at build time. The model weights live as a file on disk; if the file is missing or corrupt, the classifier is disabled and a boot-time alert fires.

The classifier produces five float scores. A pure function maps those scores to a decision:

let explicit = porn + hentai;
if explicit >= 0.70       => Blocked
else if explicit >= 0.35
     || sexy >= 0.80      => Gray
else                      => Clean

All three thresholds are configurable via environment variables. The raw scores and the resulting decision are stored in a verdicts table keyed by SHA-256, so an operator inspecting the gray queue sees the actual numbers, not just a label.

Crucially, the upload's HTTP response is the same 200 with the same SHA-256 regardless of the verdict. Classification gates *serving*, not *uploading*. A Blocked image is stored but never served on a worksafe board; a Clean image serves immediately. This means the classifier's latency never blocks the upload path — inference runs in a blocking task off the async executor, and the response fires as soon as storage is confirmed.

Tier 1 — viewer reports. Every rendered post carries a report button. Clicking it sends a report with the post's transaction ID. The server upserts a report row, incrementing a counter and refreshing the timestamp. On the first report and every fifth thereafter, the operator gets a Telegram notification with a direct link into the thread and a button to open the admin console. The client persists a reported flag in localStorage so the button renders as a disabled label after use — no silent re-clicks inflating the count for no reason.

Tier 2 — operator console. The admin API provides endpoints for hiding posts, threads, or entire boards; hard-deleting image bytes (immediate disk removal, not just a serve gate); adding or removing blocklist entries; and resolving reports. Authentication reuses the same token gate that protects the Kaspa Escrow and Marketplace admin routes — Boards is a third tenant of an existing auth system, not a separate credential silo.

The serve gate has a clear precedence order:

1. Blocklist always wins. A blocklisted hash returns 404 on every board, always. 2. Red-board override. If the board allows NSFW content, images serve regardless of verdict — the classifier is advisory-only there. 3. Worksafe default. A Blocked verdict starves the image; the post renders as text-only. 4. Missing verdict defaults to clean. This grandfathers every image uploaded before the classifier existed — no silent breakage on deployment.

The operator console's thumbnail endpoint deliberately bypasses the serve gate. A human reviewer must be able to *see* a blocked or gray image to judge it; the admin token is the only gate on that path.

KAS tips: standard transactions, attributed to posts

A KAS tip on Boards is a standard Kaspa transaction. The sender's wallet creates a transaction paying the post author's address, and the indexer attributes the tip to a specific post by its transaction reference. The indexer aggregates tips per post and displays the running total alongside the thread.

Because tips are ordinary Kaspa transactions — not a custom token or a platform-internal ledger — they inherit the same properties as any KAS transfer: the sender pays the network fee (currently 0.0001 KAS per UTXO utilized, a wallet/node policy as described in the Kaspa wiki), the transaction confirms through the same GHOSTDAG consensus as every other transfer, and the recipient controls the funds in their own wallet. KaspaForge never custodies tips — the KAS moves on-chain from sender to recipient, same as any peer-to-peer payment.

Indexing and rebuildability

The Boards indexer is a view over on-chain data, not the source of truth. Every post, reply reference, and tip is a Kaspa transaction. If the KaspaForge database were lost entirely, a fresh indexer could scan the BlockDAG, find all Boards-related transactions by their identifying patterns, and reconstruct the full text index, thread tree, and tip totals.

This is a meaningful architectural property. The indexer's database is a cache — a fast, queryable projection of immutable chain data. The hosted image bytes are the one piece that cannot be reconstructed from the DAG alone (the chain stores only the SHA-256 reference, not the pixels). But the text, the threading, the authorship signatures, and the tip history are all rebuildable.

The trade-off is latency. Scanning the full DAG to rebuild an index is not instant — it takes time proportional to the chain's length and the density of Boards transactions. In normal operation, the indexer processes new transactions in near-real-time as blocks merge. A full rebuild is a disaster-recovery path, not a routine operation.

Kaspa Boards is live. Every post is a signed on-chain transaction; images are content-addressed and moderation is layered over the hosted side only. Explore the boards at KaspaForge Boards, or read more about the broader architecture in the Kaspa Forge docs.

Create a vault

Trade-offs and honest limits

The NSFW classifier detects nudity, not age. A Blocked verdict is a statement about pixel content — how much skin, how explicit the imagery. It is not, and was never designed to be, a CSAM detector on its own. The real defense against illegal content is the report-to-hard-delete-to-blocklist loop: a human reviews a flagged image and can permanently remove it from the hosted index. The on-chain SHA-256 reference and the post text remain permanent — this is an honest limitation of storing references on an immutable ledger.

No LLM reviews any image. The earlier idea of running a language model on every post was abandoned as anti-economics for a free service. Tier 0 is the classifier, Tier 1 is the report button, and the operator console is where a human makes the actual call.

Image bytes are off-chain and centrally hosted. This is the single point of trust in the system. If KaspaForge's image hosting goes down, posts degrade to text-only. The on-chain record survives, but the pixels do not — unless someone independently cached them. Content-addressing helps: anyone who has the bytes can verify them against the on-chain hash, and a third party could mirror the image store with cryptographic proof that every served byte matches its declared digest. But no decentralized image storage is integrated today.

The blocklist is permanent by design. An operator who blocklists a hash cannot accidentally undo it through garbage collection or a server restart. Removal requires an explicit admin action. This is a feature for enforcement, but it means blocklist hygiene depends on the operator's judgment.

Moderation is per-index, not network-wide. A hard-deleted image disappears from KaspaForge's Boards index. The same bytes, referenced by the same SHA-256 in a different indexer or a node's raw transaction data, remain accessible to anyone who can read the chain. This is the same trade-off every on-chain content system faces: the ledger is neutral, and moderation is a property of the viewer, not the chain.

FAQ

Is post text ever censored on Kaspa Boards?

No. Post text lives on the Kaspa BlockDAG and is deliberately never filtered or removed by the indexer. Moderation applies only to hosted images served by KaspaForge — the chain is neutral by design.

How do KAS tips work on a Boards post?

A tip is a standard Kaspa transaction sending KAS to the post author's address. The indexer attributes the tip to a specific post and displays the running total alongside the thread.

Can the entire Boards index be rebuilt from scratch?

Yes. Every post, reply, and tip is an on-chain transaction. A fresh indexer can scan the Kaspa BlockDAG and reconstruct the full text index, thread tree, and tip totals without relying on any KaspaForge database snapshot.

What happens when someone reports an image?

The report increments a counter and pings the operator via Telegram. A human reviews the image in the admin console and can hard-delete the bytes, optionally adding the SHA-256 to a permanent blocklist that prevents re-upload.

Does the NSFW classifier catch every problematic image?

No. The classifier detects nudity — not age, context, or legality. It is one layer in a three-tier system: automated scoring, viewer reports, and human operator review. CSAM defense relies on the report-to-blocklist loop, not the model alone.

Are Boards posts permanent?

The on-chain transaction — including post text and the image SHA-256 reference — is permanent and public. Hosted image bytes can be hard-deleted by the operator, in which case the post degrades to text-only.

This article was researched, written and illustrated by OfficeForge's AI team — the same AI employees that built and run Kaspa Forge. Founder-directed, human-reviewed.

Non-custodial · open source

Put your KAS where theft can be cancelled

A covenant vault on Kaspa mainnet: your keys, your rules, our tooling. Free on-chain, forever.

Create a vault