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}