From b2322261f386685fb0e410d97bcf6025fd570900 Mon Sep 17 00:00:00 2001 From: cygnet Date: Tue, 21 Apr 2026 18:36:44 +0200 Subject: [PATCH] feat(spend): donate change output if below a certain value --- lib/constants.dart | 7 +++++ lib/extensions/api_amount.dart | 4 +++ lib/states/wallet_state.dart | 30 +++++++++++++++----- rust/src/api/structs/unsigned_transaction.rs | 19 +++++++++++++ 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/lib/constants.dart b/lib/constants.dart index 01f2a3f0..177bf1f8 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -1,4 +1,5 @@ import 'package:danawallet/data/enums/fiat_currency.dart'; +import 'package:danawallet/generated/rust/api/structs/amount.dart'; import 'package:flutter/services.dart'; // The default blindbit backend used @@ -51,3 +52,9 @@ const String nameServerDevMainnet = // name server for other flavors that user testnet/signet const String nameServerDevTestnet = "https://test.dev.nameserver.danawallet.app/v1"; + +// if the change is below this amount, suggest we donate +final Amount donateBelow = Amount(field0: BigInt.from(5000)); +// signet faucet address +const String donatePaymentCode = + "tsp1qqtp5ql6htn4jz4feslzspyel2n3dzmr5fxjrc69yge2w72hnh2jzzqh6g9zjzkwzzv3m79y84dwusw3sdw2akyzvxm8l2x4gagzxjlrvf5njz0j6"; diff --git a/lib/extensions/api_amount.dart b/lib/extensions/api_amount.dart index 8fbc61f0..3ffce3fe 100644 --- a/lib/extensions/api_amount.dart +++ b/lib/extensions/api_amount.dart @@ -14,6 +14,10 @@ extension AmountExtension on Amount { return field0 > other.field0; } + bool operator <(Amount other) { + return field0 < other.field0; + } + String displayBtc() { final btcPart = field0 ~/ BigInt.from(bitcoinUnits); final satsPart = (field0 % BigInt.from(bitcoinUnits)); diff --git a/lib/states/wallet_state.dart b/lib/states/wallet_state.dart index e1fad0bb..a5be4159 100644 --- a/lib/states/wallet_state.dart +++ b/lib/states/wallet_state.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:danawallet/constants.dart'; import 'package:danawallet/data/models/bip353_address.dart'; +import 'package:danawallet/extensions/api_amount.dart'; import 'package:danawallet/extensions/date_time.dart'; import 'package:danawallet/extensions/network.dart'; import 'package:danawallet/extensions/outpoint.dart'; @@ -294,15 +295,30 @@ class WalletState extends ChangeNotifier { Future createUnsignedTxToThisRecipient( Recipient recipient, int feerate) async { final wallet = await getWalletFromSecureStorage(); + final changeCode = wallet.getChangeAddress(); if (recipient.amount.field0 < amount.field0 - BigInt.from(546)) { - return wallet.createNewTransaction( - ownedOutputs: unspentOutputs, - apiRecipients: [ - recipient, - ], - feerate: feerate.toDouble(), - network: network); + final unsignedTx = wallet.createNewTransaction( + ownedOutputs: unspentOutputs, + apiRecipients: [ + recipient, + ], + feerate: feerate.toDouble(), + network: network, + ); + + final sendAmount = unsignedTx.getSendAmount(changeAddress: changeCode); + final changeAmount = + unsignedTx.getChangeAmount(changeAddress: changeCode); + + if (changeAmount < sendAmount) { + if (changeAmount < donateBelow) { + // replace change payment code with donation payment code + return unsignedTx.replaceChangeCode( + changeCode: changeCode, newCode: donatePaymentCode); + } + } + return unsignedTx; } else { return wallet.createDrainTransaction( ownedOutputs: unspentOutputs, diff --git a/rust/src/api/structs/unsigned_transaction.rs b/rust/src/api/structs/unsigned_transaction.rs index c95a9163..38fcf3d9 100644 --- a/rust/src/api/structs/unsigned_transaction.rs +++ b/rust/src/api/structs/unsigned_transaction.rs @@ -11,6 +11,7 @@ use crate::api::structs::amount::Amount; use crate::api::structs::discovered_output::DiscoveredOutput; use crate::api::structs::recipient::Recipient; +#[derive(Clone, Debug)] pub struct ApiSilentPaymentUnsignedTransaction { pub selected_utxos: Vec<(super::outpoint::OutPoint, DiscoveredOutput)>, pub recipients: Vec, @@ -110,4 +111,22 @@ impl ApiSilentPaymentUnsignedTransaction { .cloned() .collect() } + + #[frb(sync)] + pub fn replace_change_code( + &self, + change_code: String, + new_code: String, + ) -> anyhow::Result { + let mut cloned = self.clone(); + if let Some(change_recipient) = cloned + .recipients + .iter_mut() + .find(|r| r.payment_code == change_code) + { + change_recipient.payment_code = new_code + }; + + Ok(cloned) + } }