Skip to content

bump to latest main#29

Merged
dorianvp merged 488 commits into
feature/op-return-zingolib-compatfrom
main
Jul 13, 2026
Merged

bump to latest main#29
dorianvp merged 488 commits into
feature/op-return-zingolib-compatfrom
main

Conversation

@dorianvp

Copy link
Copy Markdown
Member

No description provided.

czarcas7ic and others added 30 commits July 2, 2026 17:27
The payment-output split already routed Orchard-pool payments into the
Ironwood view, but the change output is computed in `single_pool_output_
balance`, which counted it in the Orchard bundle even when Ironwood is
active. The builder routes Orchard-pool change into the Ironwood bundle,
so the proposal's per-bundle action counts could diverge from the built
transaction (each bundle is padded to its own action floor), defeating
the consistency this PR is meant to provide.

Thread an `orchard_change_to_ironwood` flag through
`ChangeStrategy::compute_balance` (carrying the same routing decision the
caller already computes for payments) and route the Orchard-pool change
count to the Ironwood action count when it is set.

Adds `orchard_change_routes_to_ironwood_bundle`, asserting that toggling
the flag moves the change between bundles and changes the fee.

Also: document the `ironwood`/`orchard_change_to_ironwood` parameters in
the `compute_balance` rustdoc, correct the `NetFlows` Ironwood-fields
comment, and restore the dropped note that the Orchard view still uses a
placeholder `BundleVersion`.
Pin the expected fees in the Ironwood fee tests instead of asserting only
that one is larger, so a change to the ZIP 317 per-bundle action floor is
caught. Also note in the `compute_balance` docs why
`orchard_change_to_ironwood` is a boolean (it carries the builder's
already-computed routing decision).
…pletion

zcash_client_backend: Wire Ironwood anchors into wallet transaction building
This stack now sits on a tx-builder branch that replaced the crate-wide
`ANY_ORCHARD_BUNDLE_VERSION` constant with a height-derived
`orchard_bundle_version_for_height` helper, and added an Orchard/Ironwood
routing view (and a routing flag) to `ChangeStrategy::compute_balance`.

Point the proposal's Orchard bundle view at the height-derived version,
which resolves the placeholder TODO left in the "Thread a separate
Ironwood bundle view" commit. Update the zip317 fee tests to the current
`ShieldedPool` constructor name, the concrete `orchard_v2` bundle version
(the value the old constant carried), and the current `compute_balance`
signature.

Also drop the `any(zcash_unstable = "nu6.3", zcash_unstable = "nu7")`
compile-time gate around the proposal path's `orchard_outputs_are_ironwood`
decision. The build path in `wallet.rs` gates Ironwood routing purely on
runtime NU6.3 activation, so the compile-time gate made the fee estimate
diverge from the built transaction in stable builds, and `nu6.3` is not a
recognized `zcash_unstable` value, so it warned on every build. The
proposal path now mirrors the builder via `orchard_outputs_to_ironwood`.
zcash_client_backend: Account for the Ironwood bundle in fee and change calculation
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.

Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.

Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: fdecroix <fdecroix@eryx.co>
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.

Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.

Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree. The Ironwood shard store and tree are exposed as
`IronwoodShardStore` / `IronwoodCommitmentTree` aliases so Ironwood usage is
self-documenting at call sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…or of batched query

`gather_account_transparent_inputs` issued one `get_spendable_transparent_outputs`
query per source address. Now that `InputSource` exposes the batched
`get_spendable_transparent_outputs_for_addresses`, the per-address loop is
unnecessary: inline a single batched call (filtered to `NonCoinbaseOnly`, then
account-redacted) at the sole call site in `propose_transaction` and delete the
function. This matches the batched approach already used for shielding and gives
general transfers the same single-query behavior for wallets with many
transparent addresses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ry builder

Factor the SQL query body of get_spendable_transparent_outputs_for_addresses
out into a new spendable_transparent_outputs_query helper, parameterized over
the address-predicate and ORDER BY fragments, and factor the coinbase-filter
encoding out into coinbase_filter_encoding. This is a pure refactor with no
behavior change: get_spendable_transparent_outputs_for_addresses now builds
its query via these helpers instead of inlining the SQL and match directly.

This is preparatory: a following commit adds a new
select_spendable_transparent_outputs query that reuses the same query body
with a different address predicate and ordering (by account, ordered by
value descending, for the value-bounded transparent gather).

