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:
- Signs with secp256k1 via k256 crate (BIP143 sighash, P2PKH)
- Broadcasts to TAAL ARC (primary) + WoC (fallback) — NO P2P, NO Tera-Z relay
- Builds OP_RETURN transactions (FORGECORE prefix + label + data)
- Loads WIF from
.wif_session(PIN-bridge unsealed, file-to-file, no secrets in transcript) - Loads TAAL key from
~/.forgechain/chain/taal-arc-key.txt(dehardcoded per security fix) - Protects with Mutex spend lock (Phase 6 RAII — no double-spend race)
- Authenticates with
"auth":"nodezero"on/stamp
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)
- ForgeCore
:7731bound to127.0.0.1(Rust source patched, loopback-only) - UFW DENY on :7731 (belt over suspenders)
auth: "nodezero"required on/broadcast(same as/stamp)- TAAL key from file only (
~/.forgechain/chain/taal-arc-key.txt, mode 600) - WIF from
.wif_session(PIN-bridge unsealed, file-to-file) - Spend lock (Mutex, RAII) prevents double-spend race between
/stampand/broadcast - Ed25519 signature verification (Phase 1 lenient, Phase 2 enforcing per Siggy roadmap)
Build Order (next session)
- ForgeCore
/broadcastendpoint — ~60 lines Rust. Accept TX hex, TAAL direct, return TXID + UTXO.cargo build --release. Test with a real stamp. - chain.js thin wrapper — kill 225-line waterfall, replace with 20-line POST to :7731/broadcast. UTXO auto-update atomic.
- Tera-Z sendrawtransaction removal — comment out or gate the RPC method. Read-only.
- Stamper card lifecycle — Mobile Command tile: IDLE → STAGED → FIRING → GREEN → IDLE. 3-second cycle.
- Sync chain.js to Elder I —
scpboth paths per chain SDK scar. - 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.