Fills the third of pallas-txbuilder's Conway TODOs (after auxiliary_data + certificates): - StagingTransaction gains `voting_procedures: Option<Vec<u8>>` (opaque CBOR — same Eq-derive workaround as auxiliary_data + certificates) - Builder methods .voting_procedures(cbor_bytes) + .clear_voting_procedures() - conway::build_conway_raw decodes via VotingProcedures::decode_fragment and assigns to TransactionBody.voting_procedures VotingProcedures is itself a NonEmptyKeyValuePairs<Voter, NonEmptyKeyValuePairs<GovActionId, VotingProcedure>> map, so callers pre-assemble the full map and encode once. Tests: - voting_procedures_plumb_through_to_tx_body — encode DRepKey vote-Yes on a synthetic GovActionId, build, decode, assert round-trip - no_voting_procedures_means_none — negative path
6.2 KiB
Sulkta-Coop/pallas — feat-aux-data branch notes
This is a vendored fork of txpipe/pallas
based on the v0.32.1 tag. It exists to fill in three upstream // TODO
markers that block real-world Conway-era transaction building.
The fork is consumed via [patch.crates-io] in
Sulkta-Coop/aldabra's
workspace Cargo.toml. Once these changes are accepted upstream, the
patch entry gets dropped and aldabra goes back to vanilla crates.io.
What's added beyond v0.32.1
1. pallas-txbuilder — auxiliary_data field on StagingTransaction
Upstream had // pub auxiliary_data: TODO on the struct and
auxiliary_data: None.into(), // TODO in the conway builder. Without
this, CIP-20 / CIP-25 / CIP-68 metadata can't ride a tx built with
pallas-txbuilder.
Added:
pub auxiliary_data: Option<Vec<u8>>field (opaque CBOR bytes —pallas_primitives::AuxiliaryDatadoesn't implEq, which the rest ofStagingTransactionrequires).- Builder methods
.auxiliary_data(cbor_bytes)and.clear_auxiliary_data(). conway::build_conway_rawnow decodes the bytes viaAuxiliaryData::decode_fragmentand plumbs them in. The existingauxiliary_data_hashcomputation block (already in upstream) runs after assignment and populates the body's hash automatically.
Tests added:
auxiliary_data_round_trips_through_build— encodes a CIP-25 shape, attaches, builds, decodes, asserts byte-equivalent re-encode + matchingauxiliary_data_hash.no_auxiliary_data_means_no_hash— negative path.
2. pallas-txbuilder — certificates field on StagingTransaction
Upstream had // pub certificates: TODO and
certificates: None, // TODO in the conway builder. Without this,
stake registration / delegation, pool registration, DRep ops, and
Voltaire-era cert types can't be built.
Added:
pub certificates: Option<Vec<Vec<u8>>>field (opaque CBOR per cert, same reason asauxiliary_data).- Builder methods
.add_certificate(cbor_bytes)and.clear_certificates(). conway::build_conway_rawdecodes each entry viaCertificate::decode_fragmentand plumbs them intoTransactionBody.certificatesviaNonEmptySet::from_vec.
Tests added:
certificates_plumb_through_to_tx_body— encodes aStakeRegistration(AddrKeyhash([7;28])), builds, decodes, asserts the cert round-trips byte-for-byte.no_certificates_means_none— negative path.
3. pallas-txbuilder — voting_procedures field on StagingTransaction
Upstream had voting_procedures: None, // TODO in the conway builder.
Without this, DRep / SPO / committee voting on Conway governance
actions can't ride a tx built with pallas-txbuilder.
Added 2026-05-06:
pub voting_procedures: Option<Vec<u8>>field (opaque CBOR for the sameEq-derive reason asauxiliary_data).- Builder methods
.voting_procedures(cbor_bytes)and.clear_voting_procedures(). conway::build_conway_rawdecodes viaVotingProcedures::decode_fragmentand assigns toTransactionBody.voting_procedures.
The VotingProcedures type is itself a single map
(NonEmptyKeyValuePairs<Voter, NonEmptyKeyValuePairs<GovActionId, VotingProcedure>>), so callers pre-assemble the full map and encode
once. No staged accumulator needed.
Tests added:
voting_procedures_plumb_through_to_tx_body— encodes a single DRepKey-vote-Yes on a synthetic GovActionId, builds, decodes, asserts the vote round-trips byte-for-byte.no_voting_procedures_means_none— negative path.
4. pallas-addresses — pub fn new on StakeAddress
Upstream defined pub struct StakeAddress(Network, StakePayload) with
unexported tuple fields and no new() constructor — so external
callers can't construct one directly. ShelleyAddress::new exists
already; this just matches the pattern.
Without this, computing a wallet's reward (stake) address
required round-tripping through a ShelleyAddress and TryFrom —
clunky and indirect.
How aldabra consumes this
Sulkta-Coop/aldabra/Cargo.toml has:
[patch.crates-io]
pallas-codec = { git = "...Sulkta-Coop/pallas.git", branch = "feat-aux-data" }
pallas-crypto = { git = "...Sulkta-Coop/pallas.git", branch = "feat-aux-data" }
pallas-primitives = { git = "...Sulkta-Coop/pallas.git", branch = "feat-aux-data" }
pallas-traverse = { git = "...Sulkta-Coop/pallas.git", branch = "feat-aux-data" }
pallas-addresses = { git = "...Sulkta-Coop/pallas.git", branch = "feat-aux-data" }
pallas-wallet = { git = "...Sulkta-Coop/pallas.git", branch = "feat-aux-data" }
pallas-txbuilder = { git = "...Sulkta-Coop/pallas.git", branch = "feat-aux-data" }
All seven pallas crates we depend on are patched against the same commit so cargo's version graph resolves consistently.
Upstream PR status
Not yet submitted. Plan:
- ✅ Land the changes locally on the fork.
- ✅ Verify no upstream tests broken (
cargo test --workspacepasses — confirmed 2026-05-04). - ✅ Verify aldabra builds + tests against the fork (88 unit tests green).
- ☐ Open PR against
txpipe/pallason github.com. - ☐ Once upstream merges, drop our
[patch.crates-io].
The PR will probably need to be split into three separate PRs (one per change) for upstream review ergonomics:
pallas-txbuilder: thread auxiliary_data through StagingTransaction → Conway buildpallas-txbuilder: thread certificates through StagingTransaction → Conway buildpallas-txbuilder: thread voting_procedures through StagingTransaction → Conway buildpallas-addresses: pub fn new on StakeAddress
Change discipline going forward
If we add more fork-only patches (e.g. for upcoming withdrawals / voting_procedures / proposal_procedures TODOs), each goes in a separate commit on this branch so the eventual upstream PRs are clean to extract.
All commits on this branch must:
- Have a self-contained subject explaining what TODO it fills in.
- Pass the full pallas-txbuilder test suite (
cargo test -p pallas-txbuilder). - Add at least one positive + one negative test for the new code path.
- Not depend on Sulkta-specific naming or assumptions — these patches need to be upstream-mergeable as-is.