A tiny Bitcoin P2P payment monitor. One Go file, no deps.
TxEcho joins the Bitcoin gossip network, watches tx and block relay, and
publishes fresh incoming native-SegWit outputs as flat JSON. Local dir, its own HTTP
handler, or a CDN. Wallets poll to spot payments without running a node, and
without leaking what they watch: outputs bucket by sha256(scriptPubKey), so
the server sees a bucket number shared by thousands of scripts, not an address.
Not a node. See Trust.
Grab a release binary and verify it:
sha256sum -c SHA256SUMSOr install from source:
go install github.com/thickzero/TxEcho@latestThen:
./txecho doctor # sanity check
./txecho once -duration 2m # collect briefly, publish, exit
./txecho run # keep tapping peers, publish every minuteA client only needs two paths:
manifest.json
b/<bucket>.json
Manifest:
{
"version": 1,
"row_version": 1,
"bucket_bits": 8,
"tip_height": 956806,
"tip_hash": "...",
"buckets": { "2a": {"path": "b/2a.json", "sha256": "...", "count": 1} }
}Rows are compact arrays:
["scriptHash", "txid", 0, 12345, "m"] // mempool
["scriptHash", "txid", 0, 12345, "c", 956806, "blockHash"] // confirmedAbsent bucket = empty. Buckets that had rows and later empty out get an
explicit [] so old rows do not linger in cache.
spk= hex of the output's scriptPubKey (lowercase, no0x).- Hash the ASCII bytes of that hex with SHA-256.
- Top
bucket_bitsof the digest is the bucket. Format as lowercase hex,(bucket_bits+3)/4chars. - Fetch
manifest.json, then the bucket if it is listed. - Verify the bucket body SHA-256 against the manifest. On mismatch, cache-bust or wait one TTL.
- Exact-match the 128-bit script hash before trusting a row.
Confirmations = tip_height − row height. TestClientModelGoldenVector
pins a concrete example.
Same artifacts, straight from memory:
TXECHO_PUBLISHERS=http TXECHO_HTTP_ADDR=:8080 \
TXECHO_API_KEY=$(openssl rand -hex 16) ./txecho runEndpoints (GET, HEAD):
/ same as /manifest.json
/manifest.json
/b/<bucket>.json
/healthz always open
Standard Cache-Control and a strong ETag, so If-None-Match returns 304.
With TXECHO_API_KEY set, every endpoint except /healthz needs either
X-API-Key: <key> or Authorization: Bearer <key>, compared in constant
time. Bind privately or front it with a proxy or Cloudflare Tunnel.
TXECHO_* env, .env, or flags. Flags win.
TXECHO_PEERS host[:port] list; DNS seeds if unset
TXECHO_BUCKET_BITS 4..16, default 8
TXECHO_RETENTION rolling window, default 20m
TXECHO_POLL_INTERVAL publish cadence, default 60s
TXECHO_CHECKPOINT header floor, height:hash:bits
TXECHO_STATE state file, default ./txecho-state.json
TXECHO_PUBLISHERS local,bunny,r2,http (default local)
TXECHO_OUT local dir, default ./public
TXECHO_HTTP_ADDR http listen, e.g. :8080
TXECHO_API_KEY optional key for the http publisher
Full reference lives in the header of txecho.go. Remote publishers take
their credentials from BUNNY_* and R2_*. Unchanged objects are not
re-uploaded.
TxEcho discovers incoming outputs. It does not track spends, balances, histories, or wallet activity.
Pending rows ("m") are unvalidated mempool hints from peers, only for txs
TxEcho itself requested after an inv. Cold-start mempool requests are
often ignored, so relay and recent-block backfill do most of the work.
Confirmed rows ("c") mean the block was parsed by TxEcho, its header
extends the verified chain within the last 100 blocks, its target clears the
checkpoint difficulty floor (widened 4x so a normal downward retarget cannot
freeze the tip), its merkle root matches the header, and it has no duplicate
txids (CVE-2012-2459 rejected). Stronger than trusting a stray payload,
weaker than full consensus: no retarget rules, no chainwork weighting, no
script validation. Settle real money against a full node.
Short reorgs: a strictly longer branch inside the 100-block window replaces the tip and orphaned confirmations are dropped. Equal-length forks keep first-seen; deeper forks are refused.
Baked default so you do not sync headers from genesis:
956805:00000000000000000001dd42b106f423cecda728647c973dbd5dbb18e6d5b21b:386013762
TXECHO_STATE caches recent headers and published bucket IDs. Wrong
network, wrong checkpoint, or a header gap rejects the file and rebuilds
from the checkpoint.
Static binary, pinned toolchain, byte-for-byte:
docker run --rm -v "$PWD":/src -w /src -e HOME=/tmp \
--user "$(id -u):$(id -g)" golang:1.26.4-alpine \
sh -c 'CGO_ENABLED=0 go build -trimpath -buildvcs=false -ldflags="-s -w" -o txecho .'
sha256sum txechoRebuild it. No trust required.
MIT.
On the wire it says /txecho:0.1.0/
