diff --git a/src/subcommand/preview.rs b/src/subcommand/preview.rs index 0234571aa6..208a921851 100644 --- a/src/subcommand/preview.rs +++ b/src/subcommand/preview.rs @@ -88,6 +88,7 @@ impl Preview { no_limit: false, destination: None, alignment: None, + postage: Some(TransactionBuilder::DEFAULT_TARGET_POSTAGE), }, )), } diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 871a389258..0a44117e73 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -58,6 +58,8 @@ pub(crate) struct Inscribe { pub(crate) destination: Option
, #[clap(long, help = "Send any alignment output to .")] pub(crate) alignment: Option
, + #[clap(long, help = "Amount of postage to include in the inscription. Default `10000 sats`")] + pub(crate) postage: Option, } impl Inscribe { @@ -102,6 +104,10 @@ impl Inscribe { self.commit_fee_rate.unwrap_or(self.fee_rate), self.fee_rate, self.no_limit, + match self.postage { + Some(postage) => postage, + _ => TransactionBuilder::DEFAULT_TARGET_POSTAGE, + }, )?; utxos.insert( @@ -172,6 +178,7 @@ impl Inscribe { commit_fee_rate: FeeRate, reveal_fee_rate: FeeRate, no_limit: bool, + postage: Amount, ) -> Result<(SatPoint, Transaction, Transaction, TweakedKeyPair)> { let satpoint = if let Some(satpoint) = satpoint { satpoint @@ -245,7 +252,7 @@ impl Inscribe { alignment, change, commit_fee_rate, - reveal_fee + TransactionBuilder::TARGET_POSTAGE, + reveal_fee + postage, )?; let (vout, output) = unsigned_commit_tx @@ -413,6 +420,7 @@ mod tests { FeeRate::try_from(1.0).unwrap(), FeeRate::try_from(1.0).unwrap(), false, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, ) .unwrap(); @@ -445,6 +453,7 @@ mod tests { FeeRate::try_from(1.0).unwrap(), FeeRate::try_from(1.0).unwrap(), false, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, ) .unwrap(); @@ -481,6 +490,7 @@ mod tests { FeeRate::try_from(1.0).unwrap(), FeeRate::try_from(1.0).unwrap(), false, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, ) .unwrap_err() .to_string(); @@ -524,6 +534,7 @@ mod tests { FeeRate::try_from(1.0).unwrap(), FeeRate::try_from(1.0).unwrap(), false, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, ) .is_ok()) } @@ -562,6 +573,7 @@ mod tests { FeeRate::try_from(fee_rate).unwrap(), FeeRate::try_from(fee_rate).unwrap(), false, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, ) .unwrap(); @@ -626,6 +638,7 @@ mod tests { FeeRate::try_from(commit_fee_rate).unwrap(), FeeRate::try_from(fee_rate).unwrap(), false, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, ) .unwrap(); @@ -676,6 +689,7 @@ mod tests { FeeRate::try_from(1.0).unwrap(), FeeRate::try_from(1.0).unwrap(), false, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, ) .unwrap_err() .to_string(); @@ -709,6 +723,7 @@ mod tests { FeeRate::try_from(1.0).unwrap(), FeeRate::try_from(1.0).unwrap(), true, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, ) .unwrap(); diff --git a/src/subcommand/wallet/send.rs b/src/subcommand/wallet/send.rs index 324f6c0896..56b3b7b86b 100644 --- a/src/subcommand/wallet/send.rs +++ b/src/subcommand/wallet/send.rs @@ -13,6 +13,10 @@ pub(crate) struct Send { fee_rate: FeeRate, #[clap(long, help = "Send any alignment output to .")] pub(crate) alignment: Option
, + #[clap(long, help = "Target amount of postage to include in the sent output. Default `10000 sats`")] + pub(crate) target_postage: Option, + #[clap(long, help = "Maximum amount of postage to include in the sent output. Default `20000 sats`")] + pub(crate) max_postage: Option, } #[derive(Serialize, Deserialize)] @@ -91,6 +95,14 @@ impl Send { self.alignment, change, self.fee_rate, + match self.target_postage { + Some(target_postage) => target_postage, + _ => TransactionBuilder::DEFAULT_TARGET_POSTAGE, + }, + match self.max_postage { + Some(max_postage) => max_postage, + _ => TransactionBuilder::DEFAULT_MAX_POSTAGE, + }, )?; let signed_tx = client diff --git a/src/subcommand/wallet/transaction_builder.rs b/src/subcommand/wallet/transaction_builder.rs index 2db6656391..e41769ca36 100644 --- a/src/subcommand/wallet/transaction_builder.rs +++ b/src/subcommand/wallet/transaction_builder.rs @@ -108,6 +108,8 @@ pub struct TransactionBuilder { unused_change_addresses: Vec
, utxos: BTreeSet, target: Target, + target_postage: Amount, + max_postage: Amount, } type Result = std::result::Result; @@ -115,9 +117,9 @@ type Result = std::result::Result; impl TransactionBuilder { const ADDITIONAL_INPUT_VBYTES: f64 = 58.0; const ADDITIONAL_OUTPUT_VBYTES: f64 = 43.0; - const MAX_POSTAGE: Amount = Amount::from_sat(2 * 10_000); const SCHNORR_SIGNATURE_SIZE: usize = 64; - pub(crate) const TARGET_POSTAGE: Amount = Amount::from_sat(10_000); + pub(crate) const DEFAULT_MAX_POSTAGE: Amount = Amount::from_sat(2 * 10_000); + pub(crate) const DEFAULT_TARGET_POSTAGE: Amount = Amount::from_sat(10_000); pub fn build_transaction_with_postage( outgoing: SatPoint, @@ -127,6 +129,8 @@ impl TransactionBuilder { alignment: Option
, change: [Address; 2], fee_rate: FeeRate, + target_postage: Amount, + max_postage: Amount, ) -> Result { Self::new( outgoing, @@ -137,6 +141,8 @@ impl TransactionBuilder { change, fee_rate, Target::Postage, + target_postage, + max_postage, )? .build_transaction() } @@ -169,6 +175,8 @@ impl TransactionBuilder { change, fee_rate, Target::Value(output_value), + Amount::from_sat(0), + Amount::from_sat(0), )? .build_transaction() } @@ -193,6 +201,8 @@ impl TransactionBuilder { change: [Address; 2], fee_rate: FeeRate, target: Target, + target_postage: Amount, + max_postage: Amount, ) -> Result { if change.contains(&recipient) { return Err(Error::DuplicateAddress(recipient)); @@ -215,6 +225,8 @@ impl TransactionBuilder { alignment, unused_change_addresses: change.to_vec(), target, + target_postage, + max_postage, }) } @@ -377,7 +389,7 @@ impl TransactionBuilder { if let Some(excess) = value.checked_sub(self.fee_rate.fee(self.estimate_vbytes())) { let (max, target) = match self.target { - Target::Postage => (Self::MAX_POSTAGE, Self::TARGET_POSTAGE), + Target::Postage => (self.max_postage, self.target_postage), Target::Value(value) => (value, value), }; @@ -591,7 +603,7 @@ impl TransactionBuilder { match self.target { Target::Postage => { assert!( - Amount::from_sat(output.value) <= Self::MAX_POSTAGE + slop, + Amount::from_sat(output.value) <= self.max_postage + slop, "invariant: excess postage is stripped" ); } @@ -755,6 +767,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -799,6 +813,8 @@ mod tests { (change(1), Amount::from_sat(1_724)), ], target: Target::Postage, + target_postage: TransactionBuilder::DEFAULT_TARGET_POSTAGE, + max_postage: TransactionBuilder::DEFAULT_MAX_POSTAGE, }; pretty_assert_eq!( @@ -828,6 +844,8 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .is_explicitly_rbf()) @@ -846,6 +864,8 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Ok(Transaction { version: 1, @@ -870,6 +890,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -895,6 +917,8 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Ok(Transaction { version: 1, @@ -918,6 +942,8 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Err(Error::NotEnoughCardinalUtxos), ) @@ -939,6 +965,8 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Err(Error::NotEnoughCardinalUtxos), ) @@ -960,6 +988,8 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Ok(Transaction { version: 1, @@ -967,7 +997,7 @@ mod tests { input: vec![tx_in(outpoint(1)), tx_in(outpoint(2))], output: vec![ tx_out(4_950, change(1)), - tx_out(TransactionBuilder::TARGET_POSTAGE.to_sat(), recipient()), + tx_out(TransactionBuilder::DEFAULT_TARGET_POSTAGE.to_sat(), recipient()), tx_out(14_831, change(0)), ], }) @@ -988,6 +1018,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .build() @@ -1008,6 +1040,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .build() @@ -1028,6 +1062,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .build() @@ -1048,6 +1084,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -1074,6 +1112,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -1097,13 +1137,15 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Ok(Transaction { version: 1, lock_time: PackedLockTime::ZERO, input: vec![tx_in(outpoint(1))], output: vec![ - tx_out(TransactionBuilder::TARGET_POSTAGE.to_sat(), recipient()), + tx_out(TransactionBuilder::DEFAULT_TARGET_POSTAGE.to_sat(), recipient()), tx_out(989_870, change(1)) ], }) @@ -1124,6 +1166,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -1145,6 +1189,8 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Ok(Transaction { version: 1, @@ -1171,6 +1217,8 @@ mod tests { None, [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Ok(Transaction { version: 1, @@ -1195,6 +1243,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -1224,6 +1274,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -1251,6 +1303,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -1275,6 +1329,8 @@ mod tests { [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), Target::Postage, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap() .select_outgoing() @@ -1309,6 +1365,8 @@ mod tests { (change(1), Amount::from_sat(1_774)), ], target: Target::Postage, + target_postage: TransactionBuilder::DEFAULT_TARGET_POSTAGE, + max_postage: TransactionBuilder::DEFAULT_MAX_POSTAGE, } .build() .unwrap(); @@ -1339,6 +1397,8 @@ mod tests { (change(0), Amount::from_sat(1_774)), ], target: Target::Postage, + target_postage: TransactionBuilder::DEFAULT_TARGET_POSTAGE, + max_postage: TransactionBuilder::DEFAULT_MAX_POSTAGE, } .build() .unwrap(); @@ -1360,6 +1420,8 @@ mod tests { alignment(), [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Err(Error::NotEnoughCardinalUtxos) ) @@ -1378,6 +1440,8 @@ mod tests { alignment(), [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Err(Error::UtxoContainsAdditionalInscription { outgoing_satpoint: satpoint(1, 0), @@ -1401,6 +1465,8 @@ mod tests { alignment(), [change(0), change(1)], fee_rate, + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ) .unwrap(); @@ -1593,6 +1659,8 @@ mod tests { alignment(), [change(0), change(1)], FeeRate::try_from(1.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Ok(Transaction { version: 1, @@ -1721,6 +1789,8 @@ mod tests { alignment(), [change(0), change(1)], FeeRate::try_from(250.0).unwrap(), + TransactionBuilder::DEFAULT_TARGET_POSTAGE, + TransactionBuilder::DEFAULT_MAX_POSTAGE, ), Ok(Transaction { version: 1,