From 9e3086df0d7ba431886d77f7406068bb335b1048 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Wed, 26 Nov 2025 18:57:55 +0000 Subject: [PATCH] expected fees --- proto/autogenerated/client.md | 2 ++ proto/autogenerated/go/types.go | 16 +++++++++------- proto/autogenerated/ts/types.ts | 24 ++++++++++++++++++++---- proto/service/structs.proto | 2 ++ src/services/main/appUserManager.ts | 1 + src/services/main/liquidityProvider.ts | 10 ++++++---- src/services/main/paymentManager.ts | 8 ++++++++ 7 files changed, 48 insertions(+), 15 deletions(-) diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 2d623c0f..3a0817ff 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -1472,12 +1472,14 @@ The nostr server will send back a message response, and inside the body there wi ### PayAppUserInvoiceRequest - __amount__: _number_ - __debit_npub__: _string_ *this field is optional + - __expected_fees__: _[CumulativeFees](#CumulativeFees)_ *this field is optional - __invoice__: _string_ - __user_identifier__: _string_ ### PayInvoiceRequest - __amount__: _number_ - __debit_npub__: _string_ *this field is optional + - __expected_fees__: _[CumulativeFees](#CumulativeFees)_ *this field is optional - __invoice__: _string_ ### PayInvoiceResponse diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 4c281876..f1d22e26 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -555,15 +555,17 @@ type PayAddressResponse struct { Txid string `json:"txId"` } type PayAppUserInvoiceRequest struct { - Amount int64 `json:"amount"` - Debit_npub string `json:"debit_npub"` - Invoice string `json:"invoice"` - User_identifier string `json:"user_identifier"` + Amount int64 `json:"amount"` + Debit_npub string `json:"debit_npub"` + Expected_fees *CumulativeFees `json:"expected_fees"` + Invoice string `json:"invoice"` + User_identifier string `json:"user_identifier"` } type PayInvoiceRequest struct { - Amount int64 `json:"amount"` - Debit_npub string `json:"debit_npub"` - Invoice string `json:"invoice"` + Amount int64 `json:"amount"` + Debit_npub string `json:"debit_npub"` + Expected_fees *CumulativeFees `json:"expected_fees"` + Invoice string `json:"invoice"` } type PayInvoiceResponse struct { Amount_paid int64 `json:"amount_paid"` diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index 355973fe..e0da46ac 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -3262,15 +3262,17 @@ export const PayAddressResponseValidate = (o?: PayAddressResponse, opts: PayAddr export type PayAppUserInvoiceRequest = { amount: number debit_npub?: string + expected_fees?: CumulativeFees invoice: string user_identifier: string } -export type PayAppUserInvoiceRequestOptionalField = 'debit_npub' -export const PayAppUserInvoiceRequestOptionalFields: PayAppUserInvoiceRequestOptionalField[] = ['debit_npub'] +export type PayAppUserInvoiceRequestOptionalField = 'debit_npub' | 'expected_fees' +export const PayAppUserInvoiceRequestOptionalFields: PayAppUserInvoiceRequestOptionalField[] = ['debit_npub', 'expected_fees'] export type PayAppUserInvoiceRequestOptions = OptionsBaseMessage & { checkOptionalsAreSet?: PayAppUserInvoiceRequestOptionalField[] amount_CustomCheck?: (v: number) => boolean debit_npub_CustomCheck?: (v?: string) => boolean + expected_fees_Options?: CumulativeFeesOptions invoice_CustomCheck?: (v: string) => boolean user_identifier_CustomCheck?: (v: string) => boolean } @@ -3284,6 +3286,12 @@ export const PayAppUserInvoiceRequestValidate = (o?: PayAppUserInvoiceRequest, o if ((o.debit_npub || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('debit_npub')) && typeof o.debit_npub !== 'string') return new Error(`${path}.debit_npub: is not a string`) if (opts.debit_npub_CustomCheck && !opts.debit_npub_CustomCheck(o.debit_npub)) return new Error(`${path}.debit_npub: custom check failed`) + if (typeof o.expected_fees === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('expected_fees')) { + const expected_feesErr = CumulativeFeesValidate(o.expected_fees, opts.expected_fees_Options, `${path}.expected_fees`) + if (expected_feesErr !== null) return expected_feesErr + } + + if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`) if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`) @@ -3296,14 +3304,16 @@ export const PayAppUserInvoiceRequestValidate = (o?: PayAppUserInvoiceRequest, o export type PayInvoiceRequest = { amount: number debit_npub?: string + expected_fees?: CumulativeFees invoice: string } -export type PayInvoiceRequestOptionalField = 'debit_npub' -export const PayInvoiceRequestOptionalFields: PayInvoiceRequestOptionalField[] = ['debit_npub'] +export type PayInvoiceRequestOptionalField = 'debit_npub' | 'expected_fees' +export const PayInvoiceRequestOptionalFields: PayInvoiceRequestOptionalField[] = ['debit_npub', 'expected_fees'] export type PayInvoiceRequestOptions = OptionsBaseMessage & { checkOptionalsAreSet?: PayInvoiceRequestOptionalField[] amount_CustomCheck?: (v: number) => boolean debit_npub_CustomCheck?: (v?: string) => boolean + expected_fees_Options?: CumulativeFeesOptions invoice_CustomCheck?: (v: string) => boolean } export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoiceRequestOptions = {}, path: string = 'PayInvoiceRequest::root.'): Error | null => { @@ -3316,6 +3326,12 @@ export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoic if ((o.debit_npub || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('debit_npub')) && typeof o.debit_npub !== 'string') return new Error(`${path}.debit_npub: is not a string`) if (opts.debit_npub_CustomCheck && !opts.debit_npub_CustomCheck(o.debit_npub)) return new Error(`${path}.debit_npub: custom check failed`) + if (typeof o.expected_fees === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('expected_fees')) { + const expected_feesErr = CumulativeFeesValidate(o.expected_fees, opts.expected_fees_Options, `${path}.expected_fees`) + if (expected_feesErr !== null) return expected_feesErr + } + + if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`) if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`) diff --git a/proto/service/structs.proto b/proto/service/structs.proto index bb95fe3e..f22e2c5d 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -390,6 +390,7 @@ message PayAppUserInvoiceRequest { string invoice = 2; int64 amount = 3; optional string debit_npub = 4; + optional CumulativeFees expected_fees = 5; } message SendAppUserToAppUserPaymentRequest { @@ -466,6 +467,7 @@ message PayInvoiceRequest{ string invoice = 1; int64 amount = 2; optional string debit_npub = 3; + optional CumulativeFees expected_fees = 4; } message PayInvoiceResponse{ diff --git a/src/services/main/appUserManager.ts b/src/services/main/appUserManager.ts index 75498b24..bf75246c 100644 --- a/src/services/main/appUserManager.ts +++ b/src/services/main/appUserManager.ts @@ -107,6 +107,7 @@ export default class { invoice: req.invoice, user_identifier: ctx.app_user_id, debit_npub: req.debit_npub, + expected_fees: req.expected_fees, }) } diff --git a/src/services/main/liquidityProvider.ts b/src/services/main/liquidityProvider.ts index 7789a5e3..3774c143 100644 --- a/src/services/main/liquidityProvider.ts +++ b/src/services/main/liquidityProvider.ts @@ -170,8 +170,8 @@ export class LiquidityProvider { return Object.values(this.pendingPayments).reduce((a, b) => a + b, 0) } - GetServiceFee = (amount: number) => { - const fees = this.GetFees() + GetServiceFee = (amount: number, f?: Types.CumulativeFees) => { + const fees = f ? f : this.GetFees() const serviceFeeRate = fees.serviceFeeBps / 10000 const serviceFee = Math.ceil(serviceFeeRate * amount) return Math.max(serviceFee, fees.networkFeeFixed) @@ -221,7 +221,8 @@ export class LiquidityProvider { if (!this.IsReady()) { throw new Error("liquidity provider is not ready yet, disabled or unreachable") } - const providerServiceFee = this.GetServiceFee(decodedAmount) + const fees = this.GetFees() + const providerServiceFee = this.GetServiceFee(decodedAmount, fees) this.pendingPayments[invoice] = decodedAmount + providerServiceFee const timeout = setTimeout(() => { if (!this.pendingPaymentsAck[invoice]) { @@ -231,8 +232,9 @@ export class LiquidityProvider { this.lastSeenBeacon = 0 }, 1000 * 10) this.pendingPaymentsAck[invoice] = true - const res = await this.client.PayInvoice({ invoice, amount: 0 }) + const res = await this.client.PayInvoice({ invoice, amount: 0, expected_fees: fees }) delete this.pendingPaymentsAck[invoice] + clearTimeout(timeout) if (res.status === 'ERROR') { this.log("error paying invoice", res.reason) throw new Error(res.reason) diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index f4ee41c2..5366b364 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -273,6 +273,14 @@ export default class { if (maybeBanned.locked) { throw new Error("user is banned, cannot send payment") } + if (req.expected_fees) { + const { networkFeeFixed, serviceFeeBps } = req.expected_fees + const serviceFixed = this.settings.getSettings().lndSettings.feeFixedLimit + const serviceBps = this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps + if (serviceFixed !== networkFeeFixed || serviceBps !== serviceFeeBps) { + throw new Error("fees do not match the expected fees") + } + } const decoded = await this.lnd.DecodeInvoice(req.invoice) if (decoded.numSatoshis !== 0 && req.amount !== 0) { throw new Error("invoice has value, do not provide amount the the request")