Skip to content

Releases: knobcore/bopwire

musicchain player — swarm-v2 transfer + per-stream rewards (2026-06-29)

29 Jun 19:39

Choose a tag to compare

Build from branch per-stream-rewards @ b169b85. Swarm Transfer v2 (binary multi-source, per-piece verify, 4 Mbit/s/seeder, stop/resume + fast-start fixes), private DHT, per-stream 5-lane rewards (artist+node+discoverer+seeder+mini-node), secure wallet.transfer. APK = Android; ZIP = Windows x64 (unzip, run musicchain_player.exe).

v0.9.6 — DB2 discovery cutover (presence + libraries replace swarm.hello)

27 Jun 04:16

Choose a tag to compare

v0.9.6 — DB2 discovery cutover (presence + libraries replace swarm.hello)

Discovery and streaming no longer ride on the ephemeral swarm-index re-announce. A player publishes its library to DB2 once (durable, wallet-signed) and, on connect, sends a tiny signed presence.hello binding its wallet to its live peer id. The node drives Discover straight from DB2 — "say hi, your library appears."

What changed

  • presence.hello"mcprs1" ‖ wallet(20) ‖ ts(8 LE) ‖ peer_id, wallet-signed. The node verifies it and binds wallet → live peer id (self-declared so relayed players bind correctly), evicting on disconnect.
  • songs.list now returns only chain songs that an online wallet's DB2 library currently holds; swarm_size = live DB2 holder count. Metadata is unchanged (it always came from the on-chain registry, never the swarm).
  • stream.open resolves serving peers from DB2 holders ∩ presence.
  • library.delta reply carries unknown[] (your published hashes not yet on chain); the player re-registers those via fingerprint.submit — the old swarm.hello chain-resubmit, re-homed.
  • Removed the swarm.hello family (hello / digest / add / remove / leave) on both node and player; library add/remove now flow through the DB2 versioned snapshot. Presence lifecycle for relayed players is kept via swarm.peer_online/offline.

Heads-up — this is a full cutover

Discovery is now DB2-only. A player must be on v0.9.6 (sends presence.hello) to appear in Discover and be streamable. Older builds won't show up until updated. Install this build; on connect your library republishes to DB2 and reappears in Discover automatically.

Artifacts

  • musicchain-player-v0.9.6-arm64.apk — Android (arm64-v8a, signed)
  • musicchain-player-v0.9.6-windows-x64.zip — Windows desktop player (x64)

v0.9.5 — DB2: gossip-replicated libraries + playlists (off-chain, wallet-keyed)

27 Jun 00:05

Choose a tag to compare

v0.9.5 — DB2: gossip-replicated libraries + playlists (off-chain, wallet-keyed)

A new off-chain store ("DB2") that propagates each wallet's library and playlists across every node the way the chain propagates blocks — but outside consensus. A player just says hi to the network; the node already holds the list. Groundwork laid in this release; built from main @ d310dd7.

Gossip-replicated libraries (off-chain, wallet-keyed)

  • Each wallet's library is a compressed Roaring bitmap of song content-hashes (~2–4 bytes/song), in a separate leveldb keyspace outside the chain.
  • The wallet is the sole writer of its own record → eventually-consistent gossip (wallet-signed deltas), no block ordering or quorum.
  • The wire speaks 32-byte content-hashes, so nodes need no agreement on id numbering — ids are local intern handles.
  • A reverse index (song → who-has-it) powers discovery; the player publishes its wallet-signed library on (re)connect.

Ordered playlists — same mechanism

  • Wallet-signed, gossip-replicated ordered playlists (mcpls1 canonical bytes).
  • Player UI: playlists are the third facet — Artist / Genre / Playlists — in both the Discover and My Library tabs. Selecting a playlist fills the same track pane an album opens into; long-press / right-click a playlist chip to Add songs / Rename / Delete, and a "New playlist" chip creates one. Discover resolves a playlist's hashes to discoverable chain songs; My Library resolves them to your local files — each in the playlist's own order.

