fixes and cleanup

This commit is contained in:
boufni95 2025-12-18 20:45:47 +00:00
parent 81229b3385
commit 824e98f007
11 changed files with 52 additions and 112 deletions

View file

@ -1169,8 +1169,8 @@ The nostr server will send back a message response, and inside the body there wi
- __invitation_link__: _string_ - __invitation_link__: _string_
### CumulativeFees ### CumulativeFees
- __outboundFeeFloor__: _number_
- __serviceFeeBps__: _number_ - __serviceFeeBps__: _number_
- __serviceFeeFloor__: _number_
### DebitAuthorization ### DebitAuthorization
- __authorized__: _boolean_ - __authorized__: _boolean_

View file

@ -230,8 +230,8 @@ type CreateOneTimeInviteLinkResponse struct {
Invitation_link string `json:"invitation_link"` Invitation_link string `json:"invitation_link"`
} }
type CumulativeFees struct { type CumulativeFees struct {
Outboundfeefloor int64 `json:"outboundFeeFloor"`
Servicefeebps int64 `json:"serviceFeeBps"` Servicefeebps int64 `json:"serviceFeeBps"`
Servicefeefloor int64 `json:"serviceFeeFloor"`
} }
type DebitAuthorization struct { type DebitAuthorization struct {
Authorized bool `json:"authorized"` Authorized bool `json:"authorized"`

View file

@ -1303,25 +1303,25 @@ export const CreateOneTimeInviteLinkResponseValidate = (o?: CreateOneTimeInviteL
} }
export type CumulativeFees = { export type CumulativeFees = {
outboundFeeFloor: number
serviceFeeBps: number serviceFeeBps: number
serviceFeeFloor: number
} }
export const CumulativeFeesOptionalFields: [] = [] export const CumulativeFeesOptionalFields: [] = []
export type CumulativeFeesOptions = OptionsBaseMessage & { export type CumulativeFeesOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: [] checkOptionalsAreSet?: []
outboundFeeFloor_CustomCheck?: (v: number) => boolean
serviceFeeBps_CustomCheck?: (v: number) => boolean serviceFeeBps_CustomCheck?: (v: number) => boolean
serviceFeeFloor_CustomCheck?: (v: number) => boolean
} }
export const CumulativeFeesValidate = (o?: CumulativeFees, opts: CumulativeFeesOptions = {}, path: string = 'CumulativeFees::root.'): Error | null => { export const CumulativeFeesValidate = (o?: CumulativeFees, opts: CumulativeFeesOptions = {}, path: string = 'CumulativeFees::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.outboundFeeFloor !== 'number') return new Error(`${path}.outboundFeeFloor: is not a number`)
if (opts.outboundFeeFloor_CustomCheck && !opts.outboundFeeFloor_CustomCheck(o.outboundFeeFloor)) return new Error(`${path}.outboundFeeFloor: custom check failed`)
if (typeof o.serviceFeeBps !== 'number') return new Error(`${path}.serviceFeeBps: is not a number`) if (typeof o.serviceFeeBps !== 'number') return new Error(`${path}.serviceFeeBps: is not a number`)
if (opts.serviceFeeBps_CustomCheck && !opts.serviceFeeBps_CustomCheck(o.serviceFeeBps)) return new Error(`${path}.serviceFeeBps: custom check failed`) if (opts.serviceFeeBps_CustomCheck && !opts.serviceFeeBps_CustomCheck(o.serviceFeeBps)) return new Error(`${path}.serviceFeeBps: custom check failed`)
if (typeof o.serviceFeeFloor !== 'number') return new Error(`${path}.serviceFeeFloor: is not a number`)
if (opts.serviceFeeFloor_CustomCheck && !opts.serviceFeeFloor_CustomCheck(o.serviceFeeFloor)) return new Error(`${path}.serviceFeeFloor: custom check failed`)
return null return null
} }

View file

@ -857,7 +857,7 @@ message SwapsList {
} }
message CumulativeFees { message CumulativeFees {
int64 outboundFeeFloor = 2; int64 serviceFeeFloor = 2;
int64 serviceFeeBps = 3; int64 serviceFeeBps = 3;
} }

View file

