Skip to content
gamedev · gaming · rust · proto · assets

Open catalog for gameAssets

An open marketplace for game assets — think Fab / itch.io / CraftPix / GameDevMarket, scoped strictly to game assets: 3D models, 2D art, audio, VFX, shaders, tools/plugins, and complete projects. A decoupled Rust/Axum API sharing KBVE identity, media, transport, and vocabulary with the Job Board.

Private source, public previews

The buyer never sees or receives the real asset before they're entitled to it. Listings show derived, watermarked, degraded previews generated server-side; source files stay private and grant-gated.

  • Game-native — engine/format facets, not generic categories.
  • Shared identity — one KBVE account across both products.
Spec / archStatus
Rust + AxumStack
Cloudflare R2Storage
protobufWire

What it gives you

Features

Game-native catalog

Categories and facets speak game dev — engine version, render pipeline, tris/verts, humanoid rig, atlas size, sample rate — not generic graphics/audio.

Open supply, curated trust

Anyone can list (volume), but verification badges, ratings, and moderation surface quality. Trust is earned and displayed, not gatekept at the door.

Shared ecosystem

One KBVE account, one identity, one engine/tool vocabulary across the Job Board and the Asset Marketplace — a freelancer can take gigs and sell assets.

Preview protection

Source files live in private R2 with no public URL, served only via short-lived signed URLs gated on a LicenseGrant; the public only ever sees watermarked derivatives.

Status & positioning

Asset Marketplace

Status: Spec / architecture — no app code yet. This is the document to review/edit before building. Sibling product to the Job Board — the Job Board sells services, the Asset Marketplace sells digital goods. They share identity, media, transport, and technical vocabulary.

An open marketplace for game assets — think Fab / itch.io / CraftPix / GameDevMarket, scoped strictly to game assets: 3D models, 2D art, audio, VFX, shaders, tools/plugins, and complete projects. Creators list assets; buyers discover them. v1 is a lead-gen catalog (browse, storefronts, license terms, wishlists, external buy links); on-platform checkout with per-sale commission and downloadable delivery ships in a later phase.

Strictly game assets. Unlike a generalist digital-goods store, the category tree, tagging (engines/tools/formats), and previews are built for games — engine compatibility, poly counts, rig types, tilemap formats, loopable audio, license suitability for shipped games. That focus is the wedge.

Architecture is decoupled: a standalone Rust/Axum REST API (own DB access, own first-party auth — no third-party auth SaaS) serving a separate frontend client (framework TBD) over HTTP.

  • Game-native catalog. Categories and facets speak game dev (engine version, render pipeline, tris/verts, humanoid rig, atlas size, sample rate) — not generic “graphics/audio.”
  • Open supply, curated trust. Anyone can list (volume), but verification badges, ratings, and moderation surface quality. Trust is earned and displayed, not gatekept at the door.
  • Shared ecosystem. One KBVE account, one identity, one engine/tool vocabulary across the Job Board and the Asset Marketplace — a freelancer can both take gigs and sell assets.

Who's on it

Personas & roles

PersonaWhat they doGating
SellerCreates a storefront, lists assets, sets prices + license tiers, uploads files.Open (sign up → list); optional verification tier
BuyerBrowses, wishlists, reviews; purchases + downloads (checkout phase).Open
AdminManages categories, moderates listings, handles DMCA/abuse reports, reviews verification + AI-disclosure.Internal
  • One account can be both seller and buyer, and is the same KBVE account used on the Job Board (capabilities tracked as a bitmask on the existing User.role).
  • Open marketplace: no pre-approval to list. Quality is managed post-hoc via moderation, reports, ratings, and an optional verified seller badge.

Vocabulary

Asset taxonomy

Two facets, both controlled (no free text) so filtering works:

  1. Category tree (asset-store specific, hierarchical) — what the asset is.
  2. Engine/tool/format tags (shared with the Job Board) — what it works with.

Category tree (hierarchical, admin-managed)

