Support multiple payment outputs on claim and refund transactions - #162
Open
BullishNode wants to merge 4 commits into
Open
Support multiple payment outputs on claim and refund transactions#162BullishNode wants to merge 4 commits into
BullishNode wants to merge 4 commits into
Conversation
Adds `additional_outputs: Vec<(Address, u64)>` to LBtcSwapTx and BtcSwapTx with a `with_additional_outputs` setter. The primary `output_address` receives the remainder (input - fee - sum of additional outputs) and remains the first payment output. Additional outputs pay fixed amounts; amounts are summed with overflow checks and validated non-zero when the transaction is built. Liquid: claim and refund construction share a `create_payment_outputs` helper generalized to N blinded outputs. Each output gets its own asset blinding factor, surjection proof and rangeproof; the last payment output balances the blinding equation (ValueBlindingFactor::last) against the input and the explicit fee output. All destination addresses must be confidential. Output ordering is unchanged: [payments..., fee] for claims, [fee, payments...] for refunds. Bitcoin: claim and refund transactions build one output per destination, primary remainder at index 0. Wrappers: TransactionOptions::with_additional_outputs threads the extra destinations through construct_claim and construct_refund on both chains without breaking existing callers. The cooperative (MuSig key-path) flows are unchanged: they already sign whatever transaction create_claim/create_refund built, and the full serialized transaction is sent to Boltz for the partial signature. Tests: unit tests fabricate a blinded funding utxo locally and assert that multi-output Liquid claim and refund transactions pass verify_tx_amt_proofs (pedersen balance, rangeproofs, surjection proofs) and that every output unblinds to the expected value, asset and script_pubkey; Bitcoin tests cover claim and refund output composition and the insufficient-funds and zero-amount errors. Regtest integration tests broadcast a multi-output confidential claim (relative fee) and a multi-output Bitcoin claim and confirm node acceptance.
BullishNode
force-pushed
the
feat/multi-output-claims
branch
from
July 12, 2026 05:08
d7abd83 to
68cd501
Compare
BullishNode
marked this pull request as draft
July 12, 2026 16:30
Bitcoin construction no longer rejects zero-valued outputs: it enforces balance (overflow, overspend) but not dust or relay policy, which belongs to the broadcaster. Liquid now rejects a zero-valued primary output unconditionally with a clear protocol error instead of failing deep inside blinding (rangeproofs cannot prove less than 1 satoshi). Fix BtcSwapTx::new_claim_with_utxo silently discarding the result of its claim-address network check, and validate additional-output address strings in the wrappers: network on Bitcoin, confidentiality and address params on Liquid. Includes boundary unit tests (zero, one-satoshi, overflow, zero-primary), builder documentation, and a 0.5.0 changelog section for the source-breaking public field.
Extend the regtest suites through the wrapper path: reverse claims run a non-cooperative multi-output variant, submarine refunds and chain-swap claims pass additional outputs through TransactionOptions cooperatively, so Boltz partial-signs complete multi-output transactions on both chains. A shared assert_multi_output_tx helper verifies remainder math, output ordering, and unblinds Liquid outputs with the node's blinding keys. Add node-level broadcast coverage in txs.rs: multi-output Bitcoin and Liquid refunds, and a Bitcoin multi-output claim with a relative fee to show the added output weight is paid for.
BullishNode
force-pushed
the
feat/multi-output-claims
branch
from
July 12, 2026 19:39
79aba87 to
f33f680
Compare
Reverse and chain swap subcommands against production Boltz with a recovery state file written before any payment information is shown, a refund escape hatch for chain swaps, and a NONCOOP env toggle for script-path claims. Used to verify multi-output claims with real funds: reverse swap ZqxQ58hMEIxY (Liquid claim, 2 blinded outputs + fee) and chain swaps FkNCgU6FJzfA / 5RHJIphzQrkN (Bitcoin and Liquid claims), all claimed cooperatively.
BullishNode
force-pushed
the
feat/multi-output-claims
branch
from
July 12, 2026 19:46
f33f680 to
d3b8520
Compare
BullishNode
marked this pull request as ready for review
July 12, 2026 20:14
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.
What
Adds the ability to pay multiple outputs from a swap claim or refund transaction, on both Liquid and Bitcoin.
LBtcSwapTx/BtcSwapTxgainadditional_outputs: Vec<(Address, u64)>and awith_additional_outputs(...)setter.output_addressreceives the remainder (input − fee − Σ additional) and remains the first payment output (index 0 on claims; after the fee output on Liquid refunds, matching the existing refund output order). Additional outputs pay fixed amounts in the given order.TransactionOptions::with_additional_outputs(Vec<(String, u64)>)threads the destinations throughconstruct_claim/construct_refundon both chains without breaking existing callers (new private field withDefault).Validation policy
Construction enforces balance, not policy:
checked_add(a wrapping sum cannot bypass the insufficient-funds guard) and must fit ininput − fee.TxOut::RANGEPROOF_MIN_VALUE).new_claim_with_utxocomputed but discarded its network check), address params + confidentiality on Liquid.Liquid CT details
create_claim/create_refundshare acreate_payment_outputshelper generalized to N blinded outputs:ValueBlindingFactor::lastagainst the input and the explicit fee output;The cooperative (MuSig key-path) flows are untouched: they already sign whatever transaction was built and send the full serialized tx to Boltz for the partial signature, which commits to all outputs.
Tests
verify_tx_amt_proofsand every output unblinds to the expected value/asset/script_pubkey; boundary cases cover zero, one-satoshi,[u64::MAX, 1]overflow, zero-primary, insufficient funds and unblinded addresses on the appropriate chain.tests/txs.rs): multi-output claims and refunds on both chains are broadcast to elementsd/bitcoind and accepted, including a Bitcoin multi-output claim withFee::Relativeproving the added output weight is paid for.examples/multiout_battletest.rs), all claimed cooperatively:ZqxQ58hMEIxY→ Liquid claim0e845207…(2 blinded outputs + explicit fee, confirmed)FkNCgU6FJzfA→ Bitcoin claim7266da85…(24,343 + 5,000 sat outputs)5RHJIphzQrkN→ Liquid claim24a88229…All of
cargo fmt --check,make cargo-clippy,make wasm-clippy,make cargo-test(73 passed),make wasm-test,make cargo-regtest-test(13/13 flows) andmake wasm-regtest-test(7/7) pass.Compatibility notes
LBtcSwapTx/BtcSwapTxwith literal syntax; constructors initialize it to an empty vector and the wrapper API is backward compatible.input == fee(zero primary) is unchanged; Liquid previously failed the same case deep inside blinding and now fails it with a clear protocol error.additional_outputsyet — follow-up work.