Skip to content
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

adding support for bolt12 offers #598

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

hydra-yse
Copy link
Contributor

@hydra-yse hydra-yse commented Nov 9, 2023

resolves #562 1,2
Note: In order to support these changes, the gl_client crate in sdk-core currently points to a locally-owned repository. Once greenlight and cln_grpc are both updated to support these features, we can opt-out back to the original crates. I suggest changes should be moved into a side branch until then.

Copy link
Collaborator

@dangeross dangeross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good @hydra-yse, thanks. I did a pass over the PR, let me know if you need help running the code generators.

libs/sdk-core/src/greenlight/node_api.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/greenlight/node_api.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/input_parser.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/models.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/node_api.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/node_api.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/bridge_generated.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@JssDWt JssDWt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some small remarks.

I think it could be useful to put the actual sending to bolt12 invoices in the same pull request eventually. So you know exactly which fields you need where. Like blinded paths for example are probably useful for sending, but maybe not so useful to expose to the caller.

libs/sdk-core/src/greenlight/node_api.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/models.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/invoice.rs Outdated Show resolved Hide resolved
hydra-yse pushed a commit to hydra-yse/breez-sdk that referenced this pull request Nov 14, 2023
hydra-yse pushed a commit to hydra-yse/breez-sdk that referenced this pull request Nov 14, 2023
@hydra-yse hydra-yse requested a review from dangeross November 14, 2023 16:05
Copy link
Collaborator

@dangeross dangeross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made another pass and left comments, looking good.