@ -434,7 +434,7 @@ export default class {
// Force use of provider when bypass is enabled // Force use of provider when bypass is enabled
const mustUseProvider = this.liquidProvider.getSettings().useOnlyLiquidityProvider || useProvider const mustUseProvider = this.liquidProvider.getSettings().useOnlyLiquidityProvider || useProvider
if (mustUseProvider) { if (mustUseProvider) {
const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from) const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from, feeLimit)
const providerDst = this.liquidProvider.GetProviderDestination() const providerDst = this.liquidProvider.GetProviderDestination()
return { feeSat: res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst } return { feeSat: res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst }
} }

View file

@ -69,14 +69,14 @@ export default class {
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
} }
const nostrSettings = this.settings.getSettings().nostrRelaySettings const nostrSettings = this.settings.getSettings().nostrRelaySettings
const { max, outboundFeeFloor, serviceFeeBps } = this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats) const { max, serviceFeeFloor, serviceFeeBps } = this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats)
return { return {
userId: ctx.user_id, userId: ctx.user_id,
balance: user.balance_sats, balance: user.balance_sats,
max_withdrawable: max, max_withdrawable: max,
user_identifier: appUser.identifier, user_identifier: appUser.identifier,
network_max_fee_bps: 0, network_max_fee_bps: 0,
network_max_fee_fixed: outboundFeeFloor, network_max_fee_fixed: serviceFeeFloor,
service_fee_bps: serviceFeeBps, service_fee_bps: serviceFeeBps,
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: appUser.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }), noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: appUser.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }), ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),

View file