Section titled “Category tree (hierarchical, admin-managed)”
  • 3D — Characters, Creatures, Environments, Props, Vehicles, Weapons, Modular Kits
  • 2D — Sprites, Tilesets, Characters, Backgrounds, UI/GUI, Icons
  • Textures & Materials — PBR materials, Substance, decals, skyboxes
  • Audio — Music (loops/tracks), SFX, Ambience, Voice/VO
  • VFX & Shaders — particle FX, shader packs, post-processing
  • Tools & Code — plugins, editor tools, scripts, systems
  • Complete Projects / Templates — game templates, starter kits, demos
  • Fonts — game-ready/display fonts

Engine/tool/format tags (reuse Job Board vocabulary)

Section titled “Engine/tool/format tags (reuse Job Board vocabulary)”

Unity, Unreal, Godot, GameMaker, Bevy; Blender, Maya, ZBrush, Substance, Photoshop, Spine, FMOD, Wwise; formats: fbx, gltf, obj, png, wav, ogg, unitypackage, uasset. These already exist as Taxonomy rows — reuse them, don’t duplicate.

Categories live in a new hierarchical AssetCategory; engine/tool/format tags reuse the shared Taxonomy (see protobuf section).

How it works

Core flows

  1. Sign up (first-party auth — shared KBVE account).
  2. Create a storefront (slug, name, tagline, logo/banner).
  3. (Optional) apply for verified seller badge later.
  1. Create asset — title, category, engine/tool/format tags, description.
  2. Upload file bundle as an AssetVersion (changelog + source files → private R2). The pipeline then auto-generates watermarked, degraded previews (turntable/low-poly viewer, downscaled images, low-bitrate audio clips) — the seller picks which to feature. (Checkout phase; in lead-gen v1 a seller may instead point paid assets to an external_url.)
  3. Set license offerings — Standard and/or Extended price, or mark the asset free (see Licensing).
  4. Declare AI-content disclosure.
  5. Publish → PUBLISHED (or PENDING_REVIEW if moderation gate is on).
  1. Browse / search / filter by category, engine, format, price, rating, license.
  2. View asset detail — previews, files/specs, license terms, version history, reviews.
  3. Wishlist / add to a collection; follow sellers/storefronts.
  1. Buyer selects a license tier → checkout (Stripe).
  2. Payment captured → Order recorded (reuses Invoice) → platform takes commission.
  3. LicenseGrant issued → time-limited signed download URL to the AssetVersion files (R2).
  4. Buyer can re-download owned versions + receive update notifications.
  5. Verified-purchase review unlocked.
  • Seller payouts via Stripe Connect (checkout phase).
  • Admin: category management, listing moderation, DMCA/abuse workflow, malware-scan review, AI-disclosure enforcement, audit log.

Terms

Licensing

Default model: royalty-free Standard + Extended tiers (industry norm — Fab, GameDevMarket, Unity Asset Store). Marked as the one open decision; collapsing to a single tier later is trivial, the reverse is a migration.

  • Standard — royalty-free; buyer may use the asset in an unlimited number of their own shipped games/products. Restrictions: no redistribution/resale of the raw files, no asset-flip products, optional per-title revenue or team-seat cap.
  • Extended — higher price; lifts caps. Permits use where the asset is a core resold component (templates, stock libraries, merch/print-on-demand), higher revenue thresholds, more seats, optional sublicensing.

Licenses are modeled as platform-provided LicenseTemplate rows (tier + human-readable terms + machine-readable caps), and each asset attaches one or more LicenseOffering (template + price). In lead-gen v1, licensing is terms display + metadata; enforcement (receipts, LicenseGrant, download tokens) activates with checkout. Alternatives considered: single royalty-free license (simpler, less upsell) and seller-defined licenses (flexible, fragmenting) — see Decisions.

Model

Revenue

Commission per sale. The platform takes a percentage of each transaction; the seller keeps the remainder. Because v1 is lead-gen (no on-platform checkout), commission activates in the checkout phase — v1 builds the catalog, storefronts, and demand. The percentage and any verified-seller rate breaks live in reused Setting/Global so they’re tunable without a migration. (Subscription-library access, à la CraftPix, can be layered later via the same hooks.)

