NUT-XX: Multinut payments #103

Merged
callebtc merged 15 commits from nut-14-mpp into main 2024-05-22 20:52:16 +00:00
callebtc commented 2024-03-23 13:20:22 +00:00 (Migrated from github.com)

Enables https://twitter.com/callebtc/status/1766116631795662921

Accidentally included NUT-08 and NUT-11 header tag edits.

Enables https://twitter.com/callebtc/status/1766116631795662921 Accidentally included NUT-08 and NUT-11 header tag edits.
misovan (Migrated from github.com) reviewed 2024-03-23 13:20:22 +00:00
AngusP (Migrated from github.com) reviewed 2024-03-23 13:20:22 +00:00
thesimplekid (Migrated from github.com) reviewed 2024-03-23 13:20:22 +00:00
ebrakke (Migrated from github.com) reviewed 2024-03-23 13:20:22 +00:00
xphade (Migrated from github.com) reviewed 2024-03-23 13:20:22 +00:00
thunderbiscuit (Migrated from github.com) reviewed 2024-03-23 13:20:22 +00:00
KKA11010 (Migrated from github.com) reviewed 2024-03-23 13:20:22 +00:00
gandlafbtc (Migrated from github.com) reviewed 2024-03-23 13:20:22 +00:00
callebtc commented 2024-03-23 13:35:11 +00:00 (Migrated from github.com)

I would assume that the on-chain feature by @ngutech21 also includes an amount input in the MeltRequest, is that so? Is there some possible confusion?

Also, I'm wondering if this should be a separate endpoint or not. Since it's only a single element added in the request, I went with the current bolt11 melt endpoint since I thought it fits well. Happy about anyone's thoughts.

I would assume that the on-chain feature by @ngutech21 also includes an `amount` input in the `MeltRequest`, is that so? Is there some possible confusion? Also, I'm wondering if this should be a separate endpoint or not. Since it's only a single element added in the request, I went with the current bolt11 melt endpoint since I thought it fits well. Happy about anyone's thoughts.
gandlafbtc commented 2024-03-23 14:13:10 +00:00 (Migrated from github.com)

Is there a time limit on how long these payments can take? Or how/when is a payment considered failed?

Is there a time limit on how long these payments can take? Or how/when is a payment considered failed?
elnosh commented 2024-03-23 14:27:07 +00:00 (Migrated from github.com)

If I'm constructing a multinut payment from 3 different mints and 2 of those succeed but one fails, the entire multinut payment will fail. But the 2 partial payments from the mints that succeeded, those mints will invalidate the proofs provided if their portion of the melt request succeeds. Will there be a way for wallets to "recover" those proofs from the partial mint payments that succeeded but overall payment fails?

If I'm constructing a multinut payment from 3 different mints and 2 of those succeed but one fails, the entire multinut payment will fail. But the 2 partial payments from the mints that succeeded, those mints will invalidate the proofs provided if their portion of the melt request succeeds. Will there be a way for wallets to "recover" those proofs from the partial mint payments that succeeded but overall payment fails?
callebtc commented 2024-03-23 20:10:09 +00:00 (Migrated from github.com)

Is there a time limit on how long these payments can take? Or how/when is a payment considered failed?