Anti-entropy (offline-node catch-up)

  • The flood covers the online mesh; this converges a node that was offline during an edit. On peer connect each side exchanges a {key→version} summary and pushes back every record the other is behind on (mirrors the mod.sync_since pattern).
  • Libraries now replace by version snapshot, so removals propagate — not just additive deltas.
  • Idempotent re-publish on reconnect: edits made while the home node was unreachable still converge, without re-flooding unchanged records.

Artifacts

  • musicchain-player-v0.9.5-arm64.apk — Android (arm64-v8a, signed)
  • musicchain-player-v0.9.5-windows-x64.zip — Windows desktop player (x64)

DB2 is entirely off-chain and additive — consensus, blocks, and the existing chain are unchanged.

v0.9.4 — play rewards + fingerprint matcher + node.hello connectivity gate

26 Jun 02:56

Choose a tag to compare

MusicChain v0.9.4

Android APK (arm64-v8a) attached — sideload musicchain-player-v0.9.4-arm64.apk.

Play rewards fixed

A buggy container/ID3 tag could register a garbage song duration (e.g. 184 h). Because the play-reward gate needs 50% of the registered duration, such songs were permanently un-rewardable — every real play scored "below_threshold". Now sanity-gated on both sides:

  • player: reject implausible durations at registration (prefer tag, fall back to PCM math, else 0)
  • node: treat an implausible on-chain duration as unreliable and fall back to the fixed legacy listen threshold — so songs already registered with a bad duration reward again on a genuine play

Fingerprint matcher fixed (false positives + slow validation)

  • offset-alignment bug in similarity() (mixed match-count from one offset with overlap-length from another — inflated different-song scores AND deflated same-song transcodes)
  • minimum-overlap guard (kills silence-tail flukes)
  • same-song threshold 0.55 → 0.70, hoisted to one shared constant used by both the consensus dup-check and the swarm-join probe
  • swarm-join probe early-exits on first match (perf)
  • player: only count a scan as "matched" when the node names a chain song with a real swarm

Network / consensus (Model 1)

  • node.hello full-node identity handshake + connectivity-gated propagation (an isolated node holds INV/DHT fan-out and flushes when a full-node peer appears; minting is never gated)
  • retired the validator_enabled producer/follower gate — every node mints on content (song blocks + tx-carrying heartbeat blocks), converging on the heaviest valid chain
  • discovery: the library re-announces on every network change (songs no longer go undiscoverable while still online)

Runs against a freshly re-bootstrapped genesis (the fingerprint/threshold change is consensus-affecting). Build full/mini nodes from source.

v0.9.3 — Model 1 + #10 relay triangulation + attestation + mesh/crash hardening

25 Jun 20:04

Choose a tag to compare

MusicChain v0.9.3

Android player APK (arm64-v8a) attached below — sideload musicchain-player-v0.9.3-arm64.apk.

