Skip to main content

fedimint_server_ui/dashboard/modules/
walletv2.rs

1use maud::{Markup, html};
2
3// Function to render the Wallet v2 module UI section
4pub async fn render(wallet: &fedimint_walletv2_server::Wallet) -> Markup {
5    let network = wallet.network_ui();
6    let federation_wallet = wallet.federation_wallet_ui().await;
7    let consensus_block_count = wallet.consensus_block_count_ui().await;
8    let consensus_fee_rate = wallet.consensus_feerate_ui().await;
9    let send_fee = wallet.send_fee_ui().await;
10    let receive_fee = wallet.receive_fee_ui().await;
11    let pending_tx_chain = wallet.pending_tx_chain_ui().await;
12    let tx_chain = wallet.tx_chain_ui(20).await;
13    let recovery_keys = wallet.recovery_keys_ui().await;
14
15    let total_pending_vbytes = pending_tx_chain.iter().map(|info| info.vbytes).sum::<u64>();
16
17    let total_pending_fee = pending_tx_chain
18        .iter()
19        .map(|info| info.fee.to_sat())
20        .sum::<u64>();
21
22    html! {
23        div class="row gy-4 mt-2" {
24            div class="col-12" {
25                div class="card h-100" {
26                    div class="card-header dashboard-header" { "Wallet V2" }
27                    div class="card-body" {
28                        div class="mb-4" {
29                            table class="table" {
30                                tr {
31                                    th { "Network" }
32                                    td { (network) }
33                                }
34                                @if let Some(wallet) = federation_wallet {
35                                    tr {
36                                        th { "Value in Custody" }
37                                        td { (format!("{:.8} BTC", wallet.value.to_btc())) }
38                                    }
39                                    tr {
40                                        th { "Transaction Chain Tip" }
41                                        td {
42                                            a href={ "https://mempool.space/tx/" (wallet.outpoint.txid) } class="btn btn-sm btn-outline-primary" target="_blank" {
43                                                "mempool.space"
44                                            }
45                                        }
46                                    }
47                                }
48                                tr {
49                                    th { "Consensus Block Count" }
50                                    td { (consensus_block_count) }
51                                }
52                                tr {
53                                    th { "Consensus Fee Rate" }
54                                    td {
55                                        @if let Some(fee_rate) = consensus_fee_rate {
56                                            (fee_rate) " sat/vbyte"
57                                        } @else {
58                                            "No consensus fee rate available"
59                                        }
60                                    }
61                                }
62                                tr {
63                                    th { "Send Fee" }
64                                    td {
65                                        @if let Some(fee) = send_fee {
66                                            (fee.to_sat()) " sats"
67                                        } @else {
68                                            "No send fee available"
69                                        }
70                                    }
71                                }
72                                tr {
73                                    th { "Receive Fee" }
74                                    td {
75                                        @if let Some(fee) = receive_fee {
76                                            (fee.to_sat()) " sats"
77                                        } @else {
78                                            "No receive fee available"
79                                        }
80                                    }
81                                }
82                            }
83                        }
84
85
86                        @if !pending_tx_chain.is_empty() {
87                            div class="mb-4" {
88                                h5 { "Pending Transaction Chain" }
89                                @if consensus_block_count > pending_tx_chain.last().unwrap().created + 18 {
90                                    div class="alert alert-danger" role="alert" {
91                                        "Warning: Transaction has been pending for more than 18 blocks!"
92                                    }
93                                }
94
95                                table class="table" {
96                                    thead {
97                                        tr {
98                                            th { "Index" }
99                                            th { "Value in Custody" }
100                                            th { "Fee" }
101                                            th { "vBytes" }
102                                            th { "Feerate" }
103                                            th { "Age" }
104                                            th { "Transaction" }
105                                        }
106                                    }
107                                    tbody {
108                                        @for tx in pending_tx_chain{
109                                            tr {
110                                                td { (tx.index) }
111                                                td {
112                                                    @if tx.output >= tx.input {
113                                                        span class="text-success" { "+" (tx.output - tx.input) }
114                                                    } @else {
115                                                        span class="text-danger" { "-" (tx.input - tx.output) }
116                                                    }
117                                                }
118                                                td { (tx.fee.to_sat()) }
119                                                td { (tx.vbytes) }
120                                                td { (tx.feerate()) }
121                                                td { (consensus_block_count.saturating_sub(tx.created)) }
122                                                td {
123                                                    a href={ "https://mempool.space/tx/" (tx.txid) } class="btn btn-sm btn-outline-primary" target="_blank" {
124                                                        "mempool.space"
125                                                    }
126                                                }
127                                            }
128                                        }
129                                    }
130                                }
131
132                                div class="alert alert-info" role="alert" {
133                                    "Total feerate of pending chain: " strong { (total_pending_fee / total_pending_vbytes) " sat/vbyte" }
134                                }
135                            }
136                        }
137
138
139                        @if !tx_chain.is_empty() {
140                            div class="mb-4" {
141                                h5 { "Total Transaction Chain" }
142                                table class="table" {
143                                    thead {
144                                        tr {
145                                            th { "Index" }
146                                            th { "Value in Custody" }
147                                            th { "Fee" }
148                                            th { "vBytes" }
149                                            th { "Feerate" }
150                                            th { "Age" }
151                                            th { "Transaction" }
152                                        }
153                                    }
154                                    tbody {
155                                        @for tx in tx_chain {
156                                            tr {
157                                                td { (tx.index) }
158                                                td {
159                                                    @if tx.output >= tx.input {
160                                                        span class="text-success" { "+" (tx.output - tx.input) }
161                                                    } @else {
162                                                        span class="text-danger" { "-" (tx.input - tx.output) }
163                                                    }
164                                                }
165                                                td { (tx.fee.to_sat()) }
166                                                td { (tx.vbytes) }
167                                                td { (tx.feerate()) }
168                                                td { (consensus_block_count.saturating_sub(tx.created)) }
169                                                td {
170                                                    a href={ "https://mempool.space/tx/" (tx.txid) } class="btn btn-sm btn-outline-primary" target="_blank" {
171                                                        "mempool.space"
172                                                    }
173                                                }
174                                            }
175                                        }
176                                    }
177                                }
178                            }
179                        }
180
181                        @if let Some((recovery_public_keys, recovery_private_key)) = &recovery_keys {
182                            // Federation Shutdown accordion
183                            div class="accordion mt-4" id="shutdownAccordion" {
184                                div class="accordion-item" {
185                                    h2 class="accordion-header" {
186                                        button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#shutdownCollapse" aria-expanded="false" aria-controls="shutdownCollapse" {
187                                            "Federation Shutdown"
188                                        }
189                                    }
190                                    div id="shutdownCollapse" class="accordion-collapse collapse" data-bs-parent="#shutdownAccordion" {
191                                        div class="accordion-body" {
192                                            div class="alert alert-info mb-3" {
193                                                "To recover your remaining funds after decommissioning the federation, please go to the "
194                                                a href="https://recovery.fedimint.org" target="_blank" { "recovery tool" }
195                                                " and follow the instructions."
196                                            }
197
198                                            div class="alert alert-warning mb-3" {
199                                                "The recovery keys change with every transaction. All guardians must be fully synced before extracting keys, otherwise the keys will not match the current federation UTXO."
200                                            }
201
202                                            div class="mb-3" {
203                                                table class="table table-sm" {
204                                                    thead {
205                                                        tr {
206                                                            th { "Guardian" }
207                                                            th { "Public Key (hex)" }
208                                                        }
209                                                    }
210                                                    tbody {
211                                                        @for (peer, pk) in recovery_public_keys {
212                                                            tr {
213                                                                td { (peer) }
214                                                                td class="text-break" style="word-break: break-all; font-family: monospace;" { (pk) }
215                                                            }
216                                                        }
217                                                    }
218                                                }
219                                            }
220
221                                            div class="mb-3" {
222                                                p class="mb-2" { strong { "Your Private Key (WIF)" } }
223                                                div class="alert alert-danger text-break" style="word-break: break-all;" {
224                                                    (recovery_private_key)
225                                                }
226                                            }
227                                        }
228                                    }
229                                }
230                            }
231                        }
232                    }
233                }
234            }
235        }
236    }
237}