The invariant

Preview protection & download gating

The buyer never sees or receives the real asset before they’re entitled to it. What a listing shows is a derived, watermarked, intentionally-degraded preview generated server-side from the source — never the deliverable file. Downloads of the source are gated on a LicenseGrant (paid) or the asset being free.

  • Source files (AssetFile) — the real, full-quality deliverable. Stored in private R2; no public URL, ever. Served only as a short-lived signed URL after the download route verifies a LicenseGrant.
  • Previews (status.MediaAttachment) — public/CDN, watermarked + degraded, auto-generated by the upload pipeline from the source. Safe to index, hotlink, and show to anyone — they are not usable as the real asset.
Asset typeSource (gated, private)Generated preview (public, watermarked)
3D modelfbx/gltf, textures, rigWatermarked turntable video + a decimated/low-poly WebGL viewer (no downloadable mesh) — never the source geometry
2D art / texturesfull-res PNG/PSD, PBR mapsDown-scaled, watermark-overlaid JPG/WebP; flattened (no layers)
Audio / musicfull-length WAV / stemsShort, lower-bitrate clip with an embedded audio watermark (tone/voice tag)
VFX / shaderproject filesWatermarked demo video / animated GIF
Code / toolssource archiveREADME + screenshots + redacted snippets only
Complete projectfull projectGameplay video + screenshots; no project files
  • Paid asset → download requires a LicenseGrant, which only exists after a successful Stripe payment (checkout phase). /downloads/:grantId validates the grant, then mints a signed R2 URL valid for minutes.
  • Free asset (Asset.is_free) → a LicenseGrant is auto-issued on request (still recorded, so download counts, version updates, and notifications work), then the same signed-URL path applies.
  • Lead-gen v1 caveat: until checkout ships, paid assets expose previews + an external_url (the transaction happens off-platform), while free assets can already deliver on-platform. No source file is ever reachable without a grant.

Invariant: a LicenseGrant (or is_free) is the only key to a source file, and the only thing the public ever sees is a watermarked derivative.

Postgres

Data model

Accessed via sqlx (async, compile-time-checked; migrations via sqlx migrate). Each table maps to a protobuf message; reused entities map to existing protos, new ones to the assetstore package.

TableSource proto
usersreuse User + Auth (kbveproto)
sessions, oauth_accountsshared with Job Board (new)
profiles (base)reuse Profile (kbveproto)
storefrontsnew assetstore.Storefront
asset_categories (hierarchical)new assetstore.AssetCategory
engine/tool/format tagsreuse jobboard.Taxonomy (shared vocabulary)
assetsnew assetstore.Asset (+ reuse MediaAttachment for previews)
asset_versionsnew assetstore.AssetVersion
asset_filesnew assetstore.AssetFile (R2 blob keys, private)
license_templatesnew assetstore.LicenseTemplate
license_offeringsnew assetstore.LicenseOffering
asset_reviewsnew assetstore.AssetReview
wishlists, collectionsnew assetstore.Wishlist / Collection
orders (purchase records)reuse Invoice (kbveproto)
license_grants (entitlements)new assetstore.LicenseGrant
payoutsnew assetstore.Payout (Stripe Connect)
reports, dmca_claims, audit_lognew (admin / trust & safety)
settings, globals (commission %, flags)reuse Setting, Global (kbveproto)

Decoupled

Tech architecture

ConcernChoiceWhy
FrameworkAxum (Tokio)Fast, ergonomic, tower middleware ecosystem
AuthFirst-partyaxum-login + tower-sessions (Postgres store), argon2, optional oauth2 (Discord/GitHub/Steam)Shared KBVE identity; no auth SaaS
DB accesssqlx (async, compile-time-checked)Type-safe SQL; sqlx migrate
DBPostgres (Supabase / Neon)Relational fits catalog/orders/licenses
Wire formatprost/tonic protobuf over jedi.JediEnvelopeReuses KBVE proto/envelope transport
Object storageS3-compatible (Cloudflare R2)Private source files (no public URL); time-limited signed download URLs gated on a grant. Separate public bucket/CDN for watermarked previews
File pipelinemalware scan + watermarked/degraded preview generation per asset type (turntable renders, low-poly viewers, downscaled images, low-bitrate audio)Source never gets a public URL; only derived previews are exposed
SearchPostgres FTS (v1) → Meilisearch laterFaceted catalog search; Rust-native upgrade
PaymentsStripe + Stripe Connect (checkout phase)Collect, commission, pay out sellers
NotificationsIn-app (DB) v1; email laterOrder receipts, asset updates

