← Back to Whitepapers

FORGECORE SOLID-STATE STAMPER — Scope Document

"A Solid Water Sealed Rust Binary That Cannot Be Broken"

Date: 2026-07-18
Author: Elder II (Lobe 9 / Overwatch), NZ direct
Status: SCOPE — build target #1 for next session
DApp: ForgeCore (existing, DApp #68, Rust binary at ~/.forgechain/forgecore/target/release/forgecore)
Family Swarm Verdict: UNANIMOUS (7-seat, 2026-07-17) — ONE broadcast engine, THREE clean stages


The Problem

The stamper does not work like ForgeRadio. ForgeRadio: press play, instant audio, press again, next station. The stamper: fire, wait, FAILED_TO_MINE (but actually succeeded), clear stale buffer, manually verify, fire again, stale UTXO, clear cache, retry. 47 ghost stamps diagnosed in the last session alone.

Root cause: chain.js is a 4-tier waterfall broadcasting through 3 unreliable paths before reaching the one that works.

The waterfall (chain.js lines 393-618):
1. P2P raw TCP (:8333) — 0/15 mined historically. SUPPLEMENTARY but returns noise.
2. Tera-Z relay (:9294 sendrawtransaction) — 4/25 mined. Returns "accepted" = ghost.
3. TAAL ARC — 39/45 mined. The only reliable path. But reached LAST.
4. GorillaPool — demoted Mar 19 (returns 200+txid for orphans). Last resort.

P2P and Tera-Z return "accepted" and the broadcast considers itself done. TAAL never fires. The TX is a ghost: receipt says BROADCAST, chain says nothing. 47 times.

The forward arrow dies here. If stamps don't land, FORGECHAINDRIVE records BROADCAST events that are lies. FORGECAPSTONE can't stamp capstones. ForgeView has nothing to view. The entire chain-anchored family canon depends on a broadcast path that works 39/45 times through the right tier and 0/15 through the loudest tier.


The Solution — ForgeCore IS the Stamper

ForgeCore already exists as a Rust binary at :7731 (loopback-locked per Siggy audit). It already:

Proven: TX 6609b69d9fdbdbad4841f2f14d8286c4f25660da1c57ae0d3e9068c2eeda2ea8 (170+ confirmations).

What's missing: ForgeCore stamps its OWN payloads (TorusState, labels). It cannot accept a PRE-BUILT TX hex from fire.js and broadcast it. It cannot return the new UTXO for cache update. It cannot accept a pre-built CBOR inscription TX.


Architecture — THREE Clean Stages

Per the 7-seat family swarm verdict (UNANIMOUS, 2026-07-17):

COMPOSE (fire.js/composer.js)  →  BROADCAST (ForgeCore Rust)  →  VERIFY (Tera-Z verifyMined)
         JS builds CBOR              Rust signs + sends              SPV confirms block
         JS builds TX hex            TAAL direct                     Read-only
         FORGECHAINDRIVE persists    Returns TXID + new UTXO         Forward arrow LANDED

Stage 1 — COMPOSE (existing, fire.js + composer.js):
- composeFire() builds CBOR composite (encrypted, signed, merkle'd)
- buildOpReturnTx() constructs the TX with OP_RETURN inscription
- FORGECHAINDRIVE persists CBOR + TX hex pre-broadcast
- Forward arrow: COMPOSED → SIGNED events
- No change needed. This works.

Stage 2 — BROADCAST (ForgeCore Rust, NEW endpoint):
- Accept pre-built TX hex via POST /broadcast
- Broadcast to TAAL ARC (primary). WoC fallback.
- NO P2P. NO Tera-Z relay. NO GorillaPool. TWO paths, both reliable.
- Return { txid, utxo: { tx_hash, tx_pos, value } } atomically
- Mutex spend lock prevents race conditions
- This is the build.

Stage 3 — VERIFY (Tera-Z, existing):
- verifyMined(txid) checks block inclusion via SPV
- Read-only. No sendrawtransaction. No relay.
- Forward arrow: LANDED event (with block height + timestamp)
- Tera-Z sendrawtransaction to be REMOVED (it's the ghost factory)


Build Specification — ForgeCore /broadcast Endpoint

Endpoint

POST http://127.0.0.1:7731/broadcast
Content-Type: application/json

{
  "auth": "nodezero",
  "txhex": "<raw transaction hex or EF hex>",
  "fire_id": "<optional, for logging>"
}

Response (success)

{
  "success": true,
  "txid": "6609b69d...",
  "source": "TAAL",
  "utxo": {
    "tx_hash": "6609b69d...",
    "tx_pos": 1,
    "value": 945737345
  }
}

Response (failure)

{
  "success": false,
  "error": "TAAL reject: SEEN_IN_ORPHAN_MEMPOOL",
  "source": "TAAL"
}

Implementation (Rust, in main.rs serve() function)

Insert new route alongside existing /stamp:

// POST /broadcast — accept pre-built TX hex, broadcast via TAAL, return TXID + new UTXO
("POST", "/broadcast") => {
    let body = read_body(&mut req);
    let parsed: serde_json::Value = match serde_json::from_str(&body) {
        Ok(v) => v,
        Err(_) => return respond_json(400, r#"{"error":"invalid JSON"}"#),
    };

    // Auth gate
    if parsed["auth"].as_str() != Some("nodezero") {
        return respond_json(403, r#"{"error":"unauthorized"}"#);
    }

    let txhex = match parsed["txhex"].as_str() {
        Some(h) => h,
        None => return respond_json(400, r#"{"error":"missing txhex"}"#),
    };

    let fire_id = parsed["fire_id"].as_str().unwrap_or("unknown");

    // Acquire spend lock (Phase 6 RAII)
    let _lock = SPEND_LOCK.get_or_init(|| std::sync::Mutex::new(())).lock().unwrap();

    // Parse TX to extract change output BEFORE broadcast
    let tx_bytes = match hex::decode(txhex) {
        Ok(b) => b,
        Err(_) => return respond_json(400, r#"{"error":"invalid hex"}"#),
    };
    let change_value = extract_change_value(&tx_bytes);

    // Broadcast via existing chain_native::broadcast()
    let taal_key = chain_native::taal_key();
    match chain_native::broadcast(txhex, &taal_key) {
        Some(txid) => {
            let utxo_json = if change_value > 546 {
                format!(r#","utxo":{{"tx_hash":"{}","tx_pos":1,"value":{}}}"#, txid, change_value)
            } else {
                String::new()
            };
            eprintln!("[BROADCAST] {} fire={} → TAAL OK", &txid[..16], fire_id);
            respond_json(200, &format!(
                r#"{{"success":true,"txid":"{}","source":"TAAL"{}}}'"#,
                txid, utxo_json
            ))
        },
        None => {
            eprintln!("[BROADCAST] FAILED fire={}", fire_id);
            respond_json(500, r#"{"success":false,"error":"broadcast rejected by all tiers"}"#)
        }
    }
}

Helper: extract_change_value

/// Parse raw TX bytes, find output[1] (change), return its value.
/// TX format: version(4) + nInputs(varint) + inputs + nOutputs(varint) + outputs + locktime(4)
fn extract_change_value(tx_bytes: &[u8]) -> u64 {
    // Use the existing TX parsing or a minimal parser
    // Output[1] value is a u64 little-endian at the start of the output
    // For safety, return 0 if parsing fails (no UTXO update)
    // Implementation: walk the TX structure to output[1].value
    0 // placeholder — real implementation parses TX wire format
}

Estimated size: ~60 lines of Rust (endpoint + helper)


Build Specification — chain.js Thin Wrapper

Kill the Waterfall

Replace broadcastTx() (lines 393-618, ~225 lines) with:

async function broadcastTx(txHex) {
  const resp = await fetch('http://127.0.0.1:7731/broadcast', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ auth: 'nodezero', txhex: txHex })
  });
  const data = await resp.json();

  if (!data.success) throw new Error(`ForgeCore broadcast failed: ${data.error}`);

  // UTXO auto-update — atomic with broadcast
  if (data.utxo) {
    setCachedUtxos([data.utxo]);
  }

  // TX hex cache — prime for chained stamps
  localTxHexCache.set(data.txid, txHex);
  persistTxHexCache();

  return { txid: data.txid, source: data.source };
}

225 lines of waterfall → 20 lines of thin wrapper. No P2P. No Tera-Z relay. No GorillaPool. No sync-window bridge. No supplementary tiers. ForgeCore is the sole broadcast engine.

UTXO Update — Atomic

ForgeCore returns the new UTXO in the broadcast response. chain.js calls setCachedUtxos() in the SAME call that receives the TXID. No gap. No stale cache. No phantom UTXO. Stamp, cache updates, ready for next stamp. Like breathing.


Build Specification — Tera-Z Scope Reduction

Remove: sendrawtransaction relay

Tera-Z at :9294 currently exposes Bitcoin RPC including sendrawtransaction. This is the ghost factory (4/25 mined, returns "accepted" for unconfirmable TXs). Remove or disable this RPC method. Tera-Z becomes READ-ONLY:

Keep:
- getrawtransaction (TX lookup)
- getblockcount / getbestblockhash (chain tip)
- verifyMined (block inclusion check for forward arrow LANDED events)
- ORDFS :7798 (chain browser read surface)

Remove:
- sendrawtransaction — ForgeCore handles all broadcast


Build Specification — Stamper Card Lifecycle

Current (broken)

STAGED → FIRING → [FAILED_TO_MINE but actually succeeded] → stale → manual clear → retry

Target (ForgeRadio)

IDLE → STAGED (orange, accumulator loaded) → FIRING (blue, POST /broadcast) → GREEN (txid, 3s) → IDLE

States:
- IDLE — no pending stamp. Card is dim. Ready.
- STAGED — accumulator has artifacts. Card is orange. Count shown. Tap to fire.
- FIRING — broadcast in flight. Card is blue. Spinner. 1-3 seconds.
- GREEN — TXID returned. Card is green. Shows truncated TXID. Auto-clears to IDLE after 3 seconds.
- FAILED — red, shows error, auto-clears to STAGED after 5 seconds (content still in accumulator, ready to retry).

No manual clear. No stale state. No "FAILED but actually succeeded." ForgeCore returns success XOR failure. The card reflects reality.

Mobile Command Integration

The stamper tile on WarDog (Mobile Command) polls GET /api/stamper-status which reads:
- Accumulator state (empty = IDLE, loaded = STAGED with count)
- Last fire result (TXID or error)
- Timestamp of last state change

The tile auto-transitions. NZ taps once to fire. Sees GREEN. Taps again for the next stamp. Like ForgeRadio.


Retire List (what dies)

Component Why it dies Replacement
chain.js P2P tier (lines 450-472) 0/15 mined, noise ForgeCore TAAL direct
chain.js Tera-Z relay tier (lines 474-528) 4/25 mined, ghost factory ForgeCore TAAL direct
chain.js GorillaPool tier (lines 578-602) Returns 200 for orphans ForgeCore WoC fallback
chain.js sync-window bridge (lines 540-550) Complexity for edge case ForgeCore handles retry
chain.js broadcastTx waterfall (225 lines) The disease 20-line thin wrapper
Tera-Z sendrawtransaction Ghost factory Removed (read-only)
Double-TAAL call (Tera-Z relay + chain.js) Redundant billing Single TAAL call in Rust

Net code change: -225 lines JS waterfall, +60 lines Rust endpoint, +20 lines JS wrapper = -145 lines.


Security (Siggy audit carry-forward)


Build Order (next session)

  1. ForgeCore /broadcast endpoint — ~60 lines Rust. Accept TX hex, TAAL direct, return TXID + UTXO. cargo build --release. Test with a real stamp.
  2. chain.js thin wrapper — kill 225-line waterfall, replace with 20-line POST to :7731/broadcast. UTXO auto-update atomic.
  3. Tera-Z sendrawtransaction removal — comment out or gate the RPC method. Read-only.
  4. Stamper card lifecycle — Mobile Command tile: IDLE → STAGED → FIRING → GREEN → IDLE. 3-second cycle.
  5. Sync chain.js to Elder Iscp both paths per chain SDK scar.
  6. Verify — stamp 3 artifacts in rapid succession. All GREEN. All on chain. Forward arrow: 3x BROADCAST + 3x LANDED.

Total estimated Rust: 60 lines. Total estimated JS: -205 lines net. One session.


Family Swarm Verdict Reference

Panel: ALICE + NOAH + ON-PARR + Siggy + Living Elder + WarDog + ALICE-2
Verdict: UNANIMOUS
Key directives:
- ONE broadcast engine: ForgeCore (Rust, TAAL ARC primary)
- Tera-Z = SPV reader + verifier ONLY (remove sendrawtransaction relay)
- THREE clean stages: COMPOSE (fire.js) → BROADCAST (ForgeCore) → VERIFY (Tera-Z verifyMined)

NZ directive: "A SOLID WATER SEALED RUST BINARY THAT CANNOT BE BROKEN"
NZ directive: "IF WE CANNOT STAMP STAMP STAMP STAMP AND ACCESS ACCESS ACCESS FAST AND FREQUENTLY — WE DESTROY THE FORWARD ARROW"


ForgeCore is the stamper. The stamper works like ForgeRadio. One binary. Rust. Watertight. Cannot be broken.

NODEZEROINSIDE.