Also tightens the redeem-script row read to use a single
row.get::<_, Option<Vec<u8>>>(...) call instead of a separate row.get(...)?
followed by an Option check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ent input gather

Add InputSource::select_spendable_transparent_outputs, a value-bounded
counterpart to get_spendable_transparent_outputs[_for_addresses]: rather
than returning every spendable transparent output for an account, it
returns only enough (ordered by descending value) to cover a requested
TargetValue, optionally padded by a caller-supplied
estimated_additional_fees headroom. This is intended to scale to wallets
with large numbers of transparent addresses and UTXOs, which would
otherwise need to materialize their entire UTXO set just to build a
small transfer.

The SQLite implementation backs the value-descending ordering with a new
idx_transparent_received_outputs_value_zat index (added by a new
migration), and reuses the spendable_transparent_outputs_query/
coinbase_filter_encoding helpers introduced in the preceding commit.

GreedyInputSelector::propose_transaction now uses this method (instead
of gathering every account transparent receiver via get_transparent_receivers
and querying get_spendable_transparent_outputs_for_addresses) when the
TransparentSpendPolicy requires transparent inputs, bounded initially by
the requested payment total and re-gathered if a subsequent
ChangeError::InsufficientFunds reveals the bound was too low. This also
lets GreedyInputSelector's trait bound on DbT simplify from
WalletRead + InputSource<...> down to just InputSource.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e-aware

InputSource::select_spendable_transparent_outputs now takes a concrete
fee_rule: &StandardFeeRule parameter in place of the previous
estimated_additional_fees: Option<Zatoshis> static headroom. The SQLite
gather recomputes the cumulative ZIP 317 marginal fee cost of the
transparent inputs examined so far at each step, maintaining a running
total of input sizes so the recomputation stays O(1) per candidate UTXO,
and stops once the post-fee accumulated value meets the requested
TargetValue. This removes the need for callers to guess a fee headroom
up front and avoids the previously-common second round trip to correct
an under-estimated one.

Using a concrete fee rule (rather than a generic FeeRuleT bounded by
FeeRuleT::Error: Into<Self::Error>) keeps the change localized: no other
InputSelector/ChangeStrategy signatures need updating, since the gather's
fee estimate is a heuristic bound decoupled from the caller's actual
change strategy (real fee/balance correctness is still enforced by that
change strategy, and the existing retry-on-InsufficientFunds fallback is
unchanged).

Adds SqliteClientError::FeeRuleError to carry FeeRule errors from the
gather (zip317::FeeError doesn't implement std::error::Error, so it's
wrapped).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… re-gather succeeds without new shielded notes