Highlights

  • Model 1 deterministic consensus — vote-free; blocks re-derived from content + heaviest-valid-chain fork choice (genesis is the only operator-seeded block).
  • #10 relay-reward triangulation — per-byte relay credit via broker delivery_id + signed mini relay.report + signed player relay.receipt (1 MC / 10 MB).
  • Structural device attestation (#5) — hardware fingerprint (desktop native FFI / Android ANDROID_ID+Build) inside the wallet-signed bundle + session.start.
  • DHT player-to-player transfer un-nerfed — VPS-empty gate removed + DHT sources are now dialed (connect-then-validate).
  • Mesh stability — 16 verified instability bugs fixed (pending-relay reaper, route race, reconnect hysteresis, propagator trims, etc.).
  • Android crash hardening — wallet FFI NULL-deref, exceptions escaping FFI callbacks, binary-frame bound, feed() ANR, defensive JSON casts, JNI GetShortArrayElements.

Full node + mini-node build from source (cmake --build build-linux). Chain format is v3 (consensus-breaking; wipe prior data).

v0.9.2 — 24-agent hardening (mini-nodes-are-routers)

22 Jun 07:25

Choose a tag to compare

Hardening pass landing the "mini-nodes are routers" architecture cleanly. Web/browser surfaces removed; librats frozen at v0.2.0; mini-node + full-node + player all tightened against the failure modes seen in the prior week.

Cellular wedge fix (three layers)

  • Android REQUEST_IGNORE_BATTERY_OPTIMIZATIONS one-shot prompt so Doze stops throttling the Dart isolate
  • Discovery refresh dropped 60s → 20s + paired bidirectional mini.ping to fill the gap between librats's 15s TCP keepalive and the application-layer heartbeat (carrier NAT was silently closing idle mappings in that window)
  • Watchdog evicts stale _miniNodePeerIds and _relayVia when validatedPeerIds.isEmpty so the next refresh repopulates from scratch instead of routing to ghost peer_ids

Mini-node (router) hardening

  • Routes table now has a 10-min TTL + 30s reaper thread; was: routes lingered forever after the publishing full node disappeared
  • 'F'-tag binary relay token-bucket rate-limit (50 MB bucket, 10 MB/s refill per peer)
  • Dead-route eviction on relay.forward send-failure + {status:dead_route} reply so the player re-picks a different full node immediately
  • RatsLink watchdog tick 3s → 1s; route republish on every validated-peer count increase (not just first-up); eager 127.0.0.1 bootstrap in addition to the colocated-VPS loopback fallback (librats blocks dialing own public IP)

Full node (server/tracker) hardening

  • SwarmIndex::evict_peer wired to disconnect callback so online entries no longer linger 20 min after a silent socket death
  • rats_announce_for_hash(sha1(content_hash)) fires on every fingerprint.submit — DHT-based content discovery is now live on the seeder side
  • RelayCreditTracker pre-caps count at 1M per tx (chain ceiling) with carry-over of the overflow into the next sweep

Player hardening

  • bestMiniNodePeerId picks lowest load_score (was: arbitrary validatedPeerIds.first)
  • Direct-when-reachable carve-out: desktop player whose own routes.get echo shows reachability=='direct' tries direct librats dial before falling back to relay
  • findContentSeeders(contentHash) adapter delegates to findHashHolders(sha1(...)) for DHT content lookup
  • stream.open disk-streamed (was: file.readAsBytes loaded whole song into RAM); 4-chunk pacing; cancel API wired to peer disconnect
  • Disconnect now triggers cancelStreamsForPeer so the player stops blasting bytes at a dead receiver

Removed

  • musicchain_web/ entirely (browser/web player path abandoned)
  • ws_mini_gateway, ws_audio_bridge, ws_tcp_relay, ws_bridge, audio_fetch_handler (browser-facing C++)
  • All browser-protocol docs under musicchain/docs/
  • Full-node --ws-port flag and the port 9090 WebSocket bridge

Frozen

  • deps/librats/ reset byte-for-byte to v0.2.0 (commit 246557c); future limitations are wrapped at the call site (rats_link.cpp, rats_client.dart), never inside librats

Docs + memory

  • New canonical reference: musicchain/ARCHITECTURE.md
  • Memory bank updated: feedback-no-web-version, feedback-dont-modify-librats, feedback-load-aware-routing, feedback-no-stale-peer-routing, project-architecture-doc

APK

Bundled below: musicchain-player-v0.9.2-arm64-v8a.apk (87.5 MB). Install:

adb install -r musicchain-player-v0.9.2-arm64-v8a.apk

First launch will prompt for battery-optimization exemption (Doze).

Known limitations (also tracked in ARCHITECTURE.md §8)

  • RELAY_REWARD still per-stream, not per-byte
  • No application-layer ACKs on binary relay (backpressure is time-paced, not credit-based)
  • Single VPS in current dev/test deployment; multi-VPS mesh ready in code but not exercised here

v0.9.1 — BlockPropagator + dynamic quorum + relay-credit + wallet save

18 Jun 20:23

Choose a tag to compare

musicchain v0.9.1 — BlockPropagator + dynamic quorum + relay-credit + wallet save-to-file

Core (musicchain):

  • Replace SyncManager with BlockPropagator: bitcoin-style block distribution (block.hello / getblocks / inv / getdata / data) over librats typed messages, with DHT bootstrap and per-block DHT announce for multi-source catch-up.
  • Dynamic confirmation quorum scaling with peer count: solo = 1, +1 per peer, capped at MAX_CONFIRMATIONS = 5.
  • Block.signing_hash() so verifiers check signatures against the header-minus-confirmations hash that producers actually sign.
  • Mini-node carries its wallet in mini.hello so the full node can credit the right address on RelayRewardTx via RelayCreditTracker.

Player (musicchain_player):

  • Wallet first-launch show-seed step gets a Save to a file button alongside Copy to clipboard (uses file_picker bytes for mobile sandbox, manual writeAsString for desktop).
  • Direct librats connect to the home node from LibratsDiscovery so player RPCs route peer-to-peer rather than tunneling through the VPS.
  • NDK bumped to 28.2.13676358 to match the :jni plugin.

APK: arm64-v8a only, ~83 MB, signed with the debug keys (replace before production).

v0.2.0 — moderator system, offline play-proof

16 Jun 21:49

Choose a tag to compare

Squashed v0.2.0 release. See the README for build instructions per target.

What's new since v0.1.0

  • Chain-level moderator system (FOUNDER / OP / VOICE) with passphrase founder bootstrap and no on-chain handles.
  • Offline play-proof: continuous local heartbeat + network / BSSID / battery / screen-on capture, signed bundle posted via offline.play_proof.submit on reconnect. Ten bot-pattern heuristics stubbed for the node-side validator.
  • Player UX: album-play queues the full sorted album, library rows show play count, Connect button long-press disconnects, banner shows full rats peer id + STUN-observed public address.
  • Per-target build scripts under scripts/ for all five binaries plus systemd install scripts for the two Linux node modes.

Binaries

Target File
Windows full node musicchain-node-windows-x64.zip
Windows desktop player musicchain-player-windows-x64.zip
Android player musicchain-player-android-arm64.apk
Linux full node musicchain-node-linux-x64.tar.gz
Linux mini node musicchain-mini-node-linux-x64.tar.gz

musicchain v0.1.0-20260610

10 Jun 10:06

Choose a tag to compare

musicchain — peer-to-peer music with on-chain royalties

I've been quietly building a peer-to-peer music platform for the last
few months. First public binaries are up, multi-container audio support
landed today, and the system finally feels coherent enough to write
about. Here's what it is and where it stands.

The shape of it

Songs live in two places at once. The chain holds a tiny block per
song: a Chromaprint fingerprint, content hash, ID3 metadata, royalty
splits, and a few timestamps. About 3 KB per block — small enough that
a Spotify-scale 100 M-track catalog would weigh in around 310 GB on
chain, same order of magnitude as a portable backup drive.

The audio bytes live with players. When you scan a folder, the
player computes the fingerprint locally and either matches you into an
existing swarm or registers a new block. When you press play on
someone else's track, the chain tells you which peers are currently
serving it and your player pulls the bytes peer-to-peer over librats.

That bisection is the whole trick. The chain is the catalog and the
royalty ledger; the swarm is the CDN.

Containers shipped today

mp3, ogg, flac, m4a, aac, opus, wav, aiff, wma, ape, mka — anything
libmpv decodes, the player scans and serves. Format detection rides on
extension, stamps into a 1-byte enum on the song block, and
round-trips back as the right file extension on download so other
players hand the right bytes to their decoder.

Discovery, online-only

There's no central song registry. The Discover tab is generated live
from "who's connected right now and what fingerprints did they vouch
for." Close the phone app — peers see your songs vanish from their
Discover within seconds. Open it again — they reappear.

The state pipeline runs end-to-end on rats connection callbacks plus a
small delta protocol: an unchanged library re-announces itself in one
96-byte round trip — a SHA-256 digest, no list bytes. New file? Single
swarm.add event. Delete a file? Single swarm.remove. The
"flood every fingerprint on every boot" pattern that the prototype
shipped with is gone.

The mini-nodes — our VPSes — gossip routes and online-state between
themselves over a librats mesh. Stand up another bootstrap VPS, hand it
a --peer-vps flag pointing at any existing one, and the mesh figures
out the rest. No single point of failure on the catalog surface.

Royalty model

Pre-10 000 plays per song (the discovery tier):

  • Listener earns 1 token as discoverer
  • Serving node earns 1 token
  • Artist's 1 token routes to a per-artist escrow, releasable by a
    moderator. Quality gate for the first decade of plays.

Post-10 000:

  • Artist and serving node each get 1 spendable token
  • No discoverer credit
  • Listener burns a dynamic amount that scales cubically with total
    supply between a 1 B floor and a 2 B hard cap — zero burn while the
    network is bootstrapping, exponential pressure as we approach the
    ceiling, mint refused entirely past it.

A play counts when timestamped heartbeats prove the listener consumed
at least 50 % of the song's unique timestamp range. Replay-the-chorus-
on-loop earns nothing; only union coverage of distinct seconds counts.
Seeking around still works as expected — listening to seconds 0–60 and
then 90–150 is 120 seconds of credit, not the wall-clock duration.

NAT and transport

librats over TCP today, with a custom keepalive (~30 s dead-socket
detection) and a duplicate-peer-eviction patch for the dial→disconnect
storm we hit when the watchdog re-dialed faster than half-open cleanup.

Cellular peers can't ICE-punch through symmetric NAT on the
US carrier we're testing on, so cellular traffic falls back to VPS
relay — TURN-shaped, with a binary forward path for audio chunks so
relayed downloads don't pay a base64 tax. Direct rats connections still
work on home wifi and most non-cellular networks.

Planned but not in this release: QUIC + libdatachannel ICE signaling
for proper hole punching on phones.

What this isn't

Honest list:

  • One moderator. The escrow-release model needs a real council before
    it scales beyond hobby use.
  • No algorithmic discovery. Discover is a live chain catalog filtered
    by who's online — no recommendations, no playlists from someone
    else's taste graph.
  • Mobile peer connections relay through VPS on symmetric cellular NAT.
  • Search is plain-substring against title / artist / genre / album.

Where to try it

Binaries on the latest GitHub release:

  • musicchain-player-windows-x64.zip — Windows desktop. Unzip
    anywhere, run musicchain_player.exe.
  • musicchain-player.apk — Android sideload. Needs READ_MEDIA_AUDIO
    (Android 13+) or "All files access" for folders outside /Music.

Workflow:

  1. Create a wallet (one password, auto-loaded thereafter via the OS
    keyring).
  2. Open My Library, tap the folder-plus icon, add a music folder.
  3. Tap refresh — files get fingerprinted on the device and join the
    chain catalog. Songs that match existing chain entries swarm-join
    silently; new ones queue for the next block.
  4. Switch to Discover. Pick an artist or genre, drill in, pick an
    album. The track list opens in the bottom pane (drag the handle to
    resize).
  5. Tap a track to stream, or long-press / right-click an album to
    download the whole thing into your library.

If you find a song you want to keep, hit download — it'll pull from
the swarm and add to your local library. From there, you serve it too.
That's the entire growth model.


Built solo, in C++ for the home node + VPS mini-node and Flutter for
the player. Comments, issues, and "this crashed when I…" reports
welcome on the repo.