Yes, very good point! The invoice expiry is the same as usual. However, the MPP payment has a default timeout of around 1-3 minutes in LND/CLN I think. That means that once you initiate the payment from one mint, you have 1-3 minutes to do it with all other mints as well. I think all implementations allow as many retries as you want, if the timeout should be reached and the payment gets rejected (that's my understanding). A comment about that should be added to the document.

> Is there a time limit on how long these payments can take? Or how/when is a payment considered failed? Yes, very good point! The invoice expiry is the same as usual. However, the MPP payment has a default timeout of around 1-3 minutes in LND/CLN I think. That means that once you initiate the payment from one mint, you have 1-3 minutes to do it with all other mints as well. I think all implementations allow as many retries as you want, if the timeout should be reached and the payment gets rejected (that's my understanding). A comment about that should be added to the document.
callebtc commented 2024-03-23 20:11:10 +00:00 (Migrated from github.com)

If I'm constructing a multinut payment from 3 different mints and 2 of those succeed but one fails, the entire multinut payment will fail. But the 2 partial payments from the mints that succeeded, those mints will invalidate the proofs provided if their portion of the melt request succeeds. Will there be a way for wallets to "recover" those proofs from the partial mint payments that succeeded but overall payment fails?

Like with normal Lightning payments / melts, a mint should only invalidate the proofs, if the payment was successful. That means, for a failed MPP payments, all nuts across all mints will be "unspent" again (and not pending anymore) and can be reused.

> If I'm constructing a multinut payment from 3 different mints and 2 of those succeed but one fails, the entire multinut payment will fail. But the 2 partial payments from the mints that succeeded, those mints will invalidate the proofs provided if their portion of the melt request succeeds. Will there be a way for wallets to "recover" those proofs from the partial mint payments that succeeded but overall payment fails? Like with normal Lightning payments / melts, a mint should only invalidate the proofs, if the payment was successful. That means, for a failed MPP payments, all nuts across all mints will be "unspent" again (and not pending anymore) and can be reused.
ngutech21 (Migrated from github.com) reviewed 2024-03-25 07:05:37 +00:00
ngutech21 (Migrated from github.com) commented 2024-03-25 06:55:19 +00:00

I like the overall idea of extending NUT-05 by adding a single field, instead of copying all the endpoints from NUT-05 and creating a new payment-method.
To make this backwards compatible the amount should be optional

<str|null>

otherwise this would break the api, because the PostMeltQuoteBolt11Request will be used for both endpoints NUT-05 (without mpp field) and NUT-14 (with mpp field).

I like the overall idea of extending NUT-05 by adding a single field, instead of copying all the endpoints from NUT-05 and creating a new payment-method. To make this backwards compatible the amount should be optional ```javascript <str|null> ``` otherwise this would break the api, because the PostMeltQuoteBolt11Request will be used for both endpoints NUT-05 (without mpp field) and NUT-14 (with mpp field).
ngutech21 (Migrated from github.com) commented 2024-03-25 07:05:19 +00:00

Should the mpp field be added to the settings from NUT-05 or would it be better to create new settings for NUT-14? Having explicit settings for NUT-14 would better fit in the overall picture, but would also add some redundancy. Would a mint always return the same min_max amounts, units etc. for both cases mpp=true and mpp=false or could there be differences?
If the mpp field will be part of the NUT-05 settings it has to be optional, otherwise it would break the api.

Should the mpp field be added to the settings from NUT-05 or would it be better to create new settings for NUT-14? Having explicit settings for NUT-14 would better fit in the overall picture, but would also add some redundancy. Would a mint always return the same min_max amounts, units etc. for both cases mpp=true and mpp=false or could there be differences? If the mpp field will be part of the NUT-05 settings it has to be optional, otherwise it would break the api.
ngutech21 commented 2024-03-25 07:09:56 +00:00 (Migrated from github.com)

I would assume that the on-chain feature by @ngutech21 also includes an amount input in the MeltRequest, is that so? Is there some possible confusion?

Yes the PostMeltQuoteOnchainRequest contains an amount field in my implementation. What do you mean by confusion?

> I would assume that the on-chain feature by @ngutech21 also includes an `amount` input in the `MeltRequest`, is that so? Is there some possible confusion? Yes the PostMeltQuoteOnchainRequest contains an amount field in my implementation. What do you mean by confusion?
callebtc commented 2024-03-26 15:26:59 +00:00 (Migrated from github.com)

Yes the PostMeltQuoteOnchainRequest contains an amount field in my implementation. What do you mean by confusion?

Ok that's what I thought!

One possible source of future confusion between might be between partial MPPs and if we would like to support amountless invoices in the future. For amountless invoices, we would also need to pass an amount field.

Maybe it's not too bad though, since the mint usually needs to treat amountless invoices differently (as opposed to invoices with amount).

Pseudocode of what I'm trying to say:

if meltRequest.amount:
  if invoice.amount:
    pay_mpp(invoice, amount)
  else:
    pay_amountless(invoice, amount)
else:
  pay_normal(invoice)
> Yes the PostMeltQuoteOnchainRequest contains an amount field in my implementation. What do you mean by confusion? Ok that's what I thought! One possible source of future confusion between might be between partial MPPs and if we would like to support amountless invoices in the future. For amountless invoices, we would also need to pass an `amount` field. Maybe it's not too bad though, since the mint usually needs to treat amountless invoices differently (as opposed to invoices with amount). Pseudocode of what I'm trying to say: ```python if meltRequest.amount: if invoice.amount: pay_mpp(invoice, amount) else: pay_amountless(invoice, amount) else: pay_normal(invoice) ```
ngutech21 commented 2024-03-27 13:01:00 +00:00 (Migrated from github.com)

Yes the PostMeltQuoteOnchainRequest contains an amount field in my implementation. What do you mean by confusion?

Ok that's what I thought!

One possible source of future confusion between might be between partial MPPs and if we would like to support amountless invoices in the future. For amountless invoices, we would also need to pass an amount field.

Here is a solution that can be extended in the future without breaking the Api and being backwards compatible at the same time: Instead of adding an amount we add an optional payment_type. This is more explicit than deriving something from the amount field. In NUT-14 it would look like this:

{
  "request": "lnbc100u1p0n7j7..",
  "unit": "sat",
  "payment_type": {
    "mpp": {
      "amount": 100
    }
  }
}

for no amount bolt11 invoices it would look like this:

{
  "request": "lnbc100u1p0n7j7..",
  "unit": "sat",
  "payment_type": "no_amount"
}

Rust code:

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct PostMeltQuoteBolt11Request {
    pub request: String,
    pub unit: CurrencyUnit,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payment_type: Option<Bolt11PaymentType>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum Bolt11PaymentType {
    #[serde(rename = "mpp")]
    Mpp { amount: u64 },
    #[serde(rename = "no_amount")]
    NoAmount,
}

We just have to agree on that the payment_type may only contain one entry, since Json does not support arithmetic datatypes.

Would this work well in python and typescript?

> > Yes the PostMeltQuoteOnchainRequest contains an amount field in my implementation. What do you mean by confusion? > > Ok that's what I thought! > > One possible source of future confusion between might be between partial MPPs and if we would like to support amountless invoices in the future. For amountless invoices, we would also need to pass an `amount` field. Here is a solution that can be extended in the future without breaking the Api and being backwards compatible at the same time: Instead of adding an amount we add an optional payment_type. This is more explicit than deriving something from the amount field. In NUT-14 it would look like this: ```javascript { "request": "lnbc100u1p0n7j7..", "unit": "sat", "payment_type": { "mpp": { "amount": 100 } } } ``` for no amount bolt11 invoices it would look like this: ```javascript { "request": "lnbc100u1p0n7j7..", "unit": "sat", "payment_type": "no_amount" } ``` Rust code: ```rust #[derive(Deserialize, Serialize, Debug, Clone)] pub struct PostMeltQuoteBolt11Request { pub request: String, pub unit: CurrencyUnit, #[serde(skip_serializing_if = "Option::is_none")] pub payment_type: Option<Bolt11PaymentType>, } #[derive(Deserialize, Serialize, Debug, Clone)] pub enum Bolt11PaymentType { #[serde(rename = "mpp")] Mpp { amount: u64 }, #[serde(rename = "no_amount")] NoAmount, } ``` We just have to agree on that the payment_type may only contain one entry, since Json does not support arithmetic datatypes. Would this work well in python and typescript?
callebtc commented 2024-03-29 20:48:11 +00:00 (Migrated from github.com)

This NUT could be extended to support on-chain in the future, #107. Happy to treat it as out of scope for this PR.

This NUT could be extended to support on-chain in the future, #107. Happy to treat it as out of scope for this PR.
callebtc commented 2024-03-29 21:07:50 +00:00 (Migrated from github.com)

Instead of adding an amount we add an optional payment_type.

Interesting approach. The no_amount case also would need an amount input since it refers to an amountless bolt11 invoice that where the payer needs to specify the amount herself.

> Instead of adding an amount we add an optional payment_type. Interesting approach. The `no_amount` case also would need an `amount` input since it refers to an amountless bolt11 invoice that where the payer needs to specify the `amount` herself.
callebtc commented 2024-05-04 06:47:34 +00:00 (Migrated from github.com)

What if we call this payment_option and use the following for this PR:

{
  "request": "lnbc100u1p0n7j7..",
  "unit": "sat",
  "payment_option": {
    "mpp": {
      "amount": 69
    }
  }
}

That means, for a future change where we would like to support amountless invoices, we could use

{
  "request": "lnbc<amountless>p0n7j7..",
  "unit": "sat",
  "payment_option": {
    "amountless": {
      "amount": 100
    }
  }
}
What if we call this `payment_option` and use the following for this PR: ```json { "request": "lnbc100u1p0n7j7..", "unit": "sat", "payment_option": { "mpp": { "amount": 69 } } } ``` That means, for a future change where we would like to support amountless invoices, we could use ```json { "request": "lnbc<amountless>p0n7j7..", "unit": "sat", "payment_option": { "amountless": { "amount": 100 } } } ```
callebtc commented 2024-05-11 16:38:04 +00:00 (Migrated from github.com)

As of af13a90, the PostMeltQuoteBolt11Request now reads:

{
  "request": <str>,
  "unit": <str_enum["sat"]>,
  "options": {
    "mpp": {
      "amount": <int>
    }
  }
}

The setting for this NUT (example: NUT-15) would be MultipathPaymentSetting:

{
  "15": {
    [
      {
        "method": "bolt11",
        "unit": "sat",
        "mpp": true
      },
      {
        "method": "bolt11",
        "unit": "usd",
        "mpp": true
      },    
    ]
  }
}

I would like to port these changes to nutshell before merging this PR.

As of [af13a90](https://github.com/cashubtc/nuts/pull/103/commits/af13a90a1e0adf990ac15c00077c4c77113a7f11), the `PostMeltQuoteBolt11Request` now reads: ```json { "request": <str>, "unit": <str_enum["sat"]>, "options": { "mpp": { "amount": <int> } } } ``` The setting for this NUT (example: NUT-15) would be `MultipathPaymentSetting`: ```json { "15": { [ { "method": "bolt11", "unit": "sat", "mpp": true }, { "method": "bolt11", "unit": "usd", "mpp": true }, ] } } ``` I would like to port these changes to nutshell before merging this PR.
Sign in to join this conversation.
No description provided.