Frontend — TBD (decided in a later phase)

Section titled “Frontend — TBD (decided in a later phase)”
  • Pure HTTP client of the API. Candidates: Astro + React (KBVE-native), Next.js (frontend-only), or React SPA.
  • Asset detail / storefront pages are SEO-critical → favors SSR/SSG (Astro or Next).
  • API: containerized Axum on Kubernetes (KBVE-native) / Fly.io / Railway (stateful — not serverless).
  • DB: Supabase/Neon Postgres. Object storage + CDN: Cloudflare R2 (signed URLs for paid files; public CDN for previews).

Authorization model: Axum middleware authenticates session/token; route guards check the Capability bitmask on User.role; ownership checks for storefront/asset edits; download routes verify a LicenseGrant before issuing a signed URL.

Axum

REST API surface

All mutations go through the API; the frontend is a pure client. Auth via session cookie or Authorization: Bearer. Versioned under /api/v1.

# Auth (shared with Job Board)
POST /auth/register | /auth/login | /auth/logout
GET /auth/me
GET /auth/oauth/:provider | /auth/oauth/:provider/cb
# Categories & tags
GET /categories asset category tree
GET /tags engine/tool/format tags (shared taxonomy)
POST /admin/categories [admin] create/edit category
POST /admin/tags [admin] add tag
# Storefronts
POST /storefronts create my storefront
GET /storefronts/:slug public storefront + assets
PATCH /storefronts/:slug [owner]
# Assets (catalog)
GET /assets browse/filter (category, engine, format, price, rating)
POST /assets [seller] create listing
GET /assets/:slug detail (previews, specs, license terms, versions, reviews)
PATCH /assets/:slug [seller, owner]
POST /assets/:slug/publish [seller, owner]
# Versions & files
POST /assets/:slug/versions [seller] new version (changelog)
POST /assets/:slug/versions/:v/files [seller] upload file -> R2 (+ scan)
PATCH /assets/:slug/offerings [seller] set Standard/Extended pricing
# Engagement
POST /me/wishlist/:assetId add/remove wishlist
GET/POST/PATCH /me/collections[/:id] curated lists
POST /assets/:slug/reviews [buyer] review (verified-purchase in checkout phase)
# Acquire & deliver
POST /assets/:slug/claim FREE asset -> auto-issue LicenseGrant (no payment)
POST /checkout PAID -> create order (Stripe) for selected offerings (checkout phase)
POST /webhooks/stripe payment confirmation -> Order(Invoice) + LicenseGrant (checkout phase)
GET /me/library owned assets (LicenseGrants)
GET /downloads/:grantId -> time-limited signed R2 URL (verifies grant; free or paid)
GET /me/payouts [seller] payout history (Stripe Connect) (checkout phase)
# Trust & safety
POST /reports report a listing
POST /dmca submit a DMCA/copyright claim
GET /admin/reports | /admin/dmca | /admin/moderation [admin]

Wire contract

Protocol Buffers

