-
Notifications
You must be signed in to change notification settings - Fork 104
Complete receive payjoin feature groundwork #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
97f0aa2
93cb800
cb78ccb
66e1330
6ce8cfb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| use std::borrow::Borrow; | ||
| use std::fmt; | ||
|
|
||
| use log::warn; | ||
|
|
||
| use crate::fee_rate::FeeRate; | ||
|
|
||
| #[derive(Debug)] | ||
| pub(crate) struct Params { | ||
| // version | ||
| // v: usize, | ||
| // disableoutputsubstitution | ||
| pub disable_output_substitution: bool, | ||
| // maxadditionalfeecontribution, additionalfeeoutputindex | ||
| pub additional_fee_contribution: Option<(bitcoin::Amount, usize)>, | ||
| // minfeerate | ||
| pub min_feerate: FeeRate, | ||
| } | ||
|
|
||
| impl Default for Params { | ||
| fn default() -> Self { | ||
| Params { | ||
| disable_output_substitution: false, | ||
| additional_fee_contribution: None, | ||
| min_feerate: FeeRate::ZERO, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Params { | ||
| #[cfg(feature = "receiver")] | ||
| pub fn from_query_pairs<K, V, I>(pairs: I) -> Result<Self, Error> | ||
| where | ||
| I: Iterator<Item = (K, V)>, | ||
| K: Borrow<str> + Into<String>, | ||
| V: Borrow<str> + Into<String>, | ||
| { | ||
| let mut params = Params::default(); | ||
|
|
||
| let mut additional_fee_output_index = None; | ||
| let mut max_additional_fee_contribution = None; | ||
|
|
||
| for (k, v) in pairs { | ||
| match (k.borrow(), v.borrow()) { | ||
| ("v", v) => | ||
| if v != "1" { | ||
| return Err(Error::UnknownVersion); | ||
| }, | ||
| ("additionalfeeoutputindex", index) => | ||
| additional_fee_output_index = match index.parse::<usize>() { | ||
| Ok(index) => Some(index), | ||
| Err(_error) => { | ||
| warn!( | ||
| "bad `additionalfeeoutputindex` query value '{}': {}", | ||
| index, _error | ||
| ); | ||
| None | ||
| } | ||
| }, | ||
| ("maxadditionalfeecontribution", fee) => | ||
| max_additional_fee_contribution = | ||
| match bitcoin::Amount::from_str_in(&fee, bitcoin::Denomination::Bitcoin) { | ||
| Ok(contribution) => Some(contribution), | ||
| Err(_error) => { | ||
| warn!( | ||
| "bad `maxadditionalfeecontribution` query value '{}': {}", | ||
| fee, _error | ||
| ); | ||
| None | ||
| } | ||
| }, | ||
| ("minfeerate", feerate) => | ||
| params.min_feerate = match feerate.parse::<u64>() { | ||
| Ok(rate) => FeeRate::from_sat_per_vb(rate), | ||
| Err(e) => return Err(Error::FeeRate(e)), | ||
| }, | ||
| ("disableoutputsubstitution", v) => | ||
| params.disable_output_substitution = v == "true", | ||
| _ => (), | ||
| } | ||
| } | ||
|
|
||
| match (max_additional_fee_contribution, additional_fee_output_index) { | ||
| (Some(amount), Some(index)) => | ||
| params.additional_fee_contribution = Some((amount, index)), | ||
| (Some(_), None) | (None, Some(_)) => { | ||
| warn!("only one additional-fee parameter specified: {:?}", params); | ||
| } | ||
| _ => (), | ||
| } | ||
|
|
||
| Ok(params) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub(crate) enum Error { | ||
| UnknownVersion, | ||
| FeeRate(std::num::ParseIntError), | ||
| } | ||
|
|
||
| impl fmt::Display for Error { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| match self { | ||
| Error::UnknownVersion => write!(f, "unknown version"), | ||
| Error::FeeRate(_) => write!(f, "could not parse feerate"), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl std::error::Error for Error { | ||
| fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | ||
| match self { | ||
| Error::FeeRate(error) => Some(error), | ||
| _ => None, | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,14 +38,14 @@ type InternalResult<T> = Result<T, InternalValidationError>; | |
| /// Builder for sender-side payjoin parameters | ||
| /// | ||
| /// These parameters define how client wants to handle PayJoin. | ||
| pub struct Params { | ||
| pub struct Configuration { | ||
| disable_output_substitution: bool, | ||
| fee_contribution: Option<(bitcoin::Amount, Option<usize>)>, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any value putting params here as a field?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this configuration is going to hold this data then yes, I think it makes sense to be part of this. The idea of a builder to expose this interface in an accessible way rather than expose I'm starting to think of this impl RequestBuilder {
fn create_pj_request(psbt: Psbt, pj_url: Uri<PayJoin>) -> Result<(Request, Context)>;
}instead of on the manually-validated Then create_pj_request could validate the endpoint
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The builder could have
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, builder pattern kinda makes sense. We might need more than one to implement type state, since both URI and PSBT are mandatory. Alternatively we could just accept both in a single method. |
||
| clamp_fee_contribution: bool, | ||
| min_fee_rate: FeeRate, | ||
| } | ||
|
|
||
| impl Params { | ||
| impl Configuration { | ||
| /// Offer the receiver contribution to pay for his input. | ||
| /// | ||
| /// These parameters will allow the receiver to take `max_fee_contribution` from given change | ||
|
|
@@ -57,7 +57,7 @@ impl Params { | |
| max_fee_contribution: bitcoin::Amount, | ||
| change_index: Option<usize>, | ||
| ) -> Self { | ||
| Params { | ||
| Configuration { | ||
| disable_output_substitution: false, | ||
| fee_contribution: Some((max_fee_contribution, change_index)), | ||
| clamp_fee_contribution: false, | ||
|
|
@@ -70,7 +70,7 @@ impl Params { | |
| /// While it's generally better to offer some contribution some users may wish not to. | ||
| /// This function disables contribution. | ||
| pub fn non_incentivizing() -> Self { | ||
| Params { | ||
| Configuration { | ||
| disable_output_substitution: false, | ||
| fee_contribution: None, | ||
| clamp_fee_contribution: false, | ||
|
|
@@ -539,7 +539,7 @@ fn check_change_index( | |
| fn determine_fee_contribution( | ||
| psbt: &Psbt, | ||
| payee: &Script, | ||
| params: &Params, | ||
| params: &Configuration, | ||
| ) -> Result<Option<(bitcoin::Amount, usize)>, InternalCreateRequestError> { | ||
| Ok(match params.fee_contribution { | ||
| Some((fee, None)) => find_change_index(psbt, payee, fee, params.clamp_fee_contribution)?, | ||
|
|
@@ -583,7 +583,7 @@ fn serialize_psbt(psbt: &Psbt) -> Vec<u8> { | |
| pub(crate) fn from_psbt_and_uri( | ||
| mut psbt: Psbt, | ||
| uri: crate::uri::PjUri<'_>, | ||
| params: Params, | ||
| params: Configuration, | ||
| ) -> Result<(Request, Context), CreateRequestError> { | ||
| psbt.validate_input_utxos(true).map_err(InternalCreateRequestError::InvalidOriginalInput)?; | ||
| let disable_output_substitution = | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.