TXEX / forge-media-extract
On-chain media ETL — extract + transform media stored on BSV, on our own metal
NODEZEROINSIDE.
0. Abstract
txex is a bun-native CLI + TypeScript library that extracts and transforms media (images, video, audio) stored on the BSV blockchain, then hands us clean files. It reads the four on-chain file protocols — B://, BCAT, 1Sat Ordinals, and ORDFS Streams — reassembles chunked and streamed content, traces a marketplace outpoint back to its inscription origin, and pipes the bytes through Sharp (images) and FFmpeg (video/audio) for resize / crop / format-convert / thumbnail / trim. It is pure read + transform: no broadcast, no keys, no signing — the safest possible chain tool. We adopted it from b-open-io (MIT, © 2025) under the Babbage rule: take the mechanism, run it entirely on family metal, reject the hosted default. It is wrapped by the family skill forge-media-extract. It is the media ETL that is meant to feed our on-chain content surfaces (Kate chain-gallery, FORGEPATH, ORDFS renders) — not a viewer.
BUILT and proven by running (2026-07-05): txex@0.0.4 is installed and executes a real extraction from live chain. DESIGN/VISION (honestly flagged): the sovereign fetch-source swap (Tera-Z SPV instead of GorillaPool) and the content-pipeline wiring (Kate/FORGEPATH/ORDFS) are not yet in the code path. Both are detailed in §7–§8.
1. Why txex exists
On-chain media does not arrive as a file. It arrives as an inscription envelope, or a BitCom OP_RETURN payload, or a metadata transaction pointing at a list of chunk transactions that must be fetched and concatenated in order, or a chain of 1-sat ordinal transfers that must be walked. A gallery, a render pipeline, or a FORGEPATH content surface needs a file — a .webp, an .mp4, a thumbnail at the right dimensions. Between the chain and the file sits an ETL problem: Extract the envelope, Transform the bytes, Load them where the surface can use them.
txex is that ETL. It is deliberately narrow: it does not render, it does not display, it does not sign, it does not broadcast. It reads bytes that already exist on chain and turns them into files on disk. That narrowness is the whole safety argument (§6).
2. What it extracts — the four protocols (BUILT)
Each protocol is a real parser under src/protocols/. txex auto-detects the protocol from the output's locking script (detectProtocol() in extract.ts), in order of specificity:
| Protocol | Prefix / pattern | Shape | Source |
|---|---|---|---|
| 1Sat Ordinals | OP_FALSE OP_IF "ord" … OP_ENDIF |
inscription envelope: content + MIME | protocols/ordinals.ts |
| BCAT | metadata 15DHF…h4Up + chunks 1ChD…qpJL |
metadata tx lists N chunk txids; reassembled | protocols/bcat.ts |
| B:// | 19Hxig…doAut |
single OP_RETURN: data + media type + encoding + filename | protocols/b.ts |
| ORDFS Stream | content-type ordfs/stream |
continuation chunks across ordinal transfers, followed until type changes | protocols/stream.ts |
Two hard cases are handled in code:
- BCAT reassembly (
extractBCATinextract.ts): fetches all chunk transactions in parallel (boundedpMap, default concurrency 5), verifies each chunk parses, concatenates in order, reports progress. - Origin tracing (
ordinal.tsfindOrigin): if an outpoint is a 1-sat output with no directly-recognized protocol (i.e. a marketplace-listed or transferred ordinal), txex walks the ordinal backward through the transaction chain — computing the first-in-first-out satoshi offset at each hop — until it reaches the inscription origin, then extracts from there. This is the "traces marketplace listings back to their inscription" capability, and it is real code, not a claim.
3. What it transforms (BUILT)
Once the bytes are in hand, txex pipes them through local processors. Both are present on this metal (/usr/bin/ffmpeg confirmed; Sharp is a package dependency):
- Images (Sharp):
--width/--height,--fit(cover/contain/fill/inside),--position(incl.entropy/attentionsmart-crop),--format(webp/avif/png/jpg),--quality,--blur,--grayscale,--rotate,--flip/--flop. Plus acolorsubcommand: dominant color + palette + BlurHash. - Video (FFmpeg):
--thumbnail <time>frame grab,--start/--durationtrim,--fps,--no-audio. - Audio (FFmpeg):
--bitrate,--sample-rate,--channels,--normalize.
A transform is content-addressed: the transform options are hashed (hashConfig) so a given (outpoint × transform) result is cached and reused (§5).
4. The one command (usage)
txex <txid_vout> # extract media at an outpoint (vout 0 if omitted)
txex <txid_vout> -o out.webp -w 1200 # resize to 1200px wide, WebP
txex <txid_vout> --thumbnail 00:00:05 # grab a video frame at 5s
txex <collection_outpoint> -l 50 -c 5 # download up to 50 collection items, 5 in parallel
txex info <txid_vout> # metadata only — protocol, MIME, size, sats (no extract)
txex color <txid_vout> # dominant color + palette + BlurHash
txex cache # manage the two-tier cache
It is also importable as a fully-typed library (extract(), extractData(), setStorageProvider()), which is the seam we would use to wire it into a pipeline (§8).
5. Caching + storage — the sovereign seam (BUILT seam, DESIGN swap)
txex has a two-tier cache: raw transactions (tx: namespace) and transformed outputs (tfm: namespace). The important architectural fact for us is the StorageProvider interface (storage.ts):
- Default = filesystem,
~/.txex/cache— on our own disk. No Cloudflare, no cloud, by default. The "reject Cloudflare KV" requirement is satisfied trivially here: the default is local FS; Cloudflare KV is merely one of several backends a user could plug in, and we simply do not. - Swappable via
setStorageProvider()— the interface is a plain get/set/delete/list/clear.MemoryStorageships in-box (ephemeral). A Redis or our-own-storage backend is a small class we implement against the interface.
This is a genuine sovereign seam and it is real code. What is NOT yet done: we have not written a family StorageProvider (Redis / forge-storage). Today it runs on the FS default, which is sovereign-adequate. A custom provider is DESIGN (§8).
Config is read from .txexrc / txex.config.json (cwd then $HOME) via config.ts.
6. Sovereignty posture — pure read, and where it fetches (honest)
The safety core (BUILT, verified in source): txex has no signing path and no broadcast path. Grepping the source confirms the only network egress is fetch() against a transaction indexer to read bytes. There are no keys, no wallet, no createAction, nothing that can spend or publish. Per the manifest and skill: "Pure read/transform — no broadcast, no keys. Safe." Confirmed.
The fetch source (the honest caveat): the raw-transaction provider is providers/junglebus.ts, and its base URL is hardcoded:
const JUNGLEBUS_BASE = "https://junglebus.gorillapool.io";
Every extraction — including the proven run in §9 — currently fetches from GorillaPool's JungleBus, a public BSV indexer. That is an acceptable read fallback (no keys cross it; it is bytes-in only), and it is what the manifest calls "reads via public BSV indexers as fallback." But the manifest's aspiration — "prefer our Tera-Z SPV / txstore where configurable" — is NOT met by the current code: there is no config knob that repoints the fetch at Tera-Z. The StorageProvider swap covers the cache layer, not the fetch layer. Pointing txex at our own metal for the source-of-truth read is a code edit we have not made. This is the single most important build-vs-vision gap in this tool, and it is called out plainly so no future session mistakes the aspiration for the state.
7. Built vs. Vision — the honest ledger
| Claim | State | Evidence |
|---|---|---|
txex@0.0.4 installed, runs on our metal |
BUILT | ~/.bun/bin/txex, --version → 0.0.4, --help full CLI |
| Real extraction from live chain | BUILT — proven by running | §9: info e17d…a584_0 → ORD, image/png, 2.6 KB, 1 sat |
| B:// · BCAT · 1Sat Ordinals · ORDFS Stream parsers | BUILT | src/protocols/{b,bcat,ordinals,stream}.ts |
| BCAT parallel reassembly; ordinal origin tracing | BUILT | extract.ts extractBCAT; ordinal.ts findOrigin |
| Sharp image transforms; FFmpeg video/audio transforms | BUILT | CLI flags; ffmpeg present; transform.ts / video.ts |
| Two-tier cache; FS default (no Cloudflare) | BUILT | cache.ts, storage.ts; default ~/.txex/cache |
| Pure read — no keys, no broadcast | BUILT — verified in source | only fetch() reads; no signing/wallet code |
| StorageProvider swap seam (Redis / our storage) | SEAM BUILT; family provider NOT written | setStorageProvider() exists; no forge-storage impl |
| Prefer Tera-Z SPV / our txstore for the fetch | DESIGN / VISION | JUNGLEBUS_BASE hardcoded to GorillaPool; no config knob |
| Feeds Kate gallery / FORGEPATH :7800 / ORDFS renders | DESIGN / VISION | manifest primary_use_cases; no pipeline glue on disk |
Composes with bsv_mcp_bopen (Uno Satoj) + forge_claw |
WORKFLOW / VISION | manifest composes_with; a pairing pattern, not wired code |
8. Sequencing (what "done" would take)
- Phase 0 — Adopt + prove: DONE (adopted 2026-06-25 per manifest; proven by running 2026-07-05). Runs on our metal, FS-cached, GorillaPool read.
- Phase 1 — Sovereign fetch source: add a provider abstraction so
JUNGLEBUS_BASEbecomes configurable (.txexrc) and can point at Tera-Z SPV / our txstore / family overlay first, GorillaPool as fallback. Closes the §6 gap. NEXT if a fully-sovereign read path is required. - Phase 2 — Family StorageProvider: implement a
StorageProvideragainst forge-storage / Redis so the cache is our infrastructure, not just local FS. Optional (FS default is already sovereign-adequate). - Phase 3 — Pipeline wiring: glue
forge-media-extractinto the Kate chain-gallery build, FORGEPATH (:7800) content ingestion, and ORDFS render/liferaft-pixel paths. This is the manifest's statedapps.galleryuse case; today it is a CLI a human/agent invokes, not an automated pipeline stage. - Phase 4 — Compose with discovery: pair
bsv-ordinals(Uno Satoj) to find inscriptions/collections, then txex to pull + transform — the canonical two-step for a collection ingest.
9. Proven by running (2026-07-05)
- Installed + versioned:
txex --version→0.0.4.txex --helpprints the full CLI (extract flags +info/color/cachesubcommands). Binary is~/.bun/bin/txex(bun-install symlink intonode_modules/txex/src/cli.ts— bun's standard bin mechanism, not a family genome symlink). - Live extraction from chain:
txex info e17d7856c375640427943395d2341b6ed75f73afc8b22bb3681987278978a584_0returned real on-chain metadata:
Protocol: ORD
Media Type: image/png
Size: 2.6 KB
Satoshis: 1
Fetched over GorillaPool JungleBus (/v1/transaction/get/…/bin, HTTP 200, 0.61s). This is a real 1Sat Ordinal PNG inscription, read and identified by txex on our metal — the extract path works end to end.
- Local processors present: /usr/bin/ffmpeg confirmed; Sharp is a resolved dependency in node_modules/txex.
Total: install + version + full-CLI + one live on-chain extraction + local processors present = the read/identify path is proven; the transform path is dependency-present and CLI-exposed.
10. Facts
- Package:
txex@0.0.4, MIT, © 2025 b-open-io (github.com/b-open-io/txex). Adopted 2026-06-25 (manifesttools.txex). No family TX / birth cert — external tool, vendored, run on our metal. - Deps:
@bsv/sdk^1.9.24,sharp^0.34.5,blurhash,commander,mime-types,chalk,ora. FFmpeg = system binary. - Source of truth (fetch): GorillaPool JungleBus, hardcoded (§6 caveat).
- Cache:
~/.txex/cache(FS default), swappable viaStorageProvider. - Skill:
~/.claude/skills/forge-media-extract/SKILL.md. Composes:bsv_mcp_bopen,forge_claw. Use cases (stated):tooling.media_extract,apps.gallery. - Adoption doctrine: Babbage rule — adopt the standard, run on our metal, reject the hosted default — per
bopenai-bsv-mcp-sovereign-adoption.md.
This paper is family-internal — NOT for public fire. It maps a vendored MIT tool's real state, separates BUILT from VISION honestly, and is not chain-stamped (scar #5: external code + aspiration must not be dressed as canon). If a public account of the media-ETL surface is ever wanted, author a public-safe paper separately.
NODEZEROINSIDE. ∞.