@@ -140,6 +140,10 @@ type ChanCloseCfg struct {
140140 // FeeEstimator is used to estimate the absolute starting co-op close
141141 // fee.
142142 FeeEstimator CoopFeeEstimator
143+
144+ // AuxCloser is an optional interface that can be used to modify the
145+ // way the co-op close process proceeds.
146+ AuxCloser fn.Option [AuxChanCloser ]
143147}
144148
145149// ChanCloser is a state machine that handles the cooperative channel closure
@@ -215,6 +219,20 @@ type ChanCloser struct {
215219 // we use to handle a specific race condition caused by the independent
216220 // message processing queues.
217221 cachedClosingSigned fn.Option [lnwire.ClosingSigned ]
222+
223+ // localCloseOutput is the local output on the closing transaction that
224+ // the local party should be paid to. This will only be populated if the
225+ // local balance isn't dust.
226+ localCloseOutput fn.Option [CloseOutput ]
227+
228+ // remoteCloseOutput is the remote output on the closing transaction
229+ // that the remote party should be paid to. This will only be populated
230+ // if the remote balance isn't dust.
231+ remoteCloseOutput fn.Option [CloseOutput ]
232+
233+ // auxOutputs are the optional additional outputs that might be added to
234+ // the closing transaction.
235+ auxOutputs fn.Option [AuxCloseOutputs ]
218236}
219237
220238// calcCoopCloseFee computes an "ideal" absolute co-op close fee given the
@@ -295,13 +313,13 @@ func (c *ChanCloser) initFeeBaseline() {
295313 // Depending on if a balance ends up being dust or not, we'll pass a
296314 // nil TxOut into the EstimateFee call which can handle it.
297315 var localTxOut , remoteTxOut * wire.TxOut
298- if ! c .cfg .Channel .LocalBalanceDust () {
316+ if isDust , _ := c .cfg .Channel .LocalBalanceDust (); ! isDust {
299317 localTxOut = & wire.TxOut {
300318 PkScript : c .localDeliveryScript ,
301319 Value : 0 ,
302320 }
303321 }
304- if ! c .cfg .Channel .RemoteBalanceDust () {
322+ if isDust , _ := c .cfg .Channel .RemoteBalanceDust (); ! isDust {
305323 remoteTxOut = & wire.TxOut {
306324 PkScript : c .remoteDeliveryScript ,
307325 Value : 0 ,
@@ -337,6 +355,30 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
337355 // desired closing script.
338356 shutdown := lnwire .NewShutdown (c .cid , c .localDeliveryScript )
339357
358+ // At this point, we'll check to see if we have any custom records to
359+ // add to the shutdown message.
360+ err := fn .MapOptionZ (c .cfg .AuxCloser , func (a AuxChanCloser ) error {
361+ shutdownCustomRecords , err := a .ShutdownBlob (AuxShutdownReq {
362+ ChanPoint : c .chanPoint ,
363+ ShortChanID : c .cfg .Channel .ShortChanID (),
364+ Initiator : c .cfg .Channel .IsInitiator (),
365+ CommitBlob : c .cfg .Channel .LocalCommitmentBlob (),
366+ FundingBlob : c .cfg .Channel .FundingBlob (),
367+ })
368+ if err != nil {
369+ return err
370+ }
371+
372+ shutdownCustomRecords .WhenSome (func (cr lnwire.CustomRecords ) {
373+ shutdown .CustomRecords = cr
374+ })
375+
376+ return nil
377+ })
378+ if err != nil {
379+ return nil , err
380+ }
381+
340382 // If this is a taproot channel, then we'll need to also generate a
341383 // nonce that'll be used sign the co-op close transaction offer.
342384 if c .cfg .Channel .ChanType ().IsTaproot () {
@@ -370,11 +412,22 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
370412 shutdownInfo := channeldb .NewShutdownInfo (
371413 c .localDeliveryScript , c .closer .IsLocal (),
372414 )
373- err : = c .cfg .Channel .MarkShutdownSent (shutdownInfo )
415+ err = c .cfg .Channel .MarkShutdownSent (shutdownInfo )
374416 if err != nil {
375417 return nil , err
376418 }
377419
420+ // We'll track our local close output, even if it's dust in BTC terms,
421+ // it might still carry value in custom channel terms.
422+ _ , dustAmt := c .cfg .Channel .LocalBalanceDust ()
423+ localBalance , _ := c .cfg .Channel .CommitBalances ()
424+ c .localCloseOutput = fn .Some (CloseOutput {
425+ Amt : localBalance ,
426+ DustLimit : dustAmt ,
427+ PkScript : c .localDeliveryScript ,
428+ ShutdownRecords : shutdown .CustomRecords ,
429+ })
430+
378431 return shutdown , nil
379432}
380433
@@ -444,6 +497,21 @@ func (c *ChanCloser) NegotiationHeight() uint32 {
444497 return c .negotiationHeight
445498}
446499
500+ // LocalCloseOutput returns the local close output.
501+ func (c * ChanCloser ) LocalCloseOutput () fn.Option [CloseOutput ] {
502+ return c .localCloseOutput
503+ }
504+
505+ // RemoteCloseOutput returns the remote close output.
506+ func (c * ChanCloser ) RemoteCloseOutput () fn.Option [CloseOutput ] {
507+ return c .remoteCloseOutput
508+ }
509+
510+ // AuxOutputs returns optional extra outputs.
511+ func (c * ChanCloser ) AuxOutputs () fn.Option [AuxCloseOutputs ] {
512+ return c .auxOutputs
513+ }
514+
447515// validateShutdownScript attempts to match and validate the script provided in
448516// our peer's shutdown message with the upfront shutdown script we have on
449517// record. For any script specified, we also make sure it matches our
@@ -503,6 +571,17 @@ func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) (
503571
504572 noShutdown := fn .None [lnwire.Shutdown ]()
505573
574+ // We'll track their remote close output, even if it's dust in BTC
575+ // terms, it might still carry value in custom channel terms.
576+ _ , dustAmt := c .cfg .Channel .RemoteBalanceDust ()
577+ _ , remoteBalance := c .cfg .Channel .CommitBalances ()
578+ c .remoteCloseOutput = fn .Some (CloseOutput {
579+ Amt : remoteBalance ,
580+ DustLimit : dustAmt ,
581+ PkScript : msg .Address ,
582+ ShutdownRecords : msg .CustomRecords ,
583+ })
584+
506585 switch c .state {
507586 // If we're in the close idle state, and we're receiving a channel
508587 // closure related message, then this indicates that we're on the
@@ -850,6 +929,25 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
850929 }
851930 }
852931
932+ // Before we complete the cooperative close, we'll see if we
933+ // have any extra aux options.
934+ c .auxOutputs , err = c .auxCloseOutputs (remoteProposedFee )
935+ if err != nil {
936+ return noClosing , err
937+ }
938+ c .auxOutputs .WhenSome (func (outs AuxCloseOutputs ) {
939+ closeOpts = append (
940+ closeOpts , lnwallet .WithExtraCloseOutputs (
941+ outs .ExtraCloseOutputs ,
942+ ),
943+ )
944+ closeOpts = append (
945+ closeOpts , lnwallet .WithCustomCoopSort (
946+ outs .CustomSort ,
947+ ),
948+ )
949+ })
950+
853951 closeTx , _ , err := c .cfg .Channel .CompleteCooperativeClose (
854952 localSig , remoteSig , c .localDeliveryScript ,
855953 c .remoteDeliveryScript , remoteProposedFee , closeOpts ... ,
@@ -859,6 +957,32 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
859957 }
860958 c .closingTx = closeTx
861959
960+ // If there's an aux chan closer, then we'll finalize with it
961+ // before we write to disk.
962+ err = fn .MapOptionZ (
963+ c .cfg .AuxCloser , func (aux AuxChanCloser ) error {
964+ channel := c .cfg .Channel
965+ //nolint:lll
966+ req := AuxShutdownReq {
967+ ChanPoint : c .chanPoint ,
968+ ShortChanID : c .cfg .Channel .ShortChanID (),
969+ Initiator : channel .IsInitiator (),
970+ CommitBlob : channel .LocalCommitmentBlob (),
971+ FundingBlob : channel .FundingBlob (),
972+ }
973+ desc := AuxCloseDesc {
974+ AuxShutdownReq : req ,
975+ LocalCloseOutput : c .localCloseOutput ,
976+ RemoteCloseOutput : c .remoteCloseOutput ,
977+ }
978+
979+ return aux .FinalizeClose (desc , closeTx )
980+ },
981+ )
982+ if err != nil {
983+ return noClosing , err
984+ }
985+
862986 // Before publishing the closing tx, we persist it to the
863987 // database, such that it can be republished if something goes
864988 // wrong.
@@ -908,9 +1032,45 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
9081032 }
9091033}
9101034
1035+ // auxCloseOutputs returns any additional outputs that should be used when
1036+ // closing the channel.
1037+ func (c * ChanCloser ) auxCloseOutputs (
1038+ closeFee btcutil.Amount ) (fn.Option [AuxCloseOutputs ], error ) {
1039+
1040+ var closeOuts fn.Option [AuxCloseOutputs ]
1041+ err := fn .MapOptionZ (c .cfg .AuxCloser , func (aux AuxChanCloser ) error {
1042+ req := AuxShutdownReq {
1043+ ChanPoint : c .chanPoint ,
1044+ ShortChanID : c .cfg .Channel .ShortChanID (),
1045+ Initiator : c .cfg .Channel .IsInitiator (),
1046+ CommitBlob : c .cfg .Channel .LocalCommitmentBlob (),
1047+ FundingBlob : c .cfg .Channel .FundingBlob (),
1048+ }
1049+ outs , err := aux .AuxCloseOutputs (AuxCloseDesc {
1050+ AuxShutdownReq : req ,
1051+ CloseFee : closeFee ,
1052+ CommitFee : c .cfg .Channel .CommitFee (),
1053+ LocalCloseOutput : c .localCloseOutput ,
1054+ RemoteCloseOutput : c .remoteCloseOutput ,
1055+ })
1056+ if err != nil {
1057+ return err
1058+ }
1059+
1060+ closeOuts = outs
1061+
1062+ return nil
1063+ })
1064+ if err != nil {
1065+ return closeOuts , err
1066+ }
1067+
1068+ return closeOuts , nil
1069+ }
1070+
9111071// proposeCloseSigned attempts to propose a new signature for the closing
912- // transaction for a channel based on the prior fee negotiations and our current
913- // compromise fee.
1072+ // transaction for a channel based on the prior fee negotiations and our
1073+ // current compromise fee.
9141074func (c * ChanCloser ) proposeCloseSigned (fee btcutil.Amount ) (
9151075 * lnwire.ClosingSigned , error ) {
9161076
@@ -928,6 +1088,26 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (
9281088 }
9291089 }
9301090
1091+ // We'll also now see if the aux chan closer has any additional options
1092+ // for the closing purpose.
1093+ c .auxOutputs , err = c .auxCloseOutputs (fee )
1094+ if err != nil {
1095+ return nil , err
1096+ }
1097+ c .auxOutputs .WhenSome (func (outs AuxCloseOutputs ) {
1098+ closeOpts = append (
1099+ closeOpts , lnwallet .WithExtraCloseOutputs (
1100+ outs .ExtraCloseOutputs ,
1101+ ),
1102+ )
1103+ closeOpts = append (
1104+ closeOpts , lnwallet .WithCustomCoopSort (
1105+ outs .CustomSort ,
1106+ ),
1107+ )
1108+ })
1109+
1110+ // With all our options added, we'll attempt to co-op close now.
9311111 rawSig , _ , _ , err := c .cfg .Channel .CreateCloseProposal (
9321112 fee , c .localDeliveryScript , c .remoteDeliveryScript ,
9331113 closeOpts ... ,
0 commit comments