fedimint_server_ui/
wallet.rs

1use maud::{Markup, html};
2
3// Function to render the Wallet module UI section
4pub async fn render(wallet: &fedimint_wallet_server::Wallet) -> Markup {
5    let consensus_block_count = wallet.consensus_block_count_ui().await;
6    let consensus_fee_rate = wallet.consensus_feerate_ui().await;
7    let wallet_summary = wallet.get_wallet_summary_ui().await;
8
9    // Calculate total spendable balance
10    let total_spendable: u64 = wallet_summary
11        .spendable_utxos
12        .iter()
13        .map(|utxo| utxo.amount.to_sat())
14        .sum();
15
16    // Calculate pending outgoing (unsigned + unconfirmed)
17    let total_pending_outgoing: u64 = wallet_summary
18        .unsigned_peg_out_txos
19        .iter()
20        .chain(wallet_summary.unconfirmed_peg_out_txos.iter())
21        .map(|txo| txo.amount.to_sat())
22        .sum();
23
24    // Calculate pending incoming change
25    let total_pending_change: u64 = wallet_summary
26        .unsigned_change_utxos
27        .iter()
28        .chain(wallet_summary.unconfirmed_change_utxos.iter())
29        .map(|txo| txo.amount.to_sat())
30        .sum();
31
32    html! {
33        div class="row gy-4 mt-2" {
34            div class="col-12" {
35                div class="card h-100" {
36                    div class="card-header dashboard-header" { "Wallet" }
37                    div class="card-body" {
38                        // Blockchain status information
39                        div class="mb-4" {
40                            h5 { "Blockchain Status" }
41                            table class="table" {
42                                tr {
43                                    th { "Consensus Block Count" }
44                                    td { (consensus_block_count) }
45                                }
46                                tr {
47                                    th { "Consensus Fee Rate" }
48                                    td { (consensus_fee_rate.sats_per_kvb) " sats/kvB" }
49                                }
50                            }
51                        }
52
53                        // Wallet Balance Summary
54                        div class="mb-4" {
55                            h5 { "Balance Summary" }
56                            div class="row" {
57                                div class="col-md-4" {
58                                    div class="card bg-light mb-3" {
59                                        div class="card-body text-center" {
60                                            h6 class="card-title" { "Spendable Balance" }
61                                            p class="card-text fs-4" { (total_spendable) " sats" }
62                                        }
63                                    }
64                                }
65                                div class="col-md-4" {
66                                    div class="card bg-light mb-3" {
67                                        div class="card-body text-center" {
68                                            h6 class="card-title" { "Pending Outgoing" }
69                                            p class="card-text fs-4" { (total_pending_outgoing) " sats" }
70                                        }
71                                    }
72                                }
73                                div class="col-md-4" {
74                                    div class="card bg-light mb-3" {
75                                        div class="card-body text-center" {
76                                            h6 class="card-title" { "Pending Change" }
77                                            p class="card-text fs-4" { (total_pending_change) " sats" }
78                                        }
79                                    }
80                                }
81                            }
82                        }
83
84                        // UTXOs Breakdown
85                        div class="mb-4" {
86                            h5 { "UTXO Details" }
87
88                            // Tabs for different UTXO categories
89                            ul class="nav nav-tabs" id="walletTabs" role="tablist" {
90                                li class="nav-item" role="presentation" {
91                                    button class="nav-link active" id="spendable-tab" data-bs-toggle="tab"
92                                        data-bs-target="#spendable" type="button" role="tab" aria-controls="spendable"
93                                        aria-selected="true" {
94                                        "Spendable UTXOs "
95                                        span class="badge bg-primary" { (wallet_summary.spendable_utxos.len()) }
96                                    }
97                                }
98                                li class="nav-item" role="presentation" {
99                                    button class="nav-link" id="unsigned-tab" data-bs-toggle="tab"
100                                        data-bs-target="#unsigned" type="button" role="tab" aria-controls="unsigned"
101                                        aria-selected="false" {
102                                        "Unsigned Outputs "
103                                        span class="badge bg-secondary" {
104                                            (wallet_summary.unsigned_peg_out_txos.len() + wallet_summary.unsigned_change_utxos.len())
105                                        }
106                                    }
107                                }
108                                li class="nav-item" role="presentation" {
109                                    button class="nav-link" id="unconfirmed-tab" data-bs-toggle="tab"
110                                        data-bs-target="#unconfirmed" type="button" role="tab" aria-controls="unconfirmed"
111                                        aria-selected="false" {
112                                        "Unconfirmed Outputs "
113                                        span class="badge bg-warning" {
114                                            (wallet_summary.unconfirmed_peg_out_txos.len() + wallet_summary.unconfirmed_change_utxos.len())
115                                        }
116                                    }
117                                }
118                            }
119
120                            // Tab contents
121                            div class="tab-content pt-3" id="walletTabsContent" {
122                                div class="tab-pane fade show active" id="spendable" role="tabpanel" aria-labelledby="spendable-tab" {
123                                    @if wallet_summary.spendable_utxos.is_empty() {
124                                        p { "No spendable UTXOs available." }
125                                    } @else {
126                                        div class="table-responsive" {
127                                            table class="table table-sm" {
128                                                thead {
129                                                    tr {
130                                                        th { "Outpoint" }
131                                                        th { "Amount (sats)" }
132                                                    }
133                                                }
134                                                tbody {
135                                                    @for utxo in &wallet_summary.spendable_utxos {
136                                                        tr {
137                                                            td { (format!("{}:{}", utxo.outpoint.txid, utxo.outpoint.vout)) }
138                                                            td { (utxo.amount.to_sat()) }
139                                                        }
140                                                    }
141                                                }
142                                            }
143                                        }
144                                    }
145                                }
146
147                                div class="tab-pane fade" id="unsigned" role="tabpanel" aria-labelledby="unsigned-tab" {
148                                    @if wallet_summary.unsigned_peg_out_txos.is_empty() && wallet_summary.unsigned_change_utxos.is_empty() {
149                                        p { "No unsigned outputs waiting for signatures." }
150                                    } @else {
151                                        div class="table-responsive" {
152                                            table class="table table-sm" {
153                                                thead {
154                                                    tr {
155                                                        th { "Type" }
156                                                        th { "Outpoint" }
157                                                        th { "Amount (sats)" }
158                                                    }
159                                                }
160                                                tbody {
161                                                    @for txo in &wallet_summary.unsigned_peg_out_txos {
162                                                        tr {
163                                                            td { "Peg-out" }
164                                                            td { (format!("{}:{}", txo.outpoint.txid, txo.outpoint.vout)) }
165                                                            td { (txo.amount.to_sat()) }
166                                                        }
167                                                    }
168                                                    @for txo in &wallet_summary.unsigned_change_utxos {
169                                                        tr {
170                                                            td { "Change" }
171                                                            td { (format!("{}:{}", txo.outpoint.txid, txo.outpoint.vout)) }
172                                                            td { (txo.amount.to_sat()) }
173                                                        }
174                                                    }
175                                                }
176                                            }
177                                        }
178                                    }
179                                }
180
181                                div class="tab-pane fade" id="unconfirmed" role="tabpanel" aria-labelledby="unconfirmed-tab" {
182                                    @if wallet_summary.unconfirmed_peg_out_txos.is_empty() && wallet_summary.unconfirmed_change_utxos.is_empty() {
183                                        p { "No unconfirmed outputs waiting for blockchain confirmation." }
184                                    } @else {
185                                        div class="table-responsive" {
186                                            table class="table table-sm" {
187                                                thead {
188                                                    tr {
189                                                        th { "Type" }
190                                                        th { "Outpoint" }
191                                                        th { "Amount (sats)" }
192                                                    }
193                                                }
194                                                tbody {
195                                                    @for txo in &wallet_summary.unconfirmed_peg_out_txos {
196                                                        tr {
197                                                            td { "Peg-out" }
198                                                            td { (format!("{}:{}", txo.outpoint.txid, txo.outpoint.vout)) }
199                                                            td { (txo.amount.to_sat()) }
200                                                        }
201                                                    }
202                                                    @for txo in &wallet_summary.unconfirmed_change_utxos {
203                                                        tr {
204                                                            td { "Change" }
205                                                            td { (format!("{}:{}", txo.outpoint.txid, txo.outpoint.vout)) }
206                                                            td { (txo.amount.to_sat()) }
207                                                        }
208                                                    }
209                                                }
210                                            }
211                                        }
212                                    }
213                                }
214                            }
215                        }
216                    }
217                }
218            }
219        }
220    }
221}