Are you going to add the bindings interface to the uniffi UDL in sdk-bindings also? If you make changes there, please rerun the flutter/RN codegen.

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Amount {
Bitcoin { amount_msats: u64 },
Currency { iso4217_code: [u8; 3], amount: u64 },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest changing the Currency iso4217_code to id, to match FiatCurrency id and converting it to String.

Suggested change
Currency { iso4217_code: [u8; 3], amount: u64 },
Currency { id: String, amount: u64 },

https://github.com/breez/breez-sdk/blob/3a7fd3ecd272b6271d5af843e794bb78ec866984/libs/sdk-core/src/fiat.rs#L49-L52

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Amount {
Bitcoin { amount_msats: u64 },
Currency { iso4217_code: [u8; 3], amount: u64 },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to be careful with the currency amount, as in the Offer spec it is denominated in the fractional unit of the currency. Maybe renaming it to fractional_amount is clearer.

  • MUST specify offer_amount in the currency unit adjusted by the ISO 4712 exponent (e.g. USD cents).


#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Amount {
Bitcoin { amount_msats: u64 },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Bitcoin { amount_msats: u64 },
Bitcoin { amount_msat: u64 },

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally named it amount_msats to keep naming persistence with the lightning::offers::offer::Amount enum, but there is no problem in changing it 👍


#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct FetchInvoiceResponse {
pub invoice: String,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're following the current SDK naming convention, this should be bolt12. But we should consider unifying these string invoice names across the SDK to something like payment_request or invoice. send_payment should also be able to handle both bolt11 and bolt12.

What are your thoughts on changes to the invoice naming @roeierez?

Copy link
Member

@roeierez roeierez Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the response it is OK to differentiate and use bolt12, bolt11 as it is more clear to the caller.
About send_payment I agree it should take either bolt11 or bolt12 so clearly the current bolt11 should be changed in SendPaymentRequest.

@@ -719,6 +729,25 @@ fn sign_invoice(invoice: RawInvoice) -> String {
.to_string()
}

fn fetch_invoice(offer: LNOffer) -> FetchInvoiceResponse {
// Mock node receiving FetchInvoiceRequest and building an invoice out of it
let minimum_sats = 10000u64;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to currency_amount_msat

@dangeross
Copy link
Collaborator

I know the CLN method is called fetchinvoice, but what do you think to the interface being called redeem_offer? fetch_invoice sounds like we're querying a persisted invoice.

@hydra-yse
Copy link
Contributor Author

fetch_invoice sounds like we're querying a persisted invoice.

Fair point. I can see the reasoning behind naming it fetch_invoice, but I agree that ultimately it's more like "redeeming" or "generating" one. As long as it's documented, I guess there is no issue with diverging from the CLN spec.

Copy link
Member

@roeierez roeierez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Some small comments.


#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct FetchInvoiceResponse {
pub invoice: String,
Copy link
Member

@roeierez roeierez Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the response it is OK to differentiate and use bolt12, bolt11 as it is more clear to the caller.
About send_payment I agree it should take either bolt11 or bolt12 so clearly the current bolt11 should be changed in SendPaymentRequest.

@@ -64,6 +61,7 @@ miniz_oxide = "0.7.1"
tokio-stream = "0.1.14"
serde_with = "3.3.0"
regex = "1.8.1"
gl-client = { git = "https://github.com/hydra-yse/greenlight", branch = "20231109-fetchinvoice" }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can now use the gl main branch as this was merged yesterday.

Copy link
Contributor

@JssDWt JssDWt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good!
Some small nits about naming.

There's some changes in Cargo.toml/Cargo.lock files that seem unnecessary for this change, let's clean that up before merging.

OfferReplyError(anyhow::Error),

#[error("Offer timeout: {0}")]
OfferTimeout(anyhow::Error),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to make more clear what this does.
It's a long name I'm suggesting though, maybe we can do better.

Suggested change
OfferTimeout(anyhow::Error),
OfferInvoiceRequestTimeout(anyhow::Error),

OfferExpired(anyhow::Error),

#[error("Offer reply error: {0}")]
OfferReplyError(anyhow::Error),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to make more clear what this does.
It's a long name I'm suggesting though, maybe we can do better.

Suggested change
OfferReplyError(anyhow::Error),
OfferInvoiceRequestError(anyhow::Error),

},
JsonRpcErrCode::OfferExpired => Self::OfferExpired(status.into()),
JsonRpcErrCode::OfferBadInvreqReply => Self::OfferReplyError(status.into()),
JsonRpcErrCode::OfferRouteNotFound => Self::RouteNotFound(status.into()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud. I think it may be useful to distinguish between 'payment' route not found and 'messaging' route not found. RouteNotFound is such a general error that pops up too many times. @dangeross what do you think?

libs/sdk-core/src/invoice.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/models.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/test_utils.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/input_parser.rs Outdated Show resolved Hide resolved
@@ -133,7 +133,7 @@ dictionary RouteHint {
};

dictionary LNInvoice {
string bolt11;
string raw_invoice;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field changed? do we use it for bolt12 also? I couldn't find any reference for such usage in the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider whether the LNInvoice wrapper should be able to support both Bolt11 and Bolt12 fields at once, given the overlap between the two. Currently CLN makes the invoice field naming distinct between the two types in most methods (e.g. createinvoice, listinvoices etc.) with the exception of outward methods like sendpay and pay.

The initial reasoning was that, in terms of UX, I believe that the final user is probably not really interested in the type of invoice they are paying (whether bolt11 or 12), so perhaps unifying the optional bolt11 and bolt12 parameters into a single field raw_invoice could make sense and reduce the need to constantly check which one it is. Additionally, the client would know the type a priori to the fetchinvoice call simply by the scanned QR (offer or bolt11 invoice), therefore there is most probably a way to make this work with a single field.

On the other hand, I can also see instances where the developers would like to know which type of invoice it is at any given time. That is of course more flexible on the developer's side as they are now able to handle the two distinct cases better (such as adding more fields depending on which kind it is [e.g. quantity]) and throw an error, for example, if neither is specified, without having to refer to the original offer to discern so.

I'll probably roll back this change and specify the two fields as optional, just wanted to let you in on what the thought process was behind that decision

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the details on what you had in mind @hydra-yse
For me it is even more simple than that. As long as the code doesn't user LNINvoice for bolt12 (and we don't even expose this parsing to the user) then there is no reason to unified.
If/When we implement and expose such parsing we will have more information to take the right decision.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Roei. For parsing we can differentiate between bolt11 and bolt12 invoices. For send_payment we can call the bolt11 argument payment_request. The developer would call send_payment with either the bolt11 fields from LNInvoice or the bolt12_offer field from LNOffer.

@hydra-yse hydra-yse changed the title implementing fetchinvoice for bolt12 offers adding support for bolt12 offers Nov 22, 2023
Copy link
Contributor

@JssDWt JssDWt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main questions for me are:

  • what to do with the parse_invoice interface?
  • can we call the fetch_invoice inside the send_payment method, rather than exposing it to the client?

@@ -133,7 +133,8 @@ dictionary RouteHint {
};

dictionary LNInvoice {
string bolt11;
string? bolt11;
string? bolt12;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like the side-by-side fields here. You see this change cascading throughout the codebase, into swaps and lnurl. While I don't think it's relevant there.

Maybe we need another type: Bolt12Invoice? I think the main place where this change is needed is in the parse_invoice function. Perhaps the parse_invoice function can return an enum instead?

I see that parse_invoice is exposed in the udl as well. @roeierez why do we expose that function? Doesn't the input parser cover that?

Copy link
Contributor Author

@hydra-yse hydra-yse Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also conflicted about the decision. The merging into LNInvoice made sense to me given that the invoices are functionally equivalent at the moment, since we do not handle recurrence and blinded paths yet, plus the fact that CLN's create_invoice command (which is not directly called, as it is more low-level) does exactly the same.

Despite that, some higher level commands like lightning-invoice do not allow us to create bolt12 invoices directly, therefore it would make sense to distinguish between those which can and can't with two different interfaces, then using enums to specify either one of them.

Lastly, regarding the codebase, I agree that having to propagate the changes everywhere is not ideal, yet I've checked that altering parse_invoice would require us to make many changes (if not more) as well. Despite the hassle though, I still think it may be worth going forward as you suggested.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree and strongly feel that parsing bolt11 should return a different type than LNInvoice simply because it has different data (even though currently we don't expose/use it).
As for exposing parse_invoice in the udl I agree it is not really necessary (perhaps there are times when the user "knows" that she have a bolt11 in hand and can use this function directly rather than going through the input parser). Doesn't mean we have to do the same mistake twice if we are not sure.

How about:

  1. Leave the parse_invoice as is for now (bolt11 only).
  2. Not expose parsing bolt12 directly but only via the input parser
  3. Use different structure for bolt12 invoices.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think

  • LNInvoice could be renamed to Bolt11Invoice to take away any ambiguity.
  • parse_invoice could be renamed to parse_bolt11_invoice to make it clear what it's for. Maybe add a parse_bolt12_invoice function (internally) if necessary.
  • Add a Bolt12Invoice type
  • The input parser could probably use another branch to parse bolt12 invoices as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I am ok with renaming and break computability a bit.

@@ -1368,6 +1368,16 @@ impl BreezServices {
},
})
}

