2026-06-13 21:15:03 +02:00
|
|
|
using System;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
|
using NBitcoin;
|
2026-06-15 10:19:24 +02:00
|
|
|
using PalladiumWallet.App.Services;
|
2026-06-13 21:15:03 +02:00
|
|
|
using PalladiumWallet.Core.Chain;
|
|
|
|
|
using PalladiumWallet.Core.Net;
|
|
|
|
|
using PalladiumWallet.Core.Wallet;
|
|
|
|
|
|
|
|
|
|
namespace PalladiumWallet.App.ViewModels;
|
|
|
|
|
|
|
|
|
|
public partial class MainWindowViewModel
|
|
|
|
|
{
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
private string sendTo = "";
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
private string sendAmount = "";
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
private string sendFeeRate = "1";
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
private bool sendAll;
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
private string sendPreview = "";
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
private bool hasPendingSend;
|
|
|
|
|
|
2026-06-15 10:19:24 +02:00
|
|
|
[RelayCommand]
|
|
|
|
|
private async Task ScanQr()
|
|
|
|
|
{
|
|
|
|
|
if (PlatformServices.ScanQrAsync is not { } scan) return;
|
|
|
|
|
var raw = await scan();
|
|
|
|
|
if (string.IsNullOrWhiteSpace(raw)) return;
|
2026-06-16 14:40:06 +02:00
|
|
|
// Handle URIs like "palladium:ADDRESS?amount=X" — extract address only
|
2026-06-15 10:19:24 +02:00
|
|
|
var address = raw.Contains(':') ? raw.Split(':')[1] : raw;
|
|
|
|
|
if (address.Contains('?')) address = address.Split('?')[0];
|
|
|
|
|
SendTo = address.Trim();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 21:15:03 +02:00
|
|
|
[RelayCommand]
|
|
|
|
|
private async Task PrepareSend()
|
|
|
|
|
{
|
|
|
|
|
if (_account is null || _doc?.Cache is null)
|
|
|
|
|
{
|
|
|
|
|
SendPreview = "Sincronizza prima di inviare.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_lastTransactions is null)
|
|
|
|
|
{
|
|
|
|
|
SendPreview = "Connettiti al server e sincronizza prima di inviare.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var destination = BitcoinAddress.Create(SendTo.Trim(), PalladiumNetworks.For(Net));
|
|
|
|
|
long amount = 0;
|
|
|
|
|
if (!SendAll && !CoinAmount.TryParseIn(SendAmount, _config.Unit, out amount))
|
|
|
|
|
{
|
|
|
|
|
SendPreview = "Importo non valido.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!decimal.TryParse(SendFeeRate, out var feeRate) || feeRate <= 0)
|
|
|
|
|
{
|
|
|
|
|
SendPreview = "Fee rate non valido.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_pendingSend = new TransactionFactory(_account).Build(
|
|
|
|
|
_doc.Cache.Utxos, _lastTransactions, destination, amount, feeRate,
|
|
|
|
|
_doc.Cache.NextChangeIndex, SendAll);
|
|
|
|
|
|
|
|
|
|
SendPreview = $"txid {_pendingSend.Txid[..16]}… · " +
|
|
|
|
|
$"fee {Fmt(_pendingSend.Fee.Satoshi)} " +
|
|
|
|
|
$"({_pendingSend.Transaction.GetVirtualSize()} vB)" +
|
|
|
|
|
(_pendingSend.Signed ? "" : " · NON firmata (watch-only)");
|
|
|
|
|
HasPendingSend = _pendingSend.Signed;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_pendingSend = null;
|
|
|
|
|
HasPendingSend = false;
|
|
|
|
|
SendPreview = $"Errore: {ex.Message}";
|
|
|
|
|
}
|
|
|
|
|
await Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
private async Task ConfirmSend()
|
|
|
|
|
{
|
|
|
|
|
if (_pendingSend is null || _client is null)
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var txid = await _client.BroadcastAsync(_pendingSend.ToHex());
|
|
|
|
|
SendPreview = $"Trasmessa: {txid}";
|
|
|
|
|
SendTo = SendAmount = "";
|
|
|
|
|
_pendingSend = null;
|
|
|
|
|
HasPendingSend = false;
|
|
|
|
|
await ConnectAndSync();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
SendPreview = $"Errore broadcast: {ex.Message}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|