Reuses the existing KBVE proto collection (packages/rust/jedi/proto/*, packages/kbve/src/kbveproto.proto) and the Job Board’s shared vocabulary. Rule: reuse first, extend second, add last. We add one new file — assetstore.proto (package assetstore).

NeedReused protoWhereNotes
Auth / credentialsAuthkbveproto.protoFirst-party auth model — use as-is (shared with Job Board).
User identityUserkbveproto.protorole bitmask carries seller/buyer/admin capability; reputation aggregates seller rating.
Base profileProfilekbveproto.protoSeller bio/socials; storefront extends via Storefront.
Bearer tokensApikeykbveproto.protoNon-browser clients.
Orders / receiptsInvoicekbveproto.protoPurchase records reuse Invoice (items, total, paid, status, external).
Commission %, flagsSetting, Globalkbveproto.protoTunable commission + feature flags without migration.
Preview mediaMediaAttachmentstatus.protourl, media_type (bitmask), caption — asset previews.
Engine/tool/format tagsTaxonomyjobboard.protoShared vocabulary — an asset tagged “Unity” uses the same id as a gig tagged “Unity”. (Candidate to promote into a common.proto later; see risks.)
Listing patternDiscordServer, DiscordTagdisoxide.protoTemplate (not copied): bitmask status, inline SQL CHECK comments, URL regex, updated_at. Asset/Storefront follow it.
Transport envelopeJediEnvelope, MessageKindjedi.protoPayloads ride inside JediEnvelope; existing routing bits suffice — no new kinds.
KV / cache / realtimeStoreService, RedisServicestore.proto, redis.protoCatalog cache, download-token cache, update fan-out.

Suggested location: packages/rust/jedi/proto/assetstore.proto, package assetstore, built via prost/tonic. Reuses status.proto (MediaAttachment) and jobboard.proto (Taxonomy) — both must be on the protoc include path.

syntax = "proto3";
package assetstore;
// Reuse — on the protoc include path:
import "status.proto"; // MediaAttachment
import "jobboard.proto"; // Taxonomy (shared engine/tool/format tags)
// Identity & money reused from kbveproto.proto: Auth, User, Profile, Apikey, Invoice, Setting, Global
// Transport: payloads ride inside jedi.JediEnvelope (jedi.proto)
// ─────────────────────────── Enums (bitflags where noted) ───────────────────────────
// Stored in the EXISTING User.role bitmask — no new capability table.
enum Capability {
CAP_NONE = 0;
CAP_BUYER = 1;
CAP_SELLER = 2;
CAP_ADMIN = 4;
}
// Asset lifecycle — bitmask (mirrors disoxide status convention).
enum AssetStatus {
ASSET_DRAFT = 0;
ASSET_PENDING_REVIEW = 1;
ASSET_PUBLISHED = 2;
ASSET_SUSPENDED = 4; // moderation/DMCA
ASSET_ARCHIVED = 8;
}
enum LicenseTier {
LICENSE_UNSPECIFIED = 0;
LICENSE_STANDARD = 1;
LICENSE_EXTENDED = 2;
LICENSE_CUSTOM = 3; // reserved for seller-defined templates
}
// AI-content disclosure (Fab-style requirement).
enum AiDisclosure {
AI_NONE = 0; // no generative AI used
AI_ASSISTED = 1; // AI-assisted (e.g. upscaling, denoise)
AI_GENERATED = 2; // substantially AI-generated
}
// File moderation state.
enum ScanStatus {
SCAN_PENDING = 0;
SCAN_CLEAN = 1;
SCAN_FLAGGED = 2;
}
// ─────────────────────────── Storefront ───────────────────────────
message Storefront {
// ? id BIGSERIAL PRIMARY KEY
uint64 id = 1;
// ? owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE
bytes owner_id = 2;
// ? slug TEXT NOT NULL UNIQUE CHECK (slug ~ '^[a-z0-9-]+$')
string slug = 3;
string name = 4;
string tagline = 5;
string bio = 6;
string logo_url = 7;
string banner_url = 8;
// Bitwise status (active / verified / suspended).
int32 status = 9;
string created_at = 10;
string updated_at = 11;
}
// ─────────────────────────── Category tree (hierarchical) ───────────────────────────
message AssetCategory {
// ? id BIGSERIAL PRIMARY KEY
uint64 id = 1;
// ? parent_id BIGINT REFERENCES asset_categories(id) (NULL = top level)
uint64 parent_id = 2;
// ? slug TEXT NOT NULL UNIQUE CHECK (slug ~ '^[a-z0-9-]+$') e.g. "3d-characters"
string slug = 3;
string label = 4;
string icon = 5;
// Bitwise status (active/featured).
int32 status = 6;
int32 sort_order = 7;
}
// ─────────────────────────── Licensing ───────────────────────────
message LicenseTemplate {
uint64 id = 1;
LicenseTier tier = 2;
string name = 3;
// Human-readable terms (markdown).
string terms_md = 4;
// Machine-readable caps:
bool allow_commercial = 5;
bool allow_redistribution = 6; // resell/redistribute raw files
bool allow_resale_in_product = 7; // asset as a core resold component
bool allow_sublicense = 8;
int32 seat_cap = 9; // 0 = unlimited
int64 revenue_cap = 10; // 0 = unlimited (per-title revenue cap)
string updated_at = 11;
}
// Per-asset price for a license template (e.g. Standard $X, Extended $Y).
message LicenseOffering {
uint64 id = 1;
uint64 asset_id = 2;
uint64 license_template_id = 3;
int64 price = 4; // minor units
string currency = 5;
bool active = 6;
}
// ─────────────────────────── Asset (pattern from disoxide.DiscordServer) ───────────────────────────
message Asset {
// ? id BIGSERIAL PRIMARY KEY
uint64 id = 1;
// ? seller_id UUID NOT NULL REFERENCES auth.users(id)
bytes seller_id = 2;
uint64 storefront_id = 3;
// ? slug TEXT NOT NULL UNIQUE CHECK (slug ~ '^[a-z0-9-]+$')
string slug = 4;
// ? title VARCHAR(140) NOT NULL
string title = 5;
// ? summary VARCHAR(200)
string summary = 6;
// ? description TEXT (markdown)
string description = 7;
// ? category_id BIGINT NOT NULL REFERENCES asset_categories(id)
uint64 category_id = 8;
// Shared taxonomy ids: engines / tools / formats (jobboard.Taxonomy).
repeated uint64 tag_ids = 9;
// PUBLIC, watermarked + degraded previews auto-generated from the source
// (turntable/low-poly viewer, downscaled images, low-bitrate audio) —
// NEVER the deliverable source files (those are private AssetFiles).
repeated status.MediaAttachment previews = 10; // ← reused
AiDisclosure ai_disclosure = 11;
// AssetStatus bitmask.
int32 status = 12;
uint64 current_version_id = 13;
// Lowest active license price, denormalized for listing/sort.
int64 price_from = 14;
string currency = 15;
// Free assets auto-issue a LicenseGrant on download (no payment required).
bool is_free = 24;
// Lead-gen v1: external buy link until on-platform checkout ships.
string external_url = 16;
// Denormalized stats.
uint32 downloads = 17;
uint32 sales = 18;
uint32 rating_count = 19;
float rating_avg = 20;
string published_at = 21;
string created_at = 22;
string updated_at = 23;
}
// ─────────────────────────── Versions & files ───────────────────────────
message AssetVersion {
uint64 id = 1;
uint64 asset_id = 2;
// ? version TEXT NOT NULL e.g. "1.2.0"
string version = 3;
string changelog = 4;
string created_at = 5;
}
message AssetFile {
uint64 id = 1;
uint64 version_id = 2;
string filename = 3;
// fbx / gltf / png / wav / unitypackage / uasset / zip …
string format = 4;
int64 size_bytes = 5;
// R2 object key (private; never returned raw — served via signed URL).
string blob_key = 6;
string checksum = 7;
ScanStatus scan = 8;
string created_at = 9;
}
// ─────────────────────────── Reviews & collections ───────────────────────────
message AssetReview {
uint64 id = 1;
uint64 asset_id = 2;
bytes buyer_id = 3;
// 1..5
int32 rating = 4;
string body = 5;
// True once verified-purchase gating is live (checkout phase).
bool verified_purchase = 6;
string created_at = 7;
}
message Wishlist {
bytes user_id = 1;
repeated uint64 asset_ids = 2;
string updated_at = 3;
}
message Collection {
uint64 id = 1;
bytes user_id = 2;
string name = 3;
bool is_public = 4;
repeated uint64 asset_ids = 5;
string created_at = 6;
}
// ─────────────────────────── Checkout, entitlements & payouts (checkout phase) ───────────────────────────
// Order/receipt reuses kbveproto.Invoice. Entitlement + payout are new.
message LicenseGrant {
uint64 id = 1;
// References the reused Invoice (order) that paid for it.
uint64 invoice_id = 2;
uint64 asset_id = 3;
uint64 asset_version_id = 4;
uint64 license_template_id = 5;
bytes buyer_id = 6;
// Opaque token resolved to a time-limited signed R2 URL at download time.
string download_token = 7;
string granted_at = 8;
}
message Payout {
uint64 id = 1;
bytes seller_id = 2;
int64 amount = 3; // minor units, net of commission
string currency = 4;
// 0 pending, 1 paid, 2 failed
int32 status = 5;
// Stripe Connect transfer/payout reference.
string external_ref = 6;
string period_start = 7;
string period_end = 8;
string created_at = 9;
}
// ─────────────────────────── Trust & safety ───────────────────────────
message DmcaClaim {
uint64 id = 1;
bytes claimant_id = 2;
uint64 asset_id = 3;
string description = 4;
string evidence_url = 5;
// 0 received, 1 upheld (asset suspended), 2 rejected, 3 counter-noticed
int32 status = 6;
string created_at = 7;
}
// ─────────────────────────── Service (pattern from kbveproto.MessageRpc / store.StoreService) ───────────────────────────
message ById { uint64 id = 1; }
message BySlug { string slug = 1; }
message ByUser { bytes user_id = 1; }
message Ack { bool success = 1; string message = 2; }
service AssetStoreRpc {
// Catalog admin
rpc UpsertCategory (AssetCategory) returns (Ack); // admin
rpc UpsertLicenseTemplate (LicenseTemplate) returns (Ack); // admin
// Storefront & listings
rpc CreateStorefront (Storefront) returns (Ack);
rpc CreateAsset (Asset) returns (Ack);
rpc GetAsset (BySlug) returns (Asset);
rpc PublishAsset (ById) returns (Ack);
rpc AddAssetVersion (AssetVersion) returns (Ack);
rpc AddAssetFile (AssetFile) returns (Ack);
rpc SetLicenseOffering (LicenseOffering) returns (Ack);
// Engagement
rpc ToggleWishlist (ById) returns (Ack);
rpc SubmitReview (AssetReview) returns (Ack);
// Checkout & delivery (checkout phase)
rpc IssueLicenseGrant (LicenseGrant) returns (Ack); // after Stripe webhook
rpc ResolveDownload (ById) returns (Ack); // grant id -> signed URL in message
// Trust & safety
rpc SubmitDmca (DmcaClaim) returns (Ack);
}
  • New file: assetstore.proto (1 file, 1 package).
  • Reused as-is: Auth, User, Profile, Apikey, Invoice (orders), Setting, Global, MediaAttachment, jobboard.Taxonomy (engine/tool/format tags), JediEnvelope/MessageKind, StoreService, RedisService.
  • Reused as pattern (not copied): DiscordServerAsset/Storefront, DiscordTagAssetCategory, MessageRpc/StoreServiceAssetStoreRpc.
  • New domain only: storefronts, hierarchical categories, licensing (templates/offerings/grants), versions/files, reviews/collections, payouts, DMCA.
  • No new MessageKind values — existing routing bits suffice.

Safety

Trust & safety

Open supply makes moderation load-bearing:

  • DMCA / copyright — formal claim → review → suspend workflow (DmcaClaim). Stolen assets are the #1 risk for open asset marketplaces.
  • Malware scanning — every uploaded AssetFile is scanned (ScanStatus) before it can be sold/downloaded.
  • AI-content disclosure — required AiDisclosure per asset; surfaced on listings; misdeclaration is a takedown reason.
  • Quality / asset-flip moderation — reports + ratings + optional verified-seller badge.
  • Refunds & chargebacks (checkout phase) — policy for digital goods; fraud monitoring.
  • Private source, watermarked previews — source files live in private R2 with no public URL, served only via short-lived signed URLs gated on a LicenseGrant (or is_free). The public ever only sees derived, watermarked, degraded previews — never the deliverable. See Preview protection & download gating.
  • Audit log for all admin actions.

Open questions

Key risks / open questions

  1. Stolen / pirated assets — open listing invites IP theft. Needs the DMCA workflow + possibly perceptual-hash/duplicate detection on upload. How aggressive at launch?
  2. AI-content policy — disclosure is modeled, but the policy (allowed? labeled-only? banned in some categories?) is a product decision.
  3. Lead-gen for digital goods is unusual — in v1 buyers leave via external_url, so no commission is collected until checkout ships. Confirm v1 is acceptable as a pure discovery catalog, or accelerate checkout.
  4. File hosting & egress costs — large bundles + free previews can get expensive; R2 has no egress fees (good fit), but storage tiers/limits per seller need a policy.
  5. Refund policy for instant-delivery digital goods (checkout phase).
  6. Shared Taxonomy coupling — reusing jobboard.Taxonomy couples this package to jobboard. Cleaner long-term: promote Taxonomy (+ MediaAttachment) into a shared common.proto. Decide now vs later.
  7. Relationship to Job Board — confirm single shared identity/account across both products (assumed), and whether storefront + talent profile are unified surfaces.

Plan

Phased build plan

PhaseScopeOutcome
0 — FoundationsAxum + sqlx + Postgres; migrations; seed category tree + reuse shared tags; first-party auth (shared KBVE identity); wire assetstore.protoAPI boots, auth works, DB ready
1 — StorefrontsCreate/manage storefront; seller profileSellers have a home
2 — Listings (catalog)Create asset, categories/tags, preview media, license offerings, AI disclosure; lead-gen external_urlAssets are listed
3 — DiscoveryBrowse/search/faceted filter; asset detail; license-terms displayCatalog is usable
4 — EngagementWishlists, collections, follows, reviewsDemand-side retention
5 — Checkout & deliveryStripe checkout; Order(Invoice); commission; LicenseGrant; R2 signed downloads; libraryReal store + revenue
6 — PayoutsStripe Connect onboarding; seller PayoutSellers get paid
7 — File pipeline & T&SMalware scan, preview/thumbnail generation, DMCA workflow, verified-purchase reviewsTrust hardened
8 — Frontend clientChoose framework (SEO-critical → SSR); build UIUsable product
9 — Discovery polish & monetizationMeilisearch, recommendations, featured listings, optional subscription libraryScale + revenue surface

Locked & open

Decisions

Locked: strictly game assets · open marketplace (anyone lists; post-hoc moderation) · lead-gen catalog v1, on-platform checkout later · commission per sale (activates at checkout) · decoupled architecture · Rust/Axum API · first-party auth (shared KBVE identity, no auth SaaS) · sqlx + Postgres · R2 with signed downloads · previews are server-generated, watermarked, degraded derivatives (never the source); source files are private + grant-gated; free assets auto-issue a grant · protobuf reuses KBVE protos + jobboard.Taxonomy + one new assetstore package.

Still open:

  • Licensing model — defaulted to Standard + Extended; confirm vs single royalty-free vs seller-defined.
  • AI-content policy — allowed / labeled-only / restricted by category.
  • Anti-piracy aggressiveness at launch (DMCA only vs duplicate detection).
  • Frontend framework (Astro+React / Next.js frontend-only / React SPA) — Phase 8; SEO favors SSR.
  • Commission rate + verified-seller rate breaks.
  • Shared Taxonomy/MediaAttachment — keep coupling to jobboard vs promote to common.proto.
  • Preview-generation stack — which renderer/encoder per type (e.g. headless Blender/Three.js for 3D turntables + decimation, ffmpeg for audio/video, image pipeline for watermarking), and the fidelity-vs-leak-resistance tradeoff (how degraded/watermarked).
  • Seller storage limits/tiers; refund policy; subscription-library tier (later).