@ -154,7 +154,7 @@ export default class {
const ndebitString = ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }) const ndebitString = ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] })
log("🔗 [DEBUG] Generated ndebit for user", { userId: u.user.user_id, ndebit: ndebitString }) log("🔗 [DEBUG] Generated ndebit for user", { userId: u.user.user_id, ndebit: ndebitString })
const { max, outboundFeeFloor, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats) const { max, serviceFeeFloor, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats)
return { return {
identifier: u.identifier, identifier: u.identifier,
info: { info: {
@ -163,7 +163,7 @@ export default class {
max_withdrawable: max, max_withdrawable: max,
user_identifier: u.identifier, user_identifier: u.identifier,
network_max_fee_bps: 0, network_max_fee_bps: 0,
network_max_fee_fixed: outboundFeeFloor, network_max_fee_fixed: serviceFeeFloor,
service_fee_bps: serviceFeeBps, service_fee_bps: serviceFeeBps,
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: u.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }), noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: u.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }), ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
@ -214,14 +214,14 @@ export default class {
const app = await this.storage.applicationStorage.GetApplication(appId) const app = await this.storage.applicationStorage.GetApplication(appId)
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier) const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
const nostrSettings = this.settings.getSettings().nostrRelaySettings const nostrSettings = this.settings.getSettings().nostrRelaySettings
const { max, outboundFeeFloor, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats) const { max, serviceFeeFloor, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
return { return {
max_withdrawable: max, identifier: req.user_identifier, info: { max_withdrawable: max, identifier: req.user_identifier, info: {
userId: user.user.user_id, balance: user.user.balance_sats, userId: user.user.user_id, balance: user.user.balance_sats,
max_withdrawable: max, max_withdrawable: max,
user_identifier: user.identifier, user_identifier: user.identifier,
network_max_fee_bps: 0, network_max_fee_bps: 0,
network_max_fee_fixed: outboundFeeFloor, network_max_fee_fixed: serviceFeeFloor,
service_fee_bps: serviceFeeBps, service_fee_bps: serviceFeeBps,
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: user.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }), noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: user.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }), ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),

View file

@ -130,7 +130,7 @@ export class LiquidityProvider {
return res return res
} }
this.feesCache = { this.feesCache = {
outboundFeeFloor: res.network_max_fee_fixed, serviceFeeFloor: res.network_max_fee_fixed,
serviceFeeBps: res.service_fee_bps serviceFeeBps: res.service_fee_bps
} }
this.latestReceivedBalance = res.balance this.latestReceivedBalance = res.balance
@ -151,11 +151,11 @@ export class LiquidityProvider {
return 0 return 0
} }
const balance = this.latestReceivedBalance const balance = this.latestReceivedBalance
const { outboundFeeFloor, serviceFeeBps } = this.feesCache const { serviceFeeFloor, serviceFeeBps } = this.feesCache
const div = 1 + (serviceFeeBps / 10000) const div = 1 + (serviceFeeBps / 10000)
const maxWithoutFixed = Math.floor(balance / div) const maxWithoutFixed = Math.floor(balance / div)
const fee = balance - maxWithoutFixed const fee = balance - maxWithoutFixed
return balance - Math.max(fee, outboundFeeFloor) return balance - Math.max(fee, serviceFeeFloor)
} }
GetLatestBalance = () => { GetLatestBalance = () => {
@ -173,7 +173,7 @@ export class LiquidityProvider {
const fees = f ? f : this.GetFees() const fees = f ? f : this.GetFees()
const serviceFeeRate = fees.serviceFeeBps / 10000 const serviceFeeRate = fees.serviceFeeBps / 10000
const serviceFee = Math.ceil(serviceFeeRate * amount) const serviceFee = Math.ceil(serviceFeeRate * amount)
return Math.max(serviceFee, fees.outboundFeeFloor) return Math.max(serviceFee, fees.serviceFeeFloor)
} }
CanProviderPay = async (amount: number, localServiceFee: number): Promise<boolean> => { CanProviderPay = async (amount: number, localServiceFee: number): Promise<boolean> => {
@ -215,13 +215,16 @@ export class LiquidityProvider {
} }
PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system') => { PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system', feeLimit?: number) => {
try { try {
if (!this.IsReady()) { if (!this.IsReady()) {
throw new Error("liquidity provider is not ready yet, disabled or unreachable") throw new Error("liquidity provider is not ready yet, disabled or unreachable")
} }
const fees = this.GetFees() const fees = this.GetFees()
const providerServiceFee = this.GetServiceFee(decodedAmount, fees) const providerServiceFee = this.GetServiceFee(decodedAmount, fees)
if (feeLimit && providerServiceFee > feeLimit) {
throw new Error("provider service fee is greater than the fee limit")
}
this.pendingPayments[invoice] = decodedAmount + providerServiceFee this.pendingPayments[invoice] = decodedAmount + providerServiceFee
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
if (!this.pendingPaymentsAck[invoice]) { if (!this.pendingPaymentsAck[invoice]) {

View file

@ -173,7 +173,7 @@ export default class {
getReceiveServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => { getReceiveServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => {
switch (action) { switch (action) {
case Types.UserOperationType.INCOMING_TX: case Types.UserOperationType.INCOMING_TX:
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingTxFee * amount) return 0
case Types.UserOperationType.INCOMING_INVOICE: case Types.UserOperationType.INCOMING_INVOICE:
// Incoming invoice fees are always 0 (not configurable) // Incoming invoice fees are always 0 (not configurable)
return 0 return 0
@ -197,18 +197,14 @@ export default class {
getSendServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => { getSendServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => {
switch (action) { switch (action) {
case Types.UserOperationType.OUTGOING_TX: case Types.UserOperationType.OUTGOING_TX:
// Internal address payment, treat like user-to-user throw new Error("OUTGOING_TX is not a valid send service fee action")
if (managedUser) {
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
}
return Math.ceil(this.settings.getSettings().serviceFeeSettings.rootToUserFee * amount)
case Types.UserOperationType.OUTGOING_INVOICE: case Types.UserOperationType.OUTGOING_INVOICE:
const fee = this.getInvoicePaymentServiceFee(amount, managedUser) const fee = this.getInvoicePaymentServiceFee(amount, managedUser)
// Only managed users pay the service fee floor // Only managed users pay the service fee floor
if (!managedUser) { if (!managedUser) {
return 0 return 0
} }
return Math.max(fee, this.settings.getSettings().lndSettings.serviceFeeFloor) return Math.max(fee, this.settings.getSettings().serviceFeeSettings.serviceFeeFloor)
case Types.UserOperationType.OUTGOING_USER_TO_USER: case Types.UserOperationType.OUTGOING_USER_TO_USER:
if (managedUser) { if (managedUser) {
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount) return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
@ -272,18 +268,17 @@ export default class {
} }
GetFees = (): Types.CumulativeFees => { GetFees = (): Types.CumulativeFees => {
const { serviceFeeBps } = this.settings.getSettings().serviceFeeSettings const { serviceFeeBps, serviceFeeFloor } = this.settings.getSettings().serviceFeeSettings
const { serviceFeeFloor } = this.settings.getSettings().lndSettings return { serviceFeeFloor, serviceFeeBps }
return { outboundFeeFloor: serviceFeeFloor, serviceFeeBps: serviceFeeBps }
} }
GetMaxPayableInvoice(balance: number): Types.CumulativeFees & { max: number } { GetMaxPayableInvoice(balance: number): Types.CumulativeFees & { max: number } {
const { outboundFeeFloor, serviceFeeBps } = this.GetFees() const { serviceFeeFloor, serviceFeeBps } = this.GetFees()
const div = 1 + (serviceFeeBps / 10000) const div = 1 + (serviceFeeBps / 10000)
const maxWithoutFixed = Math.floor(balance / div) const maxWithoutFixed = Math.floor(balance / div)
const fee = balance - maxWithoutFixed const fee = balance - maxWithoutFixed
const max = balance - Math.max(fee, outboundFeeFloor) const max = balance - Math.max(fee, serviceFeeFloor)
return { max, outboundFeeFloor, serviceFeeBps } return { max, serviceFeeFloor, serviceFeeBps }
} }
async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> { async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> {
const decoded = await this.lnd.DecodeInvoice(req.invoice) const decoded = await this.lnd.DecodeInvoice(req.invoice)
@ -299,10 +294,10 @@ export default class {
throw new Error("user is banned, cannot send payment") throw new Error("user is banned, cannot send payment")
} }
if (req.expected_fees) { if (req.expected_fees) {
const { outboundFeeFloor, serviceFeeBps } = req.expected_fees const { serviceFeeFloor, serviceFeeBps } = req.expected_fees
const serviceFixed = this.settings.getSettings().lndSettings.serviceFeeFloor const serviceFixed = this.settings.getSettings().serviceFeeSettings.serviceFeeFloor
const serviceBps = this.settings.getSettings().serviceFeeSettings.serviceFeeBps const serviceBps = this.settings.getSettings().serviceFeeSettings.serviceFeeBps
if (serviceFixed !== outboundFeeFloor || serviceBps !== serviceFeeBps) { if (serviceFixed !== serviceFeeFloor || serviceBps !== serviceFeeBps) {
throw new Error("fees do not match the expected fees") throw new Error("fees do not match the expected fees")
} }
} }
@ -522,21 +517,11 @@ export default class {
this.swaps.reverseSwaps.SubscribeToTransactionSwap(data, result => { this.swaps.reverseSwaps.SubscribeToTransactionSwap(data, result => {
swapResult = result swapResult = result
}) })
// Validate that the invoice amount matches what was quoted
const decoded = await this.lnd.DecodeInvoice(txSwap.invoice)
if (decoded.numSatoshis !== txSwap.invoice_amount) {
throw new Error("swap invoice amount does not match quote")
}
const fees = this.GetFees()
let payment: Types.PayInvoiceResponse let payment: Types.PayInvoiceResponse
try { try {
payment = await this.PayInvoice(ctx.user_id, { payment = await this.PayInvoice(ctx.user_id, {
amount: 0, amount: 0,
invoice: txSwap.invoice, invoice: txSwap.invoice
expected_fees: {
outboundFeeFloor: fees.outboundFeeFloor,
serviceFeeBps: fees.serviceFeeBps
}
}, app, { swapOperationId: req.swap_operation_id }) }, app, { swapOperationId: req.swap_operation_id })
if (!swapResult.ok) { if (!swapResult.ok) {
this.log("invoice payment successful, but swap failed") this.log("invoice payment successful, but swap failed")
@ -572,7 +557,7 @@ export default class {
const { blockHeight } = await this.lnd.GetInfo() const { blockHeight } = await this.lnd.GetInfo()
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id) const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
const isManagedUser = ctx.user_id !== app.owner.user_id const isManagedUser = ctx.user_id !== app.owner.user_id
const serviceFee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_TX, req.amoutSats, isManagedUser) const serviceFee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, req.amoutSats, isManagedUser)
const txId = crypto.randomBytes(32).toString("hex") const txId = crypto.randomBytes(32).toString("hex")
const addressData = `${req.address}:${txId}` const addressData = `${req.address}:${txId}`

View file

@ -3,38 +3,22 @@ import os from 'os'
import path from 'path' import path from 'path'
export type ServiceFeeSettings = { export type ServiceFeeSettings = {
incomingTxFee: number
serviceFee: number serviceFee: number
serviceFeeBps: number serviceFeeBps: number
serviceFeeFloor: number
userToUserFee: number userToUserFee: number
rootToUserFee: number rootToUserFee: number
} }
export const LoadServiceFeeSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): ServiceFeeSettings => { export const LoadServiceFeeSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): ServiceFeeSettings => {
// Support both old and new env var names for backward compatibility (new name takes precedence) const oldServiceFeeBps = chooseEnvInt("OUTGOING_INVOICE_FEE_USER_BPS", dbEnv, 60, addToDb)
// Check if new name exists first (in process.env or dbEnv) const serviceFeeBps = chooseEnvInt("SERVICE_FEE_BPS", dbEnv, oldServiceFeeBps, addToDb)
const newExists = process.env["SERVICE_FEE_BPS"] !== undefined || dbEnv["SERVICE_FEE_BPS"] !== undefined const oldRoutingFeeFloor = chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 10, addToDb)
let serviceFeeBps: number const serviceFeeFloor = chooseEnvInt("SERVICE_FEE_FLOOR_SATS", dbEnv, oldRoutingFeeFloor, addToDb)
if (newExists) {
// New name exists, use it
serviceFeeBps = chooseEnvInt("SERVICE_FEE_BPS", dbEnv, 60, addToDb)
} else {
// New name doesn't exist, check old name for backward compatibility
const oldExists = process.env["OUTGOING_INVOICE_FEE_USER_BPS"] !== undefined || dbEnv["OUTGOING_INVOICE_FEE_USER_BPS"] !== undefined
if (oldExists) {
// Old name exists, use it and migrate to new name in DB
const oldValue = chooseEnvInt("OUTGOING_INVOICE_FEE_USER_BPS", dbEnv, 60) // Don't add old name to DB
serviceFeeBps = oldValue
if (addToDb) addToDb("SERVICE_FEE_BPS", oldValue.toString()) // Migrate to new name
} else {
// Neither exists, use default with new name
serviceFeeBps = chooseEnvInt("SERVICE_FEE_BPS", dbEnv, 60, addToDb)
}
}
return { return {
incomingTxFee: 0, // Not configurable, always 0 serviceFeeBps,
serviceFeeBps: serviceFeeBps,
serviceFee: serviceFeeBps / 10000, serviceFee: serviceFeeBps / 10000,
serviceFeeFloor,
userToUserFee: chooseEnvInt("TX_FEE_INTERNAL_USER_BPS", dbEnv, 0, addToDb) / 10000, userToUserFee: chooseEnvInt("TX_FEE_INTERNAL_USER_BPS", dbEnv, 0, addToDb) / 10000,
rootToUserFee: chooseEnvInt("TX_FEE_INTERNAL_ROOT_BPS", dbEnv, 0, addToDb) / 10000, rootToUserFee: chooseEnvInt("TX_FEE_INTERNAL_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
} }
@ -91,7 +75,6 @@ const networks = ['mainnet', 'testnet', 'regtest'] as const
export type BTCNetwork = (typeof networks)[number] export type BTCNetwork = (typeof networks)[number]
export type LndSettings = { export type LndSettings = {
lndLogDir: string lndLogDir: string
serviceFeeFloor: number
routingFeeLimitBps: number routingFeeLimitBps: number
routingFeeFloor: number routingFeeFloor: number
mockLnd: boolean mockLnd: boolean
@ -125,42 +108,11 @@ export const LoadLndNodeSettingsFromEnv = (dbEnv: Record<string, string | undefi
export const LoadLndSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LndSettings => { export const LoadLndSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LndSettings => {
const network = chooseEnv('BTC_NETWORK', dbEnv, 'mainnet', addToDb) as BTCNetwork const network = chooseEnv('BTC_NETWORK', dbEnv, 'mainnet', addToDb) as BTCNetwork
const oldRoutingFeeFloor = chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 5, addToDb)
// Routing fee floor: new name takes precedence, fall back to old name for backward compatibility const routingFeeFloor = chooseEnvInt('ROUTING_FEE_FLOOR_SATS', dbEnv, oldRoutingFeeFloor, addToDb)
const routingFeeFloorNewExists = process.env['ROUTING_FEE_FLOOR_SATS'] !== undefined || dbEnv['ROUTING_FEE_FLOOR_SATS'] !== undefined
let routingFeeFloor: number
if (routingFeeFloorNewExists) {
routingFeeFloor = chooseEnvInt('ROUTING_FEE_FLOOR_SATS', dbEnv, 5, addToDb)
} else {
const routingFeeFloorOldExists = process.env['OUTBOUND_MAX_FEE_EXTRA_SATS'] !== undefined || dbEnv['OUTBOUND_MAX_FEE_EXTRA_SATS'] !== undefined
if (routingFeeFloorOldExists) {
const oldValue = chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 5) // Don't add old name to DB
routingFeeFloor = oldValue
if (addToDb) addToDb('ROUTING_FEE_FLOOR_SATS', oldValue.toString()) // Migrate to new name
} else {
routingFeeFloor = chooseEnvInt('ROUTING_FEE_FLOOR_SATS', dbEnv, 5, addToDb)
}
}
// Service fee floor: new name takes precedence, fall back to old name for backward compatibility
const serviceFeeFloorNewExists = process.env['SERVICE_FEE_FLOOR_SATS'] !== undefined || dbEnv['SERVICE_FEE_FLOOR_SATS'] !== undefined
let serviceFeeFloor: number
if (serviceFeeFloorNewExists) {
serviceFeeFloor = chooseEnvInt('SERVICE_FEE_FLOOR_SATS', dbEnv, 10, addToDb)
} else {
const serviceFeeFloorOldExists = process.env['OUTBOUND_MAX_FEE_EXTRA_SATS'] !== undefined || dbEnv['OUTBOUND_MAX_FEE_EXTRA_SATS'] !== undefined
if (serviceFeeFloorOldExists) {
const oldValue = chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 10) // Don't add old name to DB
serviceFeeFloor = oldValue
if (addToDb) addToDb('SERVICE_FEE_FLOOR_SATS', oldValue.toString()) // Migrate to new name
} else {
serviceFeeFloor = chooseEnvInt('SERVICE_FEE_FLOOR_SATS', dbEnv, 10, addToDb)
}
}
const routingFeeLimitBps = chooseEnvInt('ROUTING_FEE_LIMIT_BPS', dbEnv, 50, addToDb) const routingFeeLimitBps = chooseEnvInt('ROUTING_FEE_LIMIT_BPS', dbEnv, 50, addToDb)
return { return {
lndLogDir: chooseEnv('LND_LOG_DIR', dbEnv, resolveHome("/.lnd/logs/bitcoin/mainnet/lnd.log"), addToDb), lndLogDir: chooseEnv('LND_LOG_DIR', dbEnv, resolveHome("/.lnd/logs/bitcoin/mainnet/lnd.log"), addToDb),
serviceFeeFloor,
routingFeeLimitBps, routingFeeLimitBps,
routingFeeFloor, routingFeeFloor,
mockLnd: false, mockLnd: false,

View file

@ -59,7 +59,7 @@ export default class SettingsManager {
const { serviceFeeSettings, lndSettings } = settings const { serviceFeeSettings, lndSettings } = settings
const serviceFeeBps = serviceFeeSettings.serviceFeeBps const serviceFeeBps = serviceFeeSettings.serviceFeeBps
const routingFeeLimitBps = lndSettings.routingFeeLimitBps const routingFeeLimitBps = lndSettings.routingFeeLimitBps
const serviceFeeFloor = lndSettings.serviceFeeFloor const serviceFeeFloor = serviceFeeSettings.serviceFeeFloor
const routingFeeFloor = lndSettings.routingFeeFloor const routingFeeFloor = lndSettings.routingFeeFloor
if (routingFeeLimitBps > serviceFeeBps) { if (routingFeeLimitBps > serviceFeeBps) {