feat: Add Serialize to response types - #20
Open
evanlinjin wants to merge 1 commit into
Open
Conversation
Every type in `response` derived `Deserialize` only, so a downstream crate that wanted to persist a cached response (e.g. a script's history of `response::Tx`) had to define a mirror type and hand-write the conversions just to get the data back out. Several fields decode through `custom_serde` helpers, so each one gains a symmetrically-named serializer that writes the Electrum wire representation back out rather than the Rust type's own serde: - `to_consensus_hex` / `to_cancat_consensus_hex` - `feerate_opt_to_btc_per_kb` (writes `-1.0` for `None`) - `feerate_to_sat_per_byte` - `weight_to_vb` - `amount_to_btc` / `amount_to_sats` / `amount_to_maybe_negative_sats` - `all_inputs_confirmed_bool_to_height` (writes `0` / `-1`, not a bool) `PartialEq`/`Eq` are derived on the types that lacked them so the round trip can be asserted in tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
noahjoeris
requested changes
Aug 21, 2026
noahjoeris
left a comment
There was a problem hiding this comment.
Have a look at PR #17 which removes the need for those helpers:
- amount_from_btc / amount_to_btc
- amount_from_sats / amount_to_sats
- amount_from_maybe_negative_sats / amount_to_maybe_negative_sats
| Ok(items) | ||
| } | ||
|
|
||
| pub fn to_cancat_consensus_hex<T, S>(values: &[T], serializer: S) -> Result<S::Ok, S::Error> |
There was a problem hiding this comment.
nit: this should be _concat_ right?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Every type in
src/response.rsderivedserde::Deserializeonly. That makes the responses decodable but not persistable.The concrete motivation is downstream:
bdk_electrum_streamingcachesresponse::Txvalues (a script's Electrum history) and now wants to persist that cache. Becauseresponse::Txand friends are deserialize-only, it had to define a private mirror enum plus hand-written conversions in both directions purely to get the data back out. DerivingSerializeupstream removes that workaround entirely.What this does
Adds
serde::Serializeto every type insrc/response.rs, plusPartialEq/Eqon the ones that lacked them so round trips can be asserted.This is not just a derive change. Several fields decode through helpers in
src/custom_serde.rs, and each one needed a matching serializer so the output is still valid Electrum wire data rather than the Rust type's own serde representation:from_consensus_hexto_consensus_hexfrom_cancat_consensus_hexto_cancat_consensus_hexfeerate_opt_from_btc_per_kbfeerate_opt_to_btc_per_kbNoneis written back as-1.0feerate_from_sat_per_bytefeerate_to_sat_per_byteweight_from_vbweight_to_vbamount_from_btcamount_to_btcamount_from_satsamount_to_satsAmount's own serdeamount_from_maybe_negative_satsamount_to_maybe_negative_satsall_inputs_confirmed_bool_from_heightall_inputs_confirmed_bool_to_heightheightfield as0/-1, never a boolThe existing
#[serde(rename = ...)]attributes (hex,tx_hash) already apply to both directions, so field names are unchanged.Tests
round_trip_responses: for every type, value →serde_json::to_value→ deserialize →assert_eq!against the original.round_trip_tx_preserves_variant:Txis#[serde(untagged)], so this checks that both variants land back on themselves. They do —MempoolTxis tried first and only matches whenfeeis present, andConfirmedTxcatches the rest.mempool_tx_serializes_height_not_bool: asserts the exact JSON shape, pinningheight: 0/height: -1. This is the case most likely to regress.cargo test,cargo clippy --all-targetsandcargo fmt --checkall pass.For the reviewer to decide
amount_from_maybe_negative_satsis lossy in a way that cannot be undone.GetBalanceResp::unconfirmedis documented as possibly negative, but the deserializer calls.unsigned_abs(), so the sign is discarded at decode time andAmounthas no way to represent it.amount_to_maybe_negative_satstherefore always writes a non-negative number. A Rust-value round trip is lossless; a JSON→JSON round trip of a negative balance is not. Fixing that properly means changingunconfirmedtoSignedAmount, which is a breaking change, so I left it alone and documented the asymmetry on the helper.EstimateFeeRespandFeePairgo throughf32; the tests use values that are exactly representable (0.001BTC/kvB,1sat/vB), but arbitrary server-supplied rates may not survive a round trip bit-for-bit. This is pre-existing on the decode side; serializing just makes it visible in both directions.src/response.rsas requested. TheDeserialize-only types insrc/protocol.rsandsrc/notification.rsare untouched — happy to extend if you want them too.Checklists
All Submissions:
cargo test,cargo clippy --all-targetsandcargo fmt --checkbefore pushing🤖 Generated with Claude Code