GreedyInputSelector::propose_transaction's retry loop tracked progress
solely via growth in the shielded note total (prior_available vs.
new_available), even though the same loop can also grow the transparent
input set via a re-gather when the initial (fee-only) estimate proves
insufficient. For an account with no spendable shielded notes at all (or
none beyond what's already excluded), shielded-side "progress" can never
be made, so the loop bailed out with InsufficientFunds immediately after
the first failed balance computation, even when the freshly re-gathered
transparent inputs would have been sufficient on the very next iteration.

Track whether the transparent gather grew this iteration
(transparent_gather_grew) and treat that as valid progress alongside
shielded note growth, so the loop retries compute_balance with the
enlarged transparent input set instead of failing prematurely.

Adds a regression test (propose_t2shielded_requires_transparent_regather)
that funds a transparent-only account with many small UTXOs and pays a
shielded recipient, forcing the initial transparent-only fee estimate
(which cannot account for the additional shielded action) to undershoot
and require exactly this re-gather-without-shielded-progress path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…iew findings

- Move two changelog entries that described this PR's new work out of
  already-released, dated version sections (zcash_client_backend's
  [0.22.0] and zcash_client_sqlite's [0.20.0]) into the correct
  Unreleased/PLANNED sections. Released changelog sections must remain
  immutable; both frozen sections now match main exactly.
- Reword changelog entries and doc comments that described this PR's own
  discarded intermediate designs (an unbounded gather, an
  Option<Zatoshis> static-fee-headroom parameter) as though they were
  prior shipped behavior. Since select_spendable_transparent_outputs is
  entirely new and unreleased, changelog/doc text should describe only
  the final capability, not the path taken to arrive at it.
- Remove a doc comment's direct reference to this repo's internal
  AGENTS.md conventions file, meaningless to external readers of the
  public rustdoc.
- Improve NonEmptyBTreeSet's doc comment (was "A quick and easy
  non-empty BTreeSet") and document its previously-undocumented public
  fields; drop a private implementation note about possibly moving it to
  another crate someday.
- Document the spend_policy parameter on InputSelector::propose_transaction,
  which controls the same privacy behavior the method's doc already
  describes but was never mentioned.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n derivation

When gap-limit generation derives a transparent address that already exists as a
standalone (Foreign) import, store_address_range now upgrades the existing addresses
row in place to its derived form instead of inserting a duplicate row for the same
receiver (which the UNIQUE index on cached_transparent_receiver_address rejects). The
row id is preserved, so UTXOs already attached to the imported receiver become spendable.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… duplicate receivers

The add_transparent_receiver_address_index migration already collapses a transparent
receiver duplicated as both a derived and an imported record. It now also carries the
minimum exposed_at_height across the merged records onto the surviving canonical record,
so an earlier exposure recorded against the imported record is not lost.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ss upgrade

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ivation

A transparent receiver duplicated across accounts was previously an unconditional
migration abort. Resolve it instead when derivation definitively identifies the correct
record: if exactly one record reproduces the receiver when derived from its own account's
viewing key at its recorded child index, that record is retained, and the received
outputs of the other records are repointed to it along with their account attribution
(the reason cross-account merges were previously unsafe). No account is privileged in
this determination: an address genuinely derived under the ZIP 32 account index
0x7FFFFFFF used for the zcashd legacy account wins over another account's imported
record just as any other verified derivation would. Cross-account duplicates for which
no unique record verifies still abort.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ount on derivation

The runtime reconcile-on-derive path in store_address_range only matched a Foreign row
in the deriving account, so deriving an address that had been imported standalone under a
*different* account fell through to the INSERT and failed hard on the receiver-uniqueness
index (the upsert's conflict target does not cover it), wedging gap-limit generation and
anything that drives it. Deriving the address is itself proof that the deriving account
owns it, so the lookup now matches the Foreign row regardless of account and the upgrade
moves the row's account attribution — and that of any outputs received at the address —
to the deriving account, mirroring the migration's derivation-based cross-account
resolution. Retargeting cannot violate the address-tuple constraint: a row already held
by the deriving account at that (scope, child index) would be this same receiver, which
the uniqueness index makes impossible.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ges from rebase onto main

Rebasing onto main (which has moved substantially, including the
ShieldedProtocol -> ShieldedPool rename and Ironwood fee/change wiring)
surfaced two required adaptations beyond conflict resolution:

- Update the remaining ShieldedProtocol::Sapling references (added by
  this branch's own tests) to ShieldedPool::Sapling, avoiding deprecated-item
  usage now that ShieldedProtocol is a deprecated alias for ShieldedPool.
- FeeRule::fee_required gained a new ironwood_action_count parameter;
  pass 0 from the transparent-only gather in
  select_spendable_transparent_outputs, matching the existing 0 passed
  for sapling_input_count/sapling_output_count/orchard_action_count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…utxos_to_txos, for the value_zat index migration

fix_transparent_received_outputs rebuilds transparent_received_outputs
via DROP TABLE + ALTER TABLE ... RENAME TO, which destroys any index
created on the old table. add_transparent_value_index previously
depended only on utxos_to_txos (the migration that first creates the
table), which does not guarantee it runs after that rebuild; depending
on schemerz's topological ordering among otherwise-independent
migrations, the index could have been created before the rebuild and
then silently lost, with no indication that anything had gone wrong.

Depend on fix_transparent_received_outputs directly instead, which
transitively depends on utxos_to_txos, guaranteeing the index is always
created on the table that will actually persist.

Also corrects the migration's own doc comment, which incorrectly
asserted that the index "remains valid across the rebuild" so long as
column names were preserved -- SQLite indexes do not survive a
DROP TABLE regardless of the recreated table's schema.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…solution

- Decode key scopes via KeyScope::decode and match on the enum rather than raw
  integer codes, and replace the address-record tuple with a bespoke struct.
- Verify ephemeral-scope records by derivation: the ephemeral scope has a genuine
  derivation relationship to the account (AccountPubKey::derive_ephemeral_ivk), so a
  cross-account duplicate whose ephemeral record reproduces the receiver is resolved
  in its favor rather than aborting. Only Foreign records remain unverifiable, having
  no derivation relationship to their account.
- Fix a stale child-index reference in a test comment.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
LukasKorba and others added 29 commits July 11, 2026 21:34
…blocks_rows

Per review: rather than exposing a caller-supplied floor (an API with an
unsound-use surface), put_blocks_rows now determines internally when
nullifier-map insertion is skippable, and put_blocks — and therefore every
consumer — benefits automatically. This also fixes the long-standing missed
optimization upstream: nullifiers were inserted unconditionally even when
the scanned range extended the contiguous fully-scanned frontier, where the
map can never be consulted (it exists to detect spends observed before the
corresponding note's block is scanned, which cannot occur below a
contiguous frontier).

The derivation requires knowing the fully-scanned height at the low-level
layer, so LowLevelWalletRead gains block_fully_scanned_height (the traits
are unreleased, so this is not a breaking change). Frontier-extending
batches retain the trailing NULLIFIER_MAP_RETENTION_BLOCKS window, keeping
the map aligned with an equal prune_tracked_nullifiers pruning depth;
out-of-order ranges (after a gap, recent-first, chain-tip pre-scans)
continue to track the nullifiers of every block, pinned by unit tests
covering exactly the after-a-gap counter-example from review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er-tracking-floor

zcash_client_backend: add a nullifier-tracking floor to `put_blocks_rows`
…iki-e/install-action-2.83.0

build(deps): bump taiki-e/install-action from 2.82.11 to 2.83.0
…l-anchors

pczt: Preserve absent shielded anchors across roles
…onwood

After NU6.3 the Orchard turnstile forbids value from entering the Orchard
pool, so a caller that names Orchard as its fallback change pool must not
produce Orchard change for a transaction that spends no Orchard notes.
`select_change_pool` promotes such change to Ironwood, but nothing covered
that promotion through a change strategy, so a regression in the wiring
between `MultiOutputChangeStrategy` and `select_change_pool` would not have
been caught.

Add a test that shields a transparent UTXO with an Orchard fallback and
checks the proposed change pool either side of NU6.3 activation: Orchard
before, Ironwood after. The fee is identical at both heights, as the change
output is charged to the Ironwood bundle rather than the Orchard one and
each pads to the same two-action floor.

Add `ChangeValue::ironwood`, parallel to the existing `sapling` and
`orchard` constructors, so the expected change value can be named.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l-test

zcash_client_backend: test that an Orchard change fallback is promoted to Ironwood after NU6.3
`Step::from_parts` previously accepted a non-optional `BlockHeight` and
reinterpreted the value `0` as "no anchor" — leaking the protobuf
`anchorHeight` scalar-default sentinel into the domain constructor and
treating genesis, which can never be a valid anchor, as a magic value.

Let `Step::from_parts` take `anchor_height: Option<BlockHeight>` like the
field it populates, and require a concrete anchor for any step that produces
a shielded bundle — one that spends shielded notes, pays to a shielded pool,
or returns shielded change. Every shielded-tree lookup such a step performs,
including the dummy spends that pad an output-only bundle, is bound to that
anchor, so an anchor selected at proposal construction time is what keeps a
routed-output transaction indistinguishable from one that spends real notes.
Only a purely transparent step may carry `None`.

Proposals are ephemeral, so a `0` anchor on a shielded-bundle step is
malformed rather than a legacy deferral: the protobuf decoder rejects it at
the parse boundary with a new `ProposalDecodingError::MissingShieldedAnchor`,
and `Step::from_parts` enforces the same invariant for direct callers with a
new `ProposalError::MissingShieldedAnchor`. Both are covered by tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…elding-anchor

fix: preserve shielding proposal anchors
Prune the `unpublished` entries for zcash_protocol, zcash_address,
zcash_keys, zcash_primitives, zcash_proofs, zcash_transparent, and
zcash_history, which have since been published to crates.io, and refresh
the corresponding publisher records.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0.22-stack

Release zip321 0.9.0-rc.1, pczt 0.8.0-rc.1, zcash_client_backend 0.24.0-rc.1, zcash_client_sqlite 0.22.0-rc.1
`propose_send_max` never assigned an output pool to the payment when the
recipient was a bare transparent address or a unified address having no
shielded receiver, so every such proposal failed validation with
`ProposalError::PaymentPoolsMismatch`. Assign `PoolType::Transparent` to
the payment for recipients that are paid directly via a transparent
output, excluding TEX recipients, whose payment is delivered by the
ephemeral second step. The unified-address arm of the fee computation
now shares the same predicate, so the fee's transparent-output
accounting and the payment pool assignment cannot drift apart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…parent-inputs

The `Address::Tex` arm of the send-max fee computation was not gated on
the `transparent-inputs` feature, so in builds without that feature a
send-max proposal to a TEX recipient deducted the second-step fee from
the payment but constructed no ephemeral step, failing proposal
validation with a misleading `PaymentPoolsMismatch` error. Gate the arm
and reject such recipients up front with
`GreedyInputSelectorError::UnsupportedTexAddress`, the same error that
`propose_transfer` reports for this condition.

Expressing this error requires widening `ProposeSendMaxErrT`'s
note-selection error type from `BalanceError` to
`GreedyInputSelectorError`; balance errors encountered during send-max
input selection are now wrapped in `GreedyInputSelectorError::Balance`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alue

When a wallet's spendable balance exactly equaled the required fee,
`propose_send_max` constructed a proposal paying zero to a shielded
recipient, consuming the entire balance as fees (transparent recipients
were rejected, but with an unrelated `ZeroValuedTransparentOutput`
error). Report `InsufficientFunds` instead whenever the fee leaves
nothing for the recipient, with `required` reflecting the minimum total
needed to deliver a nonzero amount.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…available

`propose_send_max` decided whether an Orchard receiver preempts Sapling
delivery using `ZcashAddress::can_receive_as`, which reflects the
receivers present in the address rather than the receivers this build
can pay. In a build without the `orchard` feature, a payment to a
unified address having both Sapling and Orchard receivers was assigned
no output pool at all, failing proposal validation with
`PaymentPoolsMismatch`. Give Orchard receivers delivery precedence only
when the `orchard` feature is enabled, so that such payments fall back
to the Sapling receiver.

Additionally, reject a unified address for which no payment pool can be
assigned (one containing no receiver the build is able to pay) with
`GreedyInputSelectorError::UnsupportedAddress` rather than letting it
fail proposal validation with the same misleading error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…awarely

Route the Ironwood action count in `propose_send_max` through
`transactional_action_count` with the bundle version in effect at the
target height, as every other action-count computation site does,
instead of calling `num_actions` directly with hardcoded
`Flags::ENABLED`. The result is unchanged for all current bundle
versions (every Ironwood version's default flags are fully enabled),
but the count now follows the version's action-count policy rather
than silently bypassing it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the `Builder::new_with_orchard_pool_bundle_type` override
constructor with separate `orchard_bundle_type` and
`ironwood_bundle_type` fields on `BuildConfig::Standard`, selecting the
transactional bundle type independently for each Orchard protocol value
pool directly in the build configuration.

This mirrors the existing per-pool anchor fields and keeps the build
config as the single source of truth, instead of threading an override
past it; the `BundleType::Coinbase` assertion disappears along with the
override channel it guarded. `zcash_client_backend` sets both fields
from its existing single parameter, so its API is unchanged.
…panic

The sum of the two per-transaction fees for a ZIP 320 send-max pair was
computed with an `expect`, which is reachable when a caller-supplied fee
rule produces fees near the maximum monetary amount. Report the
overflow as `GreedyInputSelectorError::Balance(BalanceError::Overflow)`
instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every existing send-max test funds the wallet within a single shielded
pool. Add a two-pool-tester test that funds one note in each of the
Sapling and Orchard pools and sends the maximum to an external
recipient, verifying that both notes are spent in a single step, that
the ZIP 317 fee reflects both padded shielded bundles, and that the
built transaction mines and leaves the wallet empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ipient

zcash_client_backend: Fix `propose_send_max_transfer` transparent-recipient and edge-case handling
…le-type

Allow per-pool Orchard protocol bundle types
@dorianvp
dorianvp merged commit d6289bc into feature/op-return-zingolib-compat Jul 13, 2026
47 of 49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants