commit e877771733cb32c0fa64380efa35f094ff58eb6e
parent 3d6d8e4333c8bcea20cb6c6368bb694d27dd45da
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Wed, 22 Jul 2026 15:50:23 +0200
Validate wallet send form
Diffstat:
2 files changed, 72 insertions(+), 14 deletions(-)
diff --git a/assets/mivora-ui.js b/assets/mivora-ui.js
@@ -22,7 +22,7 @@ window.mivoraApp = function mivoraApp() {
burnAmountDraft: 0,
burnAmountDirty: false,
transferTo: "",
- transferAmount: 25,
+ transferAmount: null,
transferFee: 1,
peerAddress: "",
showBurnTransactions: false,
@@ -425,8 +425,8 @@ window.mivoraApp = function mivoraApp() {
async sendTransfer() {
try {
- const amount = this.transferAmount || 0;
- const fee = this.transferFee || 0;
+ const amount = this.transferAmount;
+ const fee = this.transferFee;
const recipient = this.short(this.transferTo);
await this.postForm(
"/api/transfer",
diff --git a/src/adapters/http.rs b/src/adapters/http.rs
@@ -18,7 +18,7 @@ use tokio::{net::TcpListener, sync::Mutex};
use crate::{
adapters::{config_store, config_store::UiConfig, p2p::GossipNetwork, wallet_store},
app::{NodeStatus, PeerInfo, SharedNode, SharedPeerBook},
- domain::{Amount, Block, DEFAULT_TRANSACTION_FEE, Transaction},
+ domain::{Amount, Block, Transaction},
};
const EXPLORER_LIMIT: usize = 50;
@@ -376,13 +376,11 @@ fn dev_seed_verify_bypass_allowed(env_present: bool) -> bool {
}
async fn transfer(state: &HttpState, form: TransferForm) -> Result<()> {
+ let (to, amount, fee) = validate_transfer_form(form)?;
+
let result = {
let mut node = state.node.lock().await;
- let result = node.transfer_with_fee(
- form.to,
- form.amount,
- form.fee.unwrap_or(DEFAULT_TRANSACTION_FEE),
- );
+ let result = node.transfer_with_fee(to, amount, fee);
let outbox = node.drain_outbox();
(result, outbox)
};
@@ -393,6 +391,18 @@ async fn transfer(state: &HttpState, form: TransferForm) -> Result<()> {
}
}
+fn validate_transfer_form(form: TransferForm) -> Result<(String, Amount, Amount)> {
+ let to = form.to.trim();
+ if to.is_empty() {
+ bail!("recipient is required");
+ }
+ if form.amount == 0 {
+ bail!("amount must be greater than zero");
+ }
+ let fee = form.fee.context("fee is required")?;
+ Ok((to.to_string(), form.amount, fee))
+}
+
fn action_json(result: Result<()>) -> Json<ActionResponse> {
match result {
Ok(_) => Json(ActionResponse {
@@ -567,7 +577,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
.block-card { flex-basis: 108px; }
}
</style>
- <script defer src="/assets/mivora-ui.js?v=29"></script>
+ <script defer src="/assets/mivora-ui.js?v=31"></script>
<script defer src="/assets/alpine.min.js"></script>
</head>
<body x-data="mivoraApp()" x-init="init()" x-cloak>
@@ -613,9 +623,9 @@ const INDEX_HTML: &str = r#"<!doctype html>
<div class="panel">
<h3>Send</h3>
<form @submit.prevent="sendTransfer">
- <label>Recipient<input x-model="transferTo" autocomplete="off"></label>
- <label>Amount<input x-model.number="transferAmount" type="number" min="1"></label>
- <label>Fee<input x-model.number="transferFee" type="number" min="0"></label>
+ <label>Recipient<input x-model="transferTo" autocomplete="off" required></label>
+ <label>Amount<input x-model.number="transferAmount" type="number" min="1" required></label>
+ <label>Fee<input x-model.number="transferFee" type="number" min="0" required></label>
<button class="primary" type="submit">Send</button>
</form>
</div>
@@ -890,7 +900,10 @@ mod tests {
use crate::adapters::{config_store, config_store::UiConfig};
- use super::{dev_seed_verify_bypass_allowed, persist_burn_per_block_config};
+ use super::{
+ TransferForm, dev_seed_verify_bypass_allowed, persist_burn_per_block_config,
+ validate_transfer_form,
+ };
#[test]
fn dev_seed_verify_bypass_requires_env_flag() {
@@ -916,4 +929,49 @@ mod tests {
assert_eq!(config.burn_per_block, 50);
}
+
+ #[test]
+ fn transfer_form_requires_recipient_amount_and_fee() {
+ let error = validate_transfer_form(TransferForm {
+ to: " ".to_string(),
+ amount: 1,
+ fee: Some(1),
+ })
+ .unwrap_err();
+ assert!(error.to_string().contains("recipient is required"));
+
+ let error = validate_transfer_form(TransferForm {
+ to: "abc".to_string(),
+ amount: 0,
+ fee: Some(1),
+ })
+ .unwrap_err();
+ assert!(
+ error
+ .to_string()
+ .contains("amount must be greater than zero")
+ );
+
+ let error = validate_transfer_form(TransferForm {
+ to: "abc".to_string(),
+ amount: 1,
+ fee: None,
+ })
+ .unwrap_err();
+ assert!(error.to_string().contains("fee is required"));
+ }
+
+ #[test]
+ fn transfer_form_trims_recipient() {
+ let (to, amount, fee) = validate_transfer_form(TransferForm {
+ to: " abc ".to_string(),
+ amount: 2,
+ fee: Some(3),
+ })
+ .unwrap();
+
+ assert_eq!(to, "abc");
+ assert_eq!(amount, 2);
+ assert_eq!(fee, 3);
+ }
}