Wavelength v0.1.1
Two weeks of running the v0.1.0 alpha on signet, testnet, and invite-only mainnet showed us where the daemon bends under load. Most of this release is what we learned. A daemon that could wedge when one consumer fell behind now sheds that pressure instead. A wallet that renewed its own expiring funds now does it inside a fee budget you set. And a command line that was pleasant to type is now safe to hand to a script or an agent.
This is a patch release on the v0.1.x line. No database migration is required, and the RPC surface is additive. Read Upgrading first: two client-side defaults changed in ways that can break an existing script.
One slow consumer no longer stalls the daemon
Wavelength runs its subsystems as actors that talk over bounded mailboxes. Bounded is the right call, because an unbounded mailbox just relocates a memory leak. The mistake was what happened at the boundary: sending to a full mailbox parked the sender until space opened up.
That turns a slow consumer into a daemon-wide outage. The timer actor was the worst case, because everything depends on timers. It delivered each callback with a blocking send, so one backlogged subscriber could park the timer goroutine and freeze every other subsystem's timers along with it. The server ingress path had the same shape: dispatching an inbound message into a full in-memory mailbox parked the reader that was supposed to keep draining the connection.
The fix is a non-blocking send, TryTell, on both in-memory and durable actor references, and then the discipline to use it everywhere a park would be fatal:
- The timer actor requeues a failed delivery with backoff instead of blocking on it, and coalesces repeat fires rather than sending to itself.
serverconnnever parks ingress dispatch on a full mailbox, bounds how many unary requests can be outstanding at once, and keeps inbound RPC dispatch out of the ingress write transaction.protofsmbounds state queries on both halves of the channel, the round FSM bounds its wallet asks and shares one deadline across scans, and the unroll registry no longer blocks on its own mailbox.
Two things make the next one visible instead of mysterious. serverconn now exports ingress liveness gauges and a deferral counter, so a backlog shows up on a dashboard before it shows up as a stall. And the idle mailbox poll decays instead of spinning at a fixed rate, which makes an idle daemon quiet enough that a busy one stands out.
Automatic refresh, on a budget you set
VTXOs expire, so the daemon renews them for you. In v0.1.0 that maintenance was too eager to be left unattended: it could pay a separate fee for every VTXO it touched, accept a quote no one had sanity-checked, and strand reservations when it hit an error or a restart.
Automatic refresh now coordinates VTXOs that share an expiry into one cohort and settles them together, enforces a quote budget and an output floor before accepting any operator quote, and reconciles ownership against durable round checkpoints so a crash mid-round releases what it reserved. The fee budget is a single floor-plus-rate curve under a mandatory global cap, which covers the fixed cost of refreshing a small VTXO without handing a large cohort an open-ended allowance.
Expiry handling got stricter in both directions. Expired VTXOs are persisted and reclaimed automatically, and a refresh restores them at full value rather than writing off the difference. A VTXO whose expiry height cannot be trusted is no longer treated as expired, because guessing in that direction spends money.
Manual refresh grew a consent gate to match. wavecli ark vtxos refresh --dry-run previews the fee before anything moves, the real call requires explicit approval, the same gate applies to the refresh tool over MCP, and passing --outpoint together with --all is now rejected instead of quietly resolved to one of the two.
A command line you can hand to a script
wavecli picked up an explicit contract this release, because agents and CI pipelines are first-class callers now:
- Money movement asks first.
wallet-sweep --broadcast,ark sweep --broadcast, and realark send inround|oorcalls require ay/Nconfirmation on a terminal. Non-interactive callers pass--yes. Without it the CLI describes the blocked action and exits 5 withCONFIRMATION_REQUIRED, before it dials the daemon. Previews and--dry-runnever ask. - Input is explicit. Piped stdin is no longer an implicit password source. The order is environment variable, then
--wallet-password-file, then--password-stdin, then the interactive prompt. - Input and output no longer share a flag.
--request-jsoncarries a raw request payload;--jsononly selects output format. - Flags are canonical kebab-case in help and examples, with snake_case spellings kept as silent aliases.
- Signals reach the command context, every finite RPC runs under a scoped timeout, and a cancelled send reports the cancellation rather than a generic failure.
Fees and accounting that reconcile
Round operator fees are now derived from sealed VTXO amounts rather than recomputed from a quote, and the forfeit round's operator fee is joined onto settlement rows, so what you were charged and what the ledger says agree.
Exit costs are stamped onto completed unilateral EXIT rows and settled fees onto cooperative-leave rows, which makes the true cost of leaving visible after the fact instead of only in the moment. Boarding fees are credited from wallet_balance, and the internal accounting leg that carries them no longer shows up as a transaction in your activity feed. Credit top-up transfers are labeled as such.
Swaps and credits
Out-swap acknowledgement terms are signed and bound to the proofs that carry them, so an acknowledgement cannot be replayed against different terms. In-swap vHTLC funding is keyed and idempotent, and funding reconciles after expiry instead of leaving a partially funded contract behind.
Sub-dust credit receives now fail with a typed error instead of hanging until the client's deadline expires. Credit admission has a dedicated, shorter timeout, and caller cancellation propagates instead of being swallowed. Both the Ark operator connection and the swap server connection run gRPC keepalives, which is what turns a silently dead TCP connection into a fast reconnect rather than a stuck request.
Wallet and chain backends
The Esplora-backed light wallet got the fix it needed most: script hashes were being sent in the wrong byte order. Wallet scans now answer from the address index, tip history seeding is reliable, and the wallet start block is resolved from the index rather than guessed.
On the broadcast side, a fee bump now fails closed at the configured maximum fee-rate cap rather than bumping past it, TxConfirmed carries the confirming block hash, and the unroll path removes abandoned and rejected transactions on terminal failure instead of leaving them in the mempool. That last one needed RemoveTransaction on the LND broadcaster, which is new.
Postgres correctness
Read-only transactions run at REPEATABLE READ, aborted transactions are classified as retryable, and serialization failures are retried inside the actor-delivery commit transaction rather than surfacing as an error. Unique-constraint violations are unwrapped so the mapped error carries the constraint name and detail, which turns an opaque write failure into something you can act on.
Mainnet operations
--allow-insecure-mainnet is a new opt-in for deployments that terminate TLS upstream and run the in-cluster hop without TLS or macaroons. The daemon's transport-security guard is a false positive in that topology, and this is the explicit way to say so. Without the flag, nothing changes.
Per-round boarding fanout is capped, which bounds how much nonce signing a single round can demand.
Release artifacts
Every v* tag now publishes the mobile bindings (Wavewalletdk.aar, Wavewalletdk.xcframework.tar.gz) and the browser WebAssembly runtime (Wavewalletdk.wasm.tar.gz) as release assets, so wavelength-sdk can fetch a stable set instead of building from a sibling checkout. Wavelength is now MIT licensed.
Upgrading
Drop-in for the daemon: no migration, and the RPC surface only grew. Two client-side changes can break an existing script:
echo -n 'pass' | wavecli unlockno longer works. Use--password-stdinto keep piping, or switch to--wallet-password-file.- Automation that moves money needs
--yes. Any pipeline callingwallet-sweep --broadcast,ark sweep --broadcast, orark send inround|oorwill exit 5 until you add it.
Old snake_case flag spellings still work, so kebab-case canonicalization needs no action.
Verifying the release
Use the repository's verification helper to check the signatures
and the downloaded binary or archive:
./scripts/verify-install.sh v0.1.1 <path>The binaries are built with Go 1.26.0. Linux users
can reproduce all archives with:
make release tag=v0.1.1On macOS and other BSD platforms, use the pinned Docker builder:
make docker-release tag=v0.1.1See the reproducible build guide
for the complete signing and verification process.
Full list of merged pull requests
- wallet: attribute round operator fees to onchain withdrawal activity by @Roasbeef in #993
- sdk/swaps+swapwallet: reject credit-bound in-ark events, label credit top-ups by @Roasbeef in #991
- round: release forfeit reservations via a status reconcile on round death by @Roasbeef in #992
- waved: allow notls/no-macaroons on mainnet behind an opt-in flag by @Roasbeef in #998
- multi: Preview refresh fees and gate refresh on consent by @levmi in #987
- sdk/swaps+swaprpc: settle credit-attach receives over the server-funded in-ark leg by @Roasbeef in #996
- wavecli: Fix refresh consent gate misdetecting non-TTY stdin by @Roasbeef in #1005
- multi: Report empty round-join as no-op, not INTERNAL by @Roasbeef in #1007
- waved: Reject unknown refresh outpoints with InvalidArgument by @Roasbeef in #1006
- release: Publish wasm runtime and mobile bindings on release by @Roasbeef in #1013
- build: Add release signing keys by @sputn1ck in #1015
- ci: Add release build workflow by @sputn1ck in #1016
- mobile-bindings: Attach WASM runtime to releases by @sputn1ck in #1019
- [v0.1.x-branch] Backport #1019: mobile-bindings: Attach WASM runtime to releases by @github-actions[bot] in #1020
- docs: Add MIT license by @bhandras in #1029
- docs: Document MIT license by @bhandras in #1031
- waved: fetch personalized terms after bootstrap by @bhandras in #1032
- wavecli: Wire process signals into command context by @darioAnongba in #1021
- txconfirm: carry the confirming block hash on TxConfirmed by @Roasbeef in #1040
- wavecli: Bound daemon RPC execution by @darioAnongba in #1022
- wavecli: Separate JSON input from output by @darioAnongba in #1023
- wavecli: Canonicalize flags to kebab case by @darioAnongba in #1024
- wavecli: define error and exit contract by @darioAnongba in #1025
- wavecli: make interactive input explicit by @darioAnongba in #1026
- waved+swapclientserver: add gRPC keepalive to long-lived daemon connections by @Roasbeef in #1044
- rpc: Clean up wavewalletrpc comments surfaced in the docs site by @jamaljsr in #1042
- Gracefully degrade sub-dust credit receive failures (#1041) by @ellemouton in #1045
- docs: fix stale instructions in the waved Docker and CLI skills by @Roasbeef in #1049
- mcp: require prepared send intents by @darioAnongba in #1027
- wavecli: reconcile schema with live surfaces by @darioAnongba in #1028
- actordelivery: retry serialization failures in the durable actor commit tx by @Roasbeef in #1053
- multi: mint one idempotency key per logical mailbox RPC by @Roasbeef in #1056
- db: run read-only Postgres transactions at REPEATABLE READ by @Roasbeef in #1057
- serverconn: keep inbound RPC dispatch out of the ingress write tx by @Roasbeef in #1058
- baselib/actor: decay the idle mailbox poll instead of polling at a fixed rate by @Roasbeef in #1055
- wavecli: gate raw money movement by @darioAnongba in #1036
- client: Recover expired VTXOs through normal refresh by @sputn1ck in #1000
- wallet: cap per-round boarding fanout to bound nonce signing (#858) by @ellemouton in #1059
- release: Merge main into v0.1.x-branch by @sputn1ck in #1072
- [v0.1.x-branch] Backport #1061: sdk/swaps: prevent duplicate in-swap vHTLC funding by @github-actions[bot] in #1070
- build: bump version to v0.1.1-rc1 by @sputn1ck in #1074
- [v0.1.x-branch] Backport #1076: lwwallet: resolve the wallet start block from the Esplora index by @github-actions[bot] in #1078
- [v0.1.x-branch] Backport #1077: lwwallet: speed up sync by bypassing the linear block scan by @github-actions[bot] in #1079
- [v0.1.x-branch] Backport #1075: wavewalletrpc: fix the sweep_all doc comment's flag spelling by @github-actions[bot] in #1080
- build: bump version to v0.1.1-rc2 by @sputn1ck in #1081
- [v0.1.x-branch] Backport #1082: multi: add non-blocking TryTell and bound timeout actor delivery by @github-actions[bot] in #1088
- [v0.1.x-branch] Backport #1083: vtxo: Make automatic refresh safe and cohort-aware by @github-actions[bot] in #1089
- [v0.1.x-branch] Backport #1067: txconfirm: fail closed at the max fee-rate cap on bump by @github-actions[bot] in #1096
- [v0.1.x-branch] Backport #1068: unroll: cancel LND broadcast of a proof tx on terminal failure by @github-actions[bot] in #1097
- [v0.1.x-branch] Backport #1093: serverconn+round: never park the ingress path, make backpressure visible by @github-actions[bot] in #1098
- [v0.1.x-branch] Backport #1092: swaps: bind out-swap acknowledgements to vHTLC terms by @github-actions[bot] in #1099
- [v0.1.x-branch] Backport #1100: waved: Reserve exact managed OOR inputs by @github-actions[bot] in #1101
- build: bump version to v0.1.1-rc3 by @sputn1ck in #1102
- build: bump version to v0.1.1 by @sputn1ck in #1104
Full Changelog: v0.1.0...v0.1.1