This release finishes the nutroot wire format for v3 (BLS) keysets, adds abort signals and an optional keepalive to the transport, and closes a long list of findings from building real wallets against rc.10 on the web, Node and Deno. There are a few more breaking changes since rc.10; the migration guide has each one with before and after code.
Upgrading from rc.10: read these even if you never touch a v3 keyset
- Quote lock keys derived from a seed are now scoped to the mint's NUT-06 identity pubkey, one counter per mint under
quoteCounterKey(mintPubkey); the singleQUOTE_COUNTER_KEYexport is gone and a customCounterSourcemust acceptmint-quote-lock:<pubkey>keys. Keys derived by rc.10 are not recoverable by scan, so keep theprivkeyof any unpaid rc.10 quote. The derivation is marked@experimental: it follows draft NUT-13 and NUT-06 and may change again before GA, which is why the contract remains "persist theprivkeywith its quote". - Every
Mintmethod that took a trailingcustomRequestnow takes{ customRequest?, signal? }. Wallet operation configs, builders (.signal()) and quote or state checks accept anAbortSignaltoo; a one-shot operation honours it until preparation finishes and never mid-commit. An aborted call rejects withCallerAbortError, now exported alongsideUncancellableReadError. AmountthrowsAmountErroron implicit numeric coercion (a + 1,a < b,Number(a)); string contexts still work.Amount.fromaccepts astructuredClone'd Amount, so IndexedDB andpostMessageround trips no longer need special handling.getDecodedTokenBinaryrequires the keyset id list.Mint.oidcAuthis removed in favour ofOIDCAuth.fromMintInfo, and OIDC endpoints must match the discovery document's scheme. Lock families are named'nutroot' | 'p2pk'rather than by version.- Requests time out after 5 minutes per attempt by default (
requestTimeout: Infinitydisables it), retries back off more slowly and log at info, and aNetworkErrornames the mint origin and the underlying cause. - The ES build preserves modules, so a bundler that imports only
Amountor the token codec no longer pays for the whole wallet.Walletitself is unchanged in size. - NUT-17: a subscription whose socket closes now has its error callback invoked (any close code), so a plain
on.mintQuotePaidcan resubscribe or fall back. The optionalwsKeepaliveMsonWalletorMintprobes the socket and treats silence as a close, for the half-open connections a proxy leaves behind.WSConnection.onClosetakes aWSCloseEvent, so the shipped types no longer need the DOM lib on Node. mnemonicToSeedSyncderives the BIP-39 seed from a phrase with no new dependency; validate the phrase first with@scure/bip39or similar.- Every mint, batch mint and melt preview now has a serialize and deserialize pair.
v3 keysets
Nutroot is still a draft and v3 keysets are served only by demo mints, so nothing here affects funds on a public mint. The transcript picked up three spec changes since rc.10 (the commit leaf, melt outputs priced at quote plus fee reserve, and proof inputs named by Y), which is what makes rc.11 spend against current Nutshell. BLS keysets carry no DLEQ; requireDleq accepts 02 proofs on the strength of the pairing check. A wallet now refuses to bind to a keyset id version this build cannot spend rather than failing later, and refuses pre-v3 locks or NUT-29 batch operations the mint does not advertise.
What's Changed
- fix(utils): resolve short keyset IDs in getDecodedTokenBinary by @Hardeezah in #1153
- feat(nutroot): the commit leaf (NUT-10 type 0x04) by @robwoodgate in #1151
- test: raise the vitest timeout and shrink two heavy prepareMint fixtures by @robwoodgate in #1157
- fix(mint): check a bolt11 melt preimage against the invoice payment hash by @robwoodgate in #1161
- fix(utils): apply the keyset unit alphabet to version 01 ids too by @robwoodgate in #1162
- feat(wallet): add serialize helpers for mint, batch mint and melt previews by @robwoodgate in #1159
- fix(auth): check OIDC endpoint schemes against the discovery document by @robwoodgate in #1160
- refactor: route the last UTF-8 decodes through utils/bytes by @robwoodgate in #1168
- docs: say where transport policy lives by @robwoodgate in #1170
- chore(docker): update NUT_IMAGE to cashubtc/nutshell:0.21.0 by @robwoodgate in #1173
- feat(nutroot): melt output amount is the quote amount plus its fee reserve by @robwoodgate in #1172
- fix(model): require boolean true for advertised NUT support by @KvngMikey in #1176
- feat(nutroot): name transcript proof inputs by Y by @robwoodgate in #1175
- chore(deps): bump typescript-eslint from 8.69.0 to 8.70.0 in the eslint group by @dependabot[bot] in #1180
- fix(utils): getDecodedTokenBinary requires the keyset id list by @robwoodgate in #1178
- fix(core): keep a token's witness verbatim when re-encoding by @robwoodgate in #1179
- chore(deps): bump the vitest group with 2 updates by @dependabot[bot] in #1182
- chore(deps): bump the dev-tooling group with 3 updates by @dependabot[bot] in #1183
- chore(deps): bump the vitest family to 5.0.0 by @robwoodgate in #1187
- test(mutation): give the Stryker vitest config a 20s test timeout by @robwoodgate in #1189
- ci(mutation): pin vitest 4 for the weekly Stryker run by @robwoodgate in #1191
- chore(docker): update CDK_IMAGE to cashubtc/mintd:0.18.1 by @robwoodgate in #1193
- fix(wallet): refuse pre-v3 locks the mint does not advertise by @Kelbie in #1195
- fix(wallet): refuse keyset id versions this build cannot spend by @robwoodgate in #1199
- docs: correct usage snippets that no longer compile by @robwoodgate in #1198
- test(wallet): pin that a proof subscription refuses an unreadable keyset by @robwoodgate in #1201
- fix(wallet): reject unspendable proofs for an unlocked payment request by @Kelbie in #1197
- feat(wallet): scope quote lock keys to the mint identity by @robwoodgate in #1202
- fix(wallet): fill NUT-04 base fields on mint quote subscription updates by @robwoodgate in #1203
- fix(core): refuse implicit numeric coercion of Amount by @robwoodgate in #1204
- docs: point at the repository for files the package does not ship by @robwoodgate in #1206
- fix(utils): refuse text with a lone surrogate in the CBOR and TLV encoders by @robwoodgate in #1209
- fix(wallet): report the websocket mode on subscriptions without pollMs by @robwoodgate in #1207
- fix(wallet): normalize melt quote subscription updates like HTTP responses by @robwoodgate in #1208
- docs: document websocket teardown and injectWebSocketImpl by @robwoodgate in #1215
- fix(transport): log a failed unsubscribe at info by @robwoodgate in #1214
- fix(events): skip the batched quote check where the mint does not offer it by @robwoodgate in #1213
- docs: correct the CREQB1 size claim and scope the pre-flight lock check to v3 by @robwoodgate in #1216
- fix(transport): export CallerAbortError and UncancellableReadError by @robwoodgate in #1212
- docs: say that proof-set concurrency is the app's job by @robwoodgate in #1220
- feat(crypto): add mnemonicToSeedSync by @robwoodgate in #1221
- fix(wallet): refuse NUT-29 batch operations the mint does not advertise by @robwoodgate in #1222
- fix(transport): time out request attempts after 5 minutes by default by @robwoodgate in #1224
- docs: lead the send pages with the recoverable pattern by @robwoodgate in #1223
- fix(model): accept a structuredClone'd Amount in Amount.from by @robwoodgate in #1228
- docs: say how Amount survives persistence by @robwoodgate in #1229
- fix(crypto): write the BLS field orders as literals by @robwoodgate in #1231
- refactor(auth): build OIDCAuth from mint info, not from Mint by @robwoodgate in #1232
- build: preserve modules in the ES build by @robwoodgate in #1233
- docs: make every docs-src snippet type-check by @robwoodgate in #1234
- docs: tell errors apart by instanceof and name, not constructor.name by @robwoodgate in #1237
- chore(eslint): use defineConfig instead of the deprecated tseslint.config by @robwoodgate in #1236
- ci: type-check docs-src snippets against the shipped types by @robwoodgate in #1235
- fix(wallet): log a keyset summary on load instead of the whole cache by @robwoodgate in #1238
- fix(transport): quieter retry logging, slower backoff, and NetworkError names the mint by @robwoodgate in #1240
- refactor(wallet): name lock families by encoding, not version by @robwoodgate in #1242
- docs: say which keyset a wallet is on and how to tell the curve by @robwoodgate in #1243
- feat(mint): per-call options with an abort signal on every Mint method by @robwoodgate in #1244
- feat(wallet): accept an abort signal on wallet operations by @robwoodgate in #1245
- chore(errors): move stray error classes into model/Errors by @robwoodgate in #1246
- docs(keysets): say BLS keysets carry no DLEQ and requireDleq accepts them by @robwoodgate in #1248
- fix(transport): drop the DOM CloseEvent type from the WSConnection public surface by @robwoodgate in #1249
- fix(transport): fail established subscriptions when the socket closes by @robwoodgate in #1251
- feat(transport): optional keepalive probe for NUT-17 sockets by @robwoodgate in #1253
- docs(wallet): mark the quote lock seed derivation experimental by @robwoodgate in #1254
- chore(main): release 5.0.0-rc.11 by @robwoodgate in #1155
New Contributors
- @Hardeezah made their first contribution in #1153
Full Changelog: v5.0.0-rc.10...v5.0.0-rc.11