cleanup fee design
This commit is contained in:
parent
5cb1cd509d
commit
ad8cd91aad
3 changed files with 93 additions and 21 deletions
|
|
@ -175,10 +175,8 @@ export default class {
|
|||
case Types.UserOperationType.INCOMING_TX:
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingTxFee * amount)
|
||||
case Types.UserOperationType.INCOMING_INVOICE:
|
||||
if (appUser) {
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppUserInvoiceFee * amount)
|
||||
}
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppInvoiceFee * amount)
|
||||
// Incoming invoice fees are always 0 (not configurable)
|
||||
return 0
|
||||
case Types.UserOperationType.INCOMING_USER_TO_USER:
|
||||
if (appUser) {
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
||||
|
|
@ -202,7 +200,7 @@ export default class {
|
|||
throw new Error("Sending a transaction is not supported")
|
||||
case Types.UserOperationType.OUTGOING_INVOICE:
|
||||
const fee = this.getInvoicePaymentServiceFee(amount, appUser)
|
||||
return Math.max(fee, this.settings.getSettings().lndSettings.outboundFeeFloor)
|
||||
return Math.max(fee, this.settings.getSettings().lndSettings.serviceFeeFloor)
|
||||
case Types.UserOperationType.OUTGOING_USER_TO_USER:
|
||||
if (appUser) {
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
||||
|
|
@ -213,6 +211,12 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
getRoutingFeeLimit = (amount: number): number => {
|
||||
const { routingFeeLimitBps, routingFeeFloor } = this.settings.getSettings().lndSettings
|
||||
const limit = Math.floor(amount * routingFeeLimitBps / 10000)
|
||||
return Math.max(limit, routingFeeFloor)
|
||||
}
|
||||
|
||||
async SetMockInvoiceAsPaid(req: Types.SetMockInvoiceAsPaidRequest) {
|
||||
if (!this.settings.getSettings().lndSettings.mockLnd) {
|
||||
throw new Error("mock disabled, cannot set invoice as paid")
|
||||
|
|
@ -261,8 +265,8 @@ export default class {
|
|||
|
||||
GetFees = (): Types.CumulativeFees => {
|
||||
const { outgoingAppUserInvoiceFeeBps } = this.settings.getSettings().serviceFeeSettings
|
||||
const { outboundFeeFloor } = this.settings.getSettings().lndSettings
|
||||
return { outboundFeeFloor, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
|
||||
const { serviceFeeFloor } = this.settings.getSettings().lndSettings
|
||||
return { outboundFeeFloor: serviceFeeFloor, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
|
||||
}
|
||||
|
||||
GetMaxPayableInvoice(balance: number): Types.CumulativeFees & { max: number } {
|
||||
|
|
@ -288,7 +292,7 @@ export default class {
|
|||
}
|
||||
if (req.expected_fees) {
|
||||
const { outboundFeeFloor, serviceFeeBps } = req.expected_fees
|
||||
const serviceFixed = this.settings.getSettings().lndSettings.outboundFeeFloor
|
||||
const serviceFixed = this.settings.getSettings().lndSettings.serviceFeeFloor
|
||||
const serviceBps = this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps
|
||||
if (serviceFixed !== outboundFeeFloor || serviceBps !== serviceFeeBps) {
|
||||
throw new Error("fees do not match the expected fees")
|
||||
|
|
@ -364,6 +368,7 @@ export default class {
|
|||
|
||||
const { amountForLnd, payAmount, serviceFee } = amounts
|
||||
const totalAmountToDecrement = payAmount + serviceFee
|
||||
const routingFeeLimit = this.getRoutingFeeLimit(payAmount)
|
||||
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount, serviceFee)
|
||||
const provider = use === 'provider' ? this.lnd.liquidProvider.GetProviderDestination() : undefined
|
||||
const pendingPayment = await this.storage.StartTransaction(async tx => {
|
||||
|
|
@ -375,7 +380,7 @@ export default class {
|
|||
const op = this.newInvoicePaymentOperation({ invoice, opId, amount: payAmount, networkFee: 0, serviceFee: serviceFee, confirmed: false })
|
||||
optionals.ack?.(op)
|
||||
try {
|
||||
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, serviceFee, payAmount, { useProvider: use === 'provider', from: 'user' }, index => {
|
||||
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit, payAmount, { useProvider: use === 'provider', from: 'user' }, index => {
|
||||
this.storage.paymentStorage.SetExternalPaymentIndex(pendingPayment.serial_id, index)
|
||||
})
|
||||
await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, payment.feeSat, serviceFee, true, payment.providerDst)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import path from 'path'
|
|||
export type ServiceFeeSettings = {
|
||||
incomingTxFee: number
|
||||
outgoingTxFee: number
|
||||
incomingAppInvoiceFee: number
|
||||
incomingAppUserInvoiceFee: number
|
||||
outgoingAppInvoiceFee: number
|
||||
outgoingAppUserInvoiceFee: number
|
||||
outgoingAppUserInvoiceFeeBps: number
|
||||
|
|
@ -15,15 +13,32 @@ export type ServiceFeeSettings = {
|
|||
}
|
||||
|
||||
export const LoadServiceFeeSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): ServiceFeeSettings => {
|
||||
const outgoingAppUserInvoiceFeeBps = chooseEnvInt("OUTGOING_INVOICE_FEE_USER_BPS", dbEnv, 0, addToDb)
|
||||
// Support both old and new env var names for backward compatibility (new name takes precedence)
|
||||
// Check if new name exists first (in process.env or dbEnv)
|
||||
const newExists = process.env["SERVICE_FEE_BPS"] !== undefined || dbEnv["SERVICE_FEE_BPS"] !== undefined
|
||||
let serviceFeeBps: number
|
||||
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 {
|
||||
incomingTxFee: chooseEnvInt("INCOMING_CHAIN_FEE_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
incomingTxFee: 0, // Not configurable, always 0
|
||||
outgoingTxFee: chooseEnvInt("OUTGOING_CHAIN_FEE_ROOT_BPS", dbEnv, 60, addToDb) / 10000,
|
||||
incomingAppInvoiceFee: chooseEnvInt("INCOMING_INVOICE_FEE_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
outgoingAppInvoiceFee: chooseEnvInt("OUTGOING_INVOICE_FEE_ROOT_BPS", dbEnv, 60, addToDb) / 10000,
|
||||
incomingAppUserInvoiceFee: chooseEnvInt("INCOMING_INVOICE_FEE_USER_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
outgoingAppUserInvoiceFeeBps,
|
||||
outgoingAppUserInvoiceFee: outgoingAppUserInvoiceFeeBps / 10000,
|
||||
outgoingAppUserInvoiceFeeBps: serviceFeeBps,
|
||||
outgoingAppUserInvoiceFee: serviceFeeBps / 10000,
|
||||
userToUserFee: chooseEnvInt("TX_FEE_INTERNAL_USER_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
appToUserFee: chooseEnvInt("TX_FEE_INTERNAL_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
}
|
||||
|
|
@ -80,7 +95,9 @@ const networks = ['mainnet', 'testnet', 'regtest'] as const
|
|||
export type BTCNetwork = (typeof networks)[number]
|
||||
export type LndSettings = {
|
||||
lndLogDir: string
|
||||
outboundFeeFloor: number
|
||||
serviceFeeFloor: number
|
||||
routingFeeLimitBps: number
|
||||
routingFeeFloor: number
|
||||
mockLnd: boolean
|
||||
network: BTCNetwork
|
||||
}
|
||||
|
|
@ -112,11 +129,44 @@ export const LoadLndNodeSettingsFromEnv = (dbEnv: Record<string, string | undefi
|
|||
|
||||
export const LoadLndSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LndSettings => {
|
||||
const network = chooseEnv('BTC_NETWORK', dbEnv, 'mainnet', addToDb) as BTCNetwork
|
||||
const limitOld = chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 10, addToDb)
|
||||
const outboundFeeFloor = chooseEnvInt('OUTBOUND_FEE_FLOOR_SATS', dbEnv, limitOld, addToDb)
|
||||
|
||||
// Routing fee floor: new name takes precedence, fall back to old name for backward compatibility
|
||||
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)
|
||||
return {
|
||||
lndLogDir: chooseEnv('LND_LOG_DIR', dbEnv, resolveHome("/.lnd/logs/bitcoin/mainnet/lnd.log"), addToDb),
|
||||
outboundFeeFloor,
|
||||
serviceFeeFloor,
|
||||
routingFeeLimitBps,
|
||||
routingFeeFloor,
|
||||
mockLnd: false,
|
||||
network: networks.includes(network) ? network : 'mainnet'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,9 +50,26 @@ export default class SettingsManager {
|
|||
for (const key in toAdd) {
|
||||
await this.storage.settingsStorage.setDbEnvIFNeeded(key, toAdd[key])
|
||||
}
|
||||
// Validate fee configuration: routing fee limit must be <= service fee
|
||||
this.validateFeeSettings(this.settings)
|
||||
return this.settings
|
||||
}
|
||||
|
||||
private validateFeeSettings(settings: FullSettings): void {
|
||||
const { serviceFeeSettings, lndSettings } = settings
|
||||
const serviceFeeBps = serviceFeeSettings.outgoingAppUserInvoiceFeeBps
|
||||
const routingFeeLimitBps = lndSettings.routingFeeLimitBps
|
||||
const serviceFeeFloor = lndSettings.serviceFeeFloor
|
||||
const routingFeeFloor = lndSettings.routingFeeFloor
|
||||
|
||||
if (routingFeeLimitBps > serviceFeeBps) {
|
||||
throw new Error(`ROUTING_FEE_LIMIT_BPS (${routingFeeLimitBps}) must be <= SERVICE_FEE_BPS (${serviceFeeBps}) to ensure Pub keeps a spread`)
|
||||
}
|
||||
if (routingFeeFloor > serviceFeeFloor) {
|
||||
throw new Error(`ROUTING_FEE_FLOOR_SATS (${routingFeeFloor}) must be <= SERVICE_FEE_FLOOR_SATS (${serviceFeeFloor}) to ensure Pub keeps a spread`)
|
||||
}
|
||||
}
|
||||
|
||||
getStorageSettings(): StorageSettings {
|
||||
return this.storage.getStorageSettings()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue