@@ -18,7 +18,7 @@ use crate::{
1818 print_payment_method, print_premium, print_required_amount, print_section_header,
1919 print_success_message, print_trade_index,
2020 } ,
21- util:: save_order,
21+ util:: { fetch_bond_claim_window_days , save_order} ,
2222} ;
2323use serde_json;
2424
@@ -98,14 +98,80 @@ fn handle_pay_bond_invoice_display(order: &Option<mostro_core::order::SmallOrder
9898 println ! ( ) ;
9999}
100100
101+ /// Render the forfeit deadline of a bond payout request.
102+ ///
103+ /// The protocol mandates computing it from the on-wire `slashed_at` anchor
104+ /// (never the local receive time):
105+ /// `deadline = slashed_at + bond_payout_claim_window_days * 86_400`. The claim
106+ /// window comes from the node's kind-38385 info event and may be unavailable.
107+ fn format_bond_forfeit_deadline ( slashed_at : i64 , claim_window_days : Option < i64 > ) -> String {
108+ let slashed = format_timestamp ( slashed_at) ;
109+ match claim_window_days {
110+ Some ( days) => {
111+ let deadline_ts = slashed_at. saturating_add ( days. saturating_mul ( 86_400 ) ) ;
112+ format ! (
113+ "Slashed at {} — forfeit deadline {} ({} day claim window)" ,
114+ slashed,
115+ format_timestamp( deadline_ts) ,
116+ days
117+ )
118+ }
119+ None => format ! (
120+ "Slashed at {} — claim window unknown (Mostro info event unavailable)" ,
121+ slashed
122+ ) ,
123+ }
124+ }
125+
126+ /// Display an inbound `add-bond-invoice` request (Mostro → non-slashed
127+ /// counterparty): the order context plus the locally-rendered forfeit deadline.
128+ fn handle_add_bond_invoice_request_display (
129+ req : & BondPayoutRequest ,
130+ claim_window_days : Option < i64 > ,
131+ ) {
132+ print_section_header ( "🪙 Bond Payout Invoice Requested" ) ;
133+ if let Some ( order_id) = req. order . id {
134+ println ! ( "📋 Order ID: {}" , order_id) ;
135+ }
136+ print_required_amount ( req. order . amount ) ;
137+ print_fiat_code ( & req. order . fiat_code ) ;
138+ println ! ( "💵 Fiat Amount: {}" , req. order. fiat_amount) ;
139+ print_payment_method ( & req. order . payment_method ) ;
140+ println ! (
141+ "⏰ {}" ,
142+ format_bond_forfeit_deadline( req. slashed_at, claim_window_days)
143+ ) ;
144+ println ! ( ) ;
145+ println ! ( "💡 A bond on this trade was slashed; you can claim your share." ) ;
146+ println ! ( "💡 Reply before the deadline with a Lightning invoice for the amount above:" ) ;
147+ println ! (
148+ " mostro-cli addbondinvoice -o {} -i <bolt11>" ,
149+ req. order
150+ . id
151+ . map( |x| x. to_string( ) )
152+ . unwrap_or_else( || "<order-id>" . to_string( ) )
153+ ) ;
154+ println ! ( ) ;
155+ }
156+
101157/// Format payload details for DM table display
102- fn format_payload_details ( payload : & Payload , action : & Action ) -> String {
158+ fn format_payload_details (
159+ payload : & Payload ,
160+ action : & Action ,
161+ claim_window_days : Option < i64 > ,
162+ ) -> String {
103163 match payload {
104164 Payload :: TextMessage ( t) => format ! ( "✉️ {}" , t) ,
105165 Payload :: PaymentRequest ( _, inv, _) => {
106166 // For invoices, show the full invoice without truncation
107167 format ! ( "⚡ Lightning Invoice:\n {}" , inv)
108168 }
169+ Payload :: BondPayoutRequest ( req) => format ! (
170+ "🪙 Bond payout request: {} sats ({})\n ⏰ {}" ,
171+ req. order. amount,
172+ req. order. fiat_code,
173+ format_bond_forfeit_deadline( req. slashed_at, claim_window_days)
174+ ) ,
109175 Payload :: Dispute ( id, _) => format ! ( "⚖️ Dispute ID: {}" , id) ,
110176 Payload :: Order ( o) if * action == Action :: NewOrder => format ! (
111177 "🆕 New Order: {} {} sats ({})" ,
@@ -537,6 +603,20 @@ pub async fn print_commands_results(message: &MessageKind, ctx: &Context) -> Res
537603 print_success_message ( "Order saved successfully!" ) ;
538604 Ok ( ( ) )
539605 }
606+ // mostro-core 0.11.3: bond payout invoice request sent to the
607+ // non-slashed counterparty after a bond is slashed. Reply with
608+ // `add-bond-invoice` carrying a bolt11 for your share.
609+ Action :: AddBondInvoice => match & message. payload {
610+ Some ( Payload :: BondPayoutRequest ( req) ) => {
611+ let claim_window_days = fetch_bond_claim_window_days ( ctx) . await ;
612+ handle_add_bond_invoice_request_display ( req, claim_window_days) ;
613+ Ok ( ( ) )
614+ }
615+ other => Err ( anyhow:: anyhow!(
616+ "AddBondInvoice expected Payload::BondPayoutRequest, got: {:?}" ,
617+ other
618+ ) ) ,
619+ } ,
540620 Action :: CantDo => {
541621 println ! ( "❌ Action Cannot Be Completed" ) ;
542622 println ! ( "═══════════════════════════════════════" ) ;
@@ -871,6 +951,7 @@ pub async fn parse_dm_events(
871951pub async fn print_direct_messages (
872952 dm : & [ ( Message , u64 , PublicKey ) ] ,
873953 mostro_pubkey : Option < PublicKey > ,
954+ claim_window_days : Option < i64 > ,
874955) -> Result < ( ) > {
875956 if dm. is_empty ( ) {
876957 println ! ( ) ;
@@ -896,6 +977,7 @@ pub async fn print_direct_messages(
896977 Action :: NewOrder => "🆕" ,
897978 Action :: AddInvoice | Action :: PayInvoice => "⚡" ,
898979 Action :: PayBondInvoice => "🪙" ,
980+ Action :: AddBondInvoice => "💰" ,
899981 Action :: FiatSent | Action :: FiatSentOk => "💸" ,
900982 Action :: Release | Action :: Released => "🔓" ,
901983 Action :: Cancel | Action :: Canceled => "🚫" ,
@@ -927,7 +1009,7 @@ pub async fn print_direct_messages(
9271009
9281010 // Print details with proper formatting
9291011 if let Some ( payload) = & inner. payload {
930- let details = format_payload_details ( payload, & inner. action ) ;
1012+ let details = format_payload_details ( payload, & inner. action , claim_window_days ) ;
9311013 println ! ( "📝 Details:" ) ;
9321014 for line in details. lines ( ) {
9331015 println ! ( " {}" , line) ;
0 commit comments