pub async fn fetch_invoice(&self, req: FetchInvoiceRequest) -> Result<FetchInvoiceResponse> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the goal to actually expose this to the client? (in that case needs changes to the udl)
I thought we could do the fetching of the invoice inside the send_payment method and we wouldn't need to expose this to the client? Maybe I misunderstood that.

Copy link
Contributor Author

@hydra-yse hydra-yse Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my thoughts on how the flow would be, and the discussions previously had, I have ended up with the following conclusion:

  1. User scans the QR code, reading the offer and decoding its contents
  2. User gets prompted with mandatory fields to fill (e.g. quantity, amount_msat [if offer's amount = any] etc.)
  3. Client calls fetch_invoice with specified parameters, gets a bolt12 invoice in return and displays relevant changes (e.g. amount_msat, which should correspond to the specified amount_msat field times the quantity field, but may have been changed by the recipient for any reason)
  4. Client displays the invoice details, if user agrees they execute send_payment, and the invoice gets paid

Thus, since it is relevant for the user to see which changes may or may not have been made, I suggest keeping fetch_invoice as a separate function exposed to the client.
Let me know if you agree or if perhaps we could generalize the mismatches directly within send_payment (e.g. when amount_msat mismatches, simply return Err and have the user retry until the amount is valid, although I do think that would be somewhat limiting for both users and developers).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I think that makes sense. It would need some extensive documentation on how to use this bolt12 flow.

Question is whether we even want to put the bolt12 send_payment in the same function as the bolt11 send_payment then. Since the caller already has to use a different flow to grab the invoice and show things to the user, maybe we're better off having separate functions, rather than trying to fit everything in the same?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame we have to expose all this all the way to the user. It makes the sdk harder to use. It's not just a send_payment flow anymore, but you really have to implement many branches as a developer. I guess there's not much we can do about that though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can start with error when mismatch. It is too duplicate flow to show changes. Also we don't know currently if in practice this really happens or what is the probability for this to happen.

How about start simple? :

  1. User pay offer (and pass amount for amount-less offers)
  2. We fetch the invoice internally if amount doesn't match return error otherwise we just pay.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JssDWt @hydra-yse my suggestion above consolidates the flow I agree we shouldn't make the flow complicated and we should do our best do send_payment would fit both cases (bolt12 and bolt11).
If the internally fetch 8nvoice returns mismatch in amount let's just return error

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure, we came to a conclusion that we don't need this method at the BreezServices level right?

libs/sdk-core/src/greenlight/node_api.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/greenlight/node_api.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/lnurl/withdraw.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/models.rs Outdated Show resolved Hide resolved
string description;
u64? absolute_expiry;
string? issuer;
u64? supported_quantity;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about remove this field and not supporting quantity at all at this stage?

libs/sdk-bindings/src/breez_sdk.udl Outdated Show resolved Hide resolved
dictionary PayOfferRequest {
string offer;
u64? amount_msat;
u64? quantity;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here perhaps not pass quantity at all? I am OK with starting without supporting quantity at all and even return error when user scans an offer that requires quantity.
I think it is better to wait for a feedback from the users here to understand if supporting it is required.

Copy link
Contributor Author

@hydra-yse hydra-yse Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about it, I agree. Implementing quantity doesn't take much (just a few lines of code in fact, since the changes are handled on CLN) though I do agree in more general terms that perhaps it could be handled elsewhere. Leaving it out for now 👍

let mut field_errors: Vec<FetchInvoiceRequiredField> = vec![];

if parsed_offer.expects_quantity() && req.quantity.is_none() {
field_errors.push(FetchInvoiceRequiredField::Quantity);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a simple way would be to just return error here? These are validation errors for the developer, not for the end user if I understand correctly.

Copy link
Contributor Author

@hydra-yse hydra-yse Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I kept them for the developer side if a change is ever detected. This way, they can check the array of fields and get a convenient enum which they can then use to throw specific errors, or perhaps show a "red field" for the values that need to be changed in order for the offer to succeed.

Edit: After thinking more about it, I have changed my mind. The offer is of course still parsed both client-side and through the SDK, yet no validation is done on the fields. The client (developer) has full responsibility of rendering the appropriate fields and checking they are valid before calling the method. If they do not, the grpc call will of course fail. Understanding whether or not it did due to missing fields is ultimately the developer's task, as we want to keep the sdk as simple as possible.

libs/sdk-core/src/greenlight/node_api.rs Outdated Show resolved Hide resolved

Ok(FetchInvoiceResponse {
bolt12: response.invoice,
changes: response.changes.map(|changes| FetchInvoiceChanges {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes seems like complicating the flow and I don't understand when exactly the API call should return changes in practice? Is this a positive or negative scenario?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the end user, this is a negative scenario. If the amount changes from what the user expects without them knowing, they would be paying a higher amount than expected without confirmation.

I'll remove throwing an error when the description or vendor change, as they are not that relevant (added them for completeness), but definitely will keep the change in the expected amount.

Comment on lines 826 to 1077
pub struct FetchInvoiceRequest {
pub offer: String,
pub amount_msat: Option<u64>,
pub quantity: Option<u64>,
pub timeout: Option<f64>,
pub payer_note: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct FetchInvoiceChanges {
pub description_appended: Option<String>,
pub description: Option<String>,
pub vendor_removed: Option<String>,
pub vendor: Option<String>,
pub amount_msat: Option<u64>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct FetchInvoiceResponse {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like these are used internally in the sdk and should not be public.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structs must be prefixed with pub in order to be accessible from other sub-modules, like the node_api

libs/sdk-bindings/src/breez_sdk.udl Outdated Show resolved Hide resolved
.amount
.map(|amount| match amount {
crate::invoice::Amount::Bitcoin { amount_msat } => amount_msat,
crate::invoice::Amount::Currency { .. } => currency_amount_msat,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do convert the currency to sats here? Let's not support it at first stage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this was part of the old tests, planning to change it and find a better way to replicate the invoice creation step from the offer using the lightning library.

@@ -1368,6 +1368,16 @@ impl BreezServices {
},
})
}

pub async fn fetch_invoice(&self, req: FetchInvoiceRequest) -> Result<FetchInvoiceResponse> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure, we came to a conclusion that we don't need this method at the BreezServices level right?

Copy link
Member

@roeierez roeierez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hydra-yse this shapes up very nicely and looks really good. I added some comments, most of them are Nits.
I think we are missing two things:

  1. Supporting open channels as part of bolt12 received payment.
  2. Adding cli commands to create offer and pay offer.

Ok(FetchInvoiceResponse {
bolt12: response.invoice,
new_amount_msat: match response.changes {
Some(changes) => changes.amount_msat.map(|amount| amount.msat),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check if other changes exists and return error? My concern is that it seems we swallow these changes without any indication for the user.

Copy link
Contributor Author

@hydra-yse hydra-yse Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For basic support, currently not. Changes in description/vendor can be ignored while a change in the amount could mean the user paying more than they expect, thus an error has to be thrown

@@ -1368,6 +1368,38 @@ impl BreezServices {
},
})
}

pub async fn create_offer(&self, req: CreateOfferRequest) -> Result<String> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API is actually similar to receive_payment (that returns bolt11) but for bolt12.
The way it is right now is a bit inconsistent since the receive_payment returns a response with the invoice parsed (LNInvoice) and here we return just the raw offer. For consistency perhaps we should return the LNOffer structure here.

Comment on lines 820 to 1080
pub struct FetchInvoiceRequest {
pub offer: String,
pub amount_msat: Option<u64>,
pub timeout: Option<f64>,
pub payer_note: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct FetchInvoiceResponse {
pub bolt12: String,
pub new_amount_msat: Option<u64>,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not publicly exposed right but only needed internally in the sdk right? If so let's change their access modifier accordingly.

u64? amount_msat;
string description;
u64? absolute_expiry;
string? issuer;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the issuer for the first version of bolt12 feature.

Copy link
Contributor

@JssDWt JssDWt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's starting to look very clean, I like it!

libs/Cargo.toml Outdated Show resolved Hide resolved
libs/sdk-bindings/src/breez_sdk.udl Outdated Show resolved Hide resolved
libs/sdk-bindings/src/breez_sdk.udl Outdated Show resolved Hide resolved
libs/sdk-core/src/breez_services.rs Outdated Show resolved Hide resolved
libs/sdk-core/src/breez_services.rs Outdated Show resolved Hide resolved
@@ -237,7 +237,7 @@ impl BreezServices {
req: SendPaymentRequest,
) -> Result<SendPaymentResponse, SendPaymentError> {
self.start_node().await?;
let parsed_invoice = parse_invoice(req.bolt11.as_str())?;
let parsed_invoice = parse_invoice(req.invoice.as_str())?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this still parses a bolt11 invoice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pay_offer internally calls send_payment, so I thought it would be appropriate to change the field to invoice as we now know it can support both bolt11 and 12.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the parse_invoice function expects a bolt11 invoice. So if a bolt12 invoice is passed here, the parsing will fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I think the best solution would be to make parse_invoice compatible with either, returning either a bolt11 or a bolt12 invoice. Alternatively, I can just replicate the send_payment flow on pay_offer and create a separate parsing method for bolt 12 invoices. Let me know what you think

@dangeross
Copy link
Collaborator

Adding CLN Bolt12 compat tracking issue here
ElementsProject/lightning#7407

@dangeross dangeross force-pushed the 20231109-fetchinvoice branch from 314feca to 344ab0b Compare July 5, 2024 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support paying BOLT12 offers
4 participants