Skip to content

feat: add contract_flags support to redirect refund to accepter#157

Merged
bennyhodl merged 1 commit into
bennyhodl:masterfrom
matthewjablack:feat/contract-flags-refund-to-accepter
May 31, 2026
Merged

feat: add contract_flags support to redirect refund to accepter#157
bennyhodl merged 1 commit into
bennyhodl:masterfrom
matthewjablack:feat/contract-flags-refund-to-accepter

Conversation

@matthewjablack

@matthewjablack matthewjablack commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Use bit 0 of the existing contract_flags byte on DlcOffer to signal that all refund transaction
proceeds go to the accepter. When the flag is set, the offerer's refund output is Amount::ZERO
(dust-discarded) and the accepter receives total_collateral.

Changes

  • Add REFUND_TO_ACCEPTER_FLAG constant (0x01) in dlc/src/lib.rs
  • Add contract_flags: u8 parameter to create_dlc_transactions, create_spliced_dlc_transactions, and
    create_cets_and_refund_tx
  • Modify refund output construction to redirect all funds to accepter when flag is set
  • Add contract_flags: u8 field to OfferedContract struct with serde default for backwards compatibility
  • Wire contract_flags through OfferDlc ↔ OfferedContract conversions
  • Thread contract_flags through channel transaction creation (create_channel_transactions,
    create_renewal_channel_transactions)
  • Propagate contract_flags from offered_contract at all call sites in contract_updater.rs and
    channel_updater.rs
  • Add contract_flags to OfferedContract binary serialization
  • Propagate contract_flags from offered_contract at all call sites in contract_updater.rs and
    channel_updater.rs
  • Add contract_flags to OfferedContract binary serialization
  • Add unit test verifying single-output refund TX pays total collateral to accepter's payout SPK
  • Update existing tests, benchmarks, and compatibility tests with default 0 flag create_renewal_channel_transactions)
  • Propagate contract_flags from offered_contract at all call sites in contract_updater.rs and
    channel_updater.rs
  • Add contract_flags to OfferedContract binary serialization
  • Add unit test verifying single-output refund TX pays total collateral to accepter's payout SPK
  • Update existing tests, benchmarks, and compatibility tests with default 0 flag

@matthewjablack matthewjablack changed the title feat: use contract_flags bit 0 to redirect refund to accepter feat: add contract_flags support to redirect refund to accepter May 26, 2026
@matthewjablack matthewjablack force-pushed the feat/contract-flags-refund-to-accepter branch from ec8f65c to 73dfb81 Compare May 26, 2026 02:42

@bennyhodl bennyhodl left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment, please rebase this to 1 commit

Comment thread dlc/src/lib.rs Outdated
@bennyhodl

Copy link
Copy Markdown
Owner

Offeror can't originate the redirect flag — only honor an inbound one

The accepter side is fully wired: an inbound OfferDlc carries contract_flags through OfferedContract::try_from_offer_dlc (offered_contract.rs:164) into the refund tx, and both parties build identical outputs so signatures validate. 👍

But there's no path for this node to originate the flag as the offeror. OfferedContract::new hardcodes contract_flags: 0 (offered_contract.rs:126) and nothing mutates it afterward — I checked every write site, and the only non-zero source in the codebase is the inbound wire value. So send_offer / send_offer_with_announcements can never set bit 0. That makes the feature one-directional.

For bi-directional behavior, plumb the flag in through the offer input — e.g. add it to ContractInput:

pub struct ContractInput {
    pub offer_collateral: Amount,
    pub accept_collateral: Amount,
    pub fee_rate: u64,
    #[serde(default)]
    pub contract_flags: u8,
    pub contract_infos: Vec<ContractInputInfo>,
}

and read it in OfferedContract::new:

contract_flags: contract.contract_flags,

#[serde(default)] keeps JSON consumers backward-compatible (they only set it when redirecting); in-repo struct literals just need the new field.

Two related notes worth deciding on, even if out of scope here:

  • Channels can't use the flag. Channel offers (channel_updater.rs:112) and renewals (:1161, and on_renew_offer:1251) all build with contract_flags: 0, and RenewOffer has no field for it. Consistent on both sides, so no break — just unsupported. Worth a doc note or a follow-up.
  • No validation that bit 0 is sane for the contract. Redirecting all refund proceeds to the accepter with a non-zero offer collateral means the offeror forfeits their refund. Might be worth asserting/intending that explicitly (it's the single-funded use case).

@matthewjablack matthewjablack force-pushed the feat/contract-flags-refund-to-accepter branch from 73dfb81 to 7187600 Compare May 27, 2026 21:40
@matthewjablack matthewjablack force-pushed the feat/contract-flags-refund-to-accepter branch from 7187600 to 735ded0 Compare May 27, 2026 22:38
@matthewjablack

Copy link
Copy Markdown
Contributor Author

The force-push 735ded0 addresses both code issues:

  1. checked_add!total_collateral — fixed at dlc/src/lib.rs:651
  2. Offeror can now originate the flag — added contract_flags: u8 to ContractInput with #[serde(default)], and OfferedContract::new reads contract.contract_flags instead of hardcoding 0. Exactly as you suggested.

On the two follow-up notes:

  • Channels: Agreed, out of scope for now. Channel offers consistently hardcode contract_flags: 0 — no break, just unsupported.
  • Validation on bit 0: This is intentionally valid with non-zero offer collateral. The use case is loans — the borrower (offerer) posts all BTC collateral, and the lender (accepter) has 0 collateral. The loan is disbursed off-chain, so on refund the lender should receive everything (the collateral). Non-zero offer collateral with bit 0 set is the designed behavior, not an edge case to guard against.

@matthewjablack matthewjablack requested a review from bennyhodl May 28, 2026 17:09
@bennyhodl bennyhodl merged commit 67f8d7a into bennyhodl:master May 31, 2026
59 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.

2 participants