service fee only
This commit is contained in:
parent
6985f51d99
commit
31751d83c3
12 changed files with 173 additions and 125 deletions
|
|
@ -1145,7 +1145,6 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- __invitation_link__: _string_
|
- __invitation_link__: _string_
|
||||||
|
|
||||||
### CumulativeFees
|
### CumulativeFees
|
||||||
- __networkFeeBps__: _number_
|
|
||||||
- __networkFeeFixed__: _number_
|
- __networkFeeFixed__: _number_
|
||||||
- __serviceFeeBps__: _number_
|
- __serviceFeeBps__: _number_
|
||||||
|
|
||||||
|
|
@ -1473,14 +1472,12 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
### PayAppUserInvoiceRequest
|
### PayAppUserInvoiceRequest
|
||||||
- __amount__: _number_
|
- __amount__: _number_
|
||||||
- __debit_npub__: _string_ *this field is optional
|
- __debit_npub__: _string_ *this field is optional
|
||||||
- __fee_limit_sats__: _number_ *this field is optional
|
|
||||||
- __invoice__: _string_
|
- __invoice__: _string_
|
||||||
- __user_identifier__: _string_
|
- __user_identifier__: _string_
|
||||||
|
|
||||||
### PayInvoiceRequest
|
### PayInvoiceRequest
|
||||||
- __amount__: _number_
|
- __amount__: _number_
|
||||||
- __debit_npub__: _string_ *this field is optional
|
- __debit_npub__: _string_ *this field is optional
|
||||||
- __fee_limit_sats__: _number_ *this field is optional
|
|
||||||
- __invoice__: _string_
|
- __invoice__: _string_
|
||||||
|
|
||||||
### PayInvoiceResponse
|
### PayInvoiceResponse
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,6 @@ type CreateOneTimeInviteLinkResponse struct {
|
||||||
Invitation_link string `json:"invitation_link"`
|
Invitation_link string `json:"invitation_link"`
|
||||||
}
|
}
|
||||||
type CumulativeFees struct {
|
type CumulativeFees struct {
|
||||||
Networkfeebps int64 `json:"networkFeeBps"`
|
|
||||||
Networkfeefixed int64 `json:"networkFeeFixed"`
|
Networkfeefixed int64 `json:"networkFeeFixed"`
|
||||||
Servicefeebps int64 `json:"serviceFeeBps"`
|
Servicefeebps int64 `json:"serviceFeeBps"`
|
||||||
}
|
}
|
||||||
|
|
@ -558,15 +557,13 @@ type PayAddressResponse struct {
|
||||||
type PayAppUserInvoiceRequest struct {
|
type PayAppUserInvoiceRequest struct {
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
Debit_npub string `json:"debit_npub"`
|
Debit_npub string `json:"debit_npub"`
|
||||||
Fee_limit_sats int64 `json:"fee_limit_sats"`
|
|
||||||
Invoice string `json:"invoice"`
|
Invoice string `json:"invoice"`
|
||||||
User_identifier string `json:"user_identifier"`
|
User_identifier string `json:"user_identifier"`
|
||||||
}
|
}
|
||||||
type PayInvoiceRequest struct {
|
type PayInvoiceRequest struct {
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
Debit_npub string `json:"debit_npub"`
|
Debit_npub string `json:"debit_npub"`
|
||||||
Fee_limit_sats int64 `json:"fee_limit_sats"`
|
Invoice string `json:"invoice"`
|
||||||
Invoice string `json:"invoice"`
|
|
||||||
}
|
}
|
||||||
type PayInvoiceResponse struct {
|
type PayInvoiceResponse struct {
|
||||||
Amount_paid int64 `json:"amount_paid"`
|
Amount_paid int64 `json:"amount_paid"`
|
||||||
|
|
|
||||||
|
|
@ -1295,14 +1295,12 @@ export const CreateOneTimeInviteLinkResponseValidate = (o?: CreateOneTimeInviteL
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CumulativeFees = {
|
export type CumulativeFees = {
|
||||||
networkFeeBps: number
|
|
||||||
networkFeeFixed: number
|
networkFeeFixed: number
|
||||||
serviceFeeBps: number
|
serviceFeeBps: number
|
||||||
}
|
}
|
||||||
export const CumulativeFeesOptionalFields: [] = []
|
export const CumulativeFeesOptionalFields: [] = []
|
||||||
export type CumulativeFeesOptions = OptionsBaseMessage & {
|
export type CumulativeFeesOptions = OptionsBaseMessage & {
|
||||||
checkOptionalsAreSet?: []
|
checkOptionalsAreSet?: []
|
||||||
networkFeeBps_CustomCheck?: (v: number) => boolean
|
|
||||||
networkFeeFixed_CustomCheck?: (v: number) => boolean
|
networkFeeFixed_CustomCheck?: (v: number) => boolean
|
||||||
serviceFeeBps_CustomCheck?: (v: number) => boolean
|
serviceFeeBps_CustomCheck?: (v: number) => boolean
|
||||||
}
|
}
|
||||||
|
|
@ -1310,9 +1308,6 @@ export const CumulativeFeesValidate = (o?: CumulativeFees, opts: CumulativeFeesO
|
||||||
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.networkFeeBps !== 'number') return new Error(`${path}.networkFeeBps: is not a number`)
|
|
||||||
if (opts.networkFeeBps_CustomCheck && !opts.networkFeeBps_CustomCheck(o.networkFeeBps)) return new Error(`${path}.networkFeeBps: custom check failed`)
|
|
||||||
|
|
||||||
if (typeof o.networkFeeFixed !== 'number') return new Error(`${path}.networkFeeFixed: is not a number`)
|
if (typeof o.networkFeeFixed !== 'number') return new Error(`${path}.networkFeeFixed: is not a number`)
|
||||||
if (opts.networkFeeFixed_CustomCheck && !opts.networkFeeFixed_CustomCheck(o.networkFeeFixed)) return new Error(`${path}.networkFeeFixed: custom check failed`)
|
if (opts.networkFeeFixed_CustomCheck && !opts.networkFeeFixed_CustomCheck(o.networkFeeFixed)) return new Error(`${path}.networkFeeFixed: custom check failed`)
|
||||||
|
|
||||||
|
|
@ -3267,17 +3262,15 @@ export const PayAddressResponseValidate = (o?: PayAddressResponse, opts: PayAddr
|
||||||
export type PayAppUserInvoiceRequest = {
|
export type PayAppUserInvoiceRequest = {
|
||||||
amount: number
|
amount: number
|
||||||
debit_npub?: string
|
debit_npub?: string
|
||||||
fee_limit_sats?: number
|
|
||||||
invoice: string
|
invoice: string
|
||||||
user_identifier: string
|
user_identifier: string
|
||||||
}
|
}
|
||||||
export type PayAppUserInvoiceRequestOptionalField = 'debit_npub' | 'fee_limit_sats'
|
export type PayAppUserInvoiceRequestOptionalField = 'debit_npub'
|
||||||
export const PayAppUserInvoiceRequestOptionalFields: PayAppUserInvoiceRequestOptionalField[] = ['debit_npub', 'fee_limit_sats']
|
export const PayAppUserInvoiceRequestOptionalFields: PayAppUserInvoiceRequestOptionalField[] = ['debit_npub']
|
||||||
export type PayAppUserInvoiceRequestOptions = OptionsBaseMessage & {
|
export type PayAppUserInvoiceRequestOptions = OptionsBaseMessage & {
|
||||||
checkOptionalsAreSet?: PayAppUserInvoiceRequestOptionalField[]
|
checkOptionalsAreSet?: PayAppUserInvoiceRequestOptionalField[]
|
||||||
amount_CustomCheck?: (v: number) => boolean
|
amount_CustomCheck?: (v: number) => boolean
|
||||||
debit_npub_CustomCheck?: (v?: string) => boolean
|
debit_npub_CustomCheck?: (v?: string) => boolean
|
||||||
fee_limit_sats_CustomCheck?: (v?: number) => boolean
|
|
||||||
invoice_CustomCheck?: (v: string) => boolean
|
invoice_CustomCheck?: (v: string) => boolean
|
||||||
user_identifier_CustomCheck?: (v: string) => boolean
|
user_identifier_CustomCheck?: (v: string) => boolean
|
||||||
}
|
}
|
||||||
|
|
@ -3291,9 +3284,6 @@ 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 ((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 (opts.debit_npub_CustomCheck && !opts.debit_npub_CustomCheck(o.debit_npub)) return new Error(`${path}.debit_npub: custom check failed`)
|
||||||
|
|
||||||
if ((o.fee_limit_sats || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('fee_limit_sats')) && typeof o.fee_limit_sats !== 'number') return new Error(`${path}.fee_limit_sats: is not a number`)
|
|
||||||
if (opts.fee_limit_sats_CustomCheck && !opts.fee_limit_sats_CustomCheck(o.fee_limit_sats)) return new Error(`${path}.fee_limit_sats: custom check failed`)
|
|
||||||
|
|
||||||
if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`)
|
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`)
|
if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`)
|
||||||
|
|
||||||
|
|
@ -3306,16 +3296,14 @@ export const PayAppUserInvoiceRequestValidate = (o?: PayAppUserInvoiceRequest, o
|
||||||
export type PayInvoiceRequest = {
|
export type PayInvoiceRequest = {
|
||||||
amount: number
|
amount: number
|
||||||
debit_npub?: string
|
debit_npub?: string
|
||||||
fee_limit_sats?: number
|
|
||||||
invoice: string
|
invoice: string
|
||||||
}
|
}
|
||||||
export type PayInvoiceRequestOptionalField = 'debit_npub' | 'fee_limit_sats'
|
export type PayInvoiceRequestOptionalField = 'debit_npub'
|
||||||
export const PayInvoiceRequestOptionalFields: PayInvoiceRequestOptionalField[] = ['debit_npub', 'fee_limit_sats']
|
export const PayInvoiceRequestOptionalFields: PayInvoiceRequestOptionalField[] = ['debit_npub']
|
||||||
export type PayInvoiceRequestOptions = OptionsBaseMessage & {
|
export type PayInvoiceRequestOptions = OptionsBaseMessage & {
|
||||||
checkOptionalsAreSet?: PayInvoiceRequestOptionalField[]
|
checkOptionalsAreSet?: PayInvoiceRequestOptionalField[]
|
||||||
amount_CustomCheck?: (v: number) => boolean
|
amount_CustomCheck?: (v: number) => boolean
|
||||||
debit_npub_CustomCheck?: (v?: string) => boolean
|
debit_npub_CustomCheck?: (v?: string) => boolean
|
||||||
fee_limit_sats_CustomCheck?: (v?: number) => boolean
|
|
||||||
invoice_CustomCheck?: (v: string) => boolean
|
invoice_CustomCheck?: (v: string) => boolean
|
||||||
}
|
}
|
||||||
export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoiceRequestOptions = {}, path: string = 'PayInvoiceRequest::root.'): Error | null => {
|
export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoiceRequestOptions = {}, path: string = 'PayInvoiceRequest::root.'): Error | null => {
|
||||||
|
|
@ -3328,9 +3316,6 @@ 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 ((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 (opts.debit_npub_CustomCheck && !opts.debit_npub_CustomCheck(o.debit_npub)) return new Error(`${path}.debit_npub: custom check failed`)
|
||||||
|
|
||||||
if ((o.fee_limit_sats || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('fee_limit_sats')) && typeof o.fee_limit_sats !== 'number') return new Error(`${path}.fee_limit_sats: is not a number`)
|
|
||||||
if (opts.fee_limit_sats_CustomCheck && !opts.fee_limit_sats_CustomCheck(o.fee_limit_sats)) return new Error(`${path}.fee_limit_sats: custom check failed`)
|
|
||||||
|
|
||||||
if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`)
|
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`)
|
if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -389,8 +389,7 @@ message PayAppUserInvoiceRequest {
|
||||||
string user_identifier = 1;
|
string user_identifier = 1;
|
||||||
string invoice = 2;
|
string invoice = 2;
|
||||||
int64 amount = 3;
|
int64 amount = 3;
|
||||||
optional string debit_npub = 4;
|
optional string debit_npub = 4;
|
||||||
optional int64 fee_limit_sats = 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message SendAppUserToAppUserPaymentRequest {
|
message SendAppUserToAppUserPaymentRequest {
|
||||||
|
|
@ -466,8 +465,7 @@ message DecodeInvoiceResponse{
|
||||||
message PayInvoiceRequest{
|
message PayInvoiceRequest{
|
||||||
string invoice = 1;
|
string invoice = 1;
|
||||||
int64 amount = 2;
|
int64 amount = 2;
|
||||||
optional string debit_npub = 3;
|
optional string debit_npub = 3;
|
||||||
optional int64 fee_limit_sats = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message PayInvoiceResponse{
|
message PayInvoiceResponse{
|
||||||
|
|
@ -831,7 +829,6 @@ message MessagingToken {
|
||||||
|
|
||||||
|
|
||||||
message CumulativeFees {
|
message CumulativeFees {
|
||||||
int64 networkFeeBps = 1;
|
|
||||||
int64 networkFeeFixed = 2;
|
int64 networkFeeFixed = 2;
|
||||||
int64 serviceFeeBps = 3;
|
int64 serviceFeeBps = 3;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { SendCoinsReq } from './sendCoinsReq.js';
|
||||||
import { AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo, ChannelEventCb } from './settings.js';
|
import { AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo, ChannelEventCb } from './settings.js';
|
||||||
import { ERROR, getLogger } from '../helpers/logger.js';
|
import { ERROR, getLogger } from '../helpers/logger.js';
|
||||||
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
||||||
import { LiquidityProvider, LiquidityRequest } from '../main/liquidityProvider.js';
|
import { LiquidityProvider } from '../main/liquidityProvider.js';
|
||||||
import { Utils } from '../helpers/utilsWrapper.js';
|
import { Utils } from '../helpers/utilsWrapper.js';
|
||||||
import { TxPointSettings } from '../storage/tlv/stateBundler.js';
|
import { TxPointSettings } from '../storage/tlv/stateBundler.js';
|
||||||
import { WalletKitClient } from '../../../proto/lnd/walletkit.client.js';
|
import { WalletKitClient } from '../../../proto/lnd/walletkit.client.js';
|
||||||
|
|
@ -342,9 +342,9 @@ export default class {
|
||||||
return { numSatoshis: Number(res.response.numSatoshis), paymentHash: res.response.paymentHash }
|
return { numSatoshis: Number(res.response.numSatoshis), paymentHash: res.response.paymentHash }
|
||||||
}
|
}
|
||||||
|
|
||||||
GetFeeLimitAmount(amount: number): number {
|
/* GetFeeLimitAmount(amount: number): number {
|
||||||
return Math.ceil(amount * this.getSettings().lndSettings.feeRateLimit + this.getSettings().lndSettings.feeFixedLimit);
|
return Math.ceil(amount * this.getSettings().lndSettings.feeRateLimit + this.getSettings().lndSettings.feeFixedLimit);
|
||||||
}
|
} */
|
||||||
|
|
||||||
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
||||||
// console.log("Getting channel balance")
|
// console.log("Getting channel balance")
|
||||||
|
|
@ -359,7 +359,7 @@ export default class {
|
||||||
throw new Error("lnd node is currently out of sync")
|
throw new Error("lnd node is currently out of sync")
|
||||||
}
|
}
|
||||||
if (useProvider) {
|
if (useProvider) {
|
||||||
const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from, feeLimit)
|
const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from/* , feeLimit */)
|
||||||
const providerDst = this.liquidProvider.GetProviderDestination()
|
const providerDst = this.liquidProvider.GetProviderDestination()
|
||||||
return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst }
|
return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,13 @@ 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, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats)
|
const { max, /* networkFeeBps, */ networkFeeFixed, 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: networkFeeBps,
|
network_max_fee_bps: 0,
|
||||||
network_max_fee_fixed: networkFeeFixed,
|
network_max_fee_fixed: networkFeeFixed,
|
||||||
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] }),
|
||||||
|
|
@ -107,7 +107,6 @@ export default class {
|
||||||
invoice: req.invoice,
|
invoice: req.invoice,
|
||||||
user_identifier: ctx.app_user_id,
|
user_identifier: ctx.app_user_id,
|
||||||
debit_npub: req.debit_npub,
|
debit_npub: req.debit_npub,
|
||||||
fee_limit_sats: req.fee_limit_sats
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ export default class {
|
||||||
async StartAppsServiceBeacon(publishBeacon: (app: Application, fees: Types.CumulativeFees) => void) {
|
async StartAppsServiceBeacon(publishBeacon: (app: Application, fees: Types.CumulativeFees) => void) {
|
||||||
this.serviceBeaconInterval = setInterval(async () => {
|
this.serviceBeaconInterval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const fees = this.paymentManager.GetAllFees()
|
const fees = this.paymentManager.GetFees()
|
||||||
const apps = await this.storage.applicationStorage.GetApplications()
|
const apps = await this.storage.applicationStorage.GetApplications()
|
||||||
apps.forEach(app => {
|
apps.forEach(app => {
|
||||||
publishBeacon(app, fees)
|
publishBeacon(app, fees)
|
||||||
|
|
@ -167,7 +167,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, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats)
|
const { max, /* networkFeeBps, */networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats)
|
||||||
return {
|
return {
|
||||||
identifier: u.identifier,
|
identifier: u.identifier,
|
||||||
info: {
|
info: {
|
||||||
|
|
@ -175,7 +175,7 @@ export default class {
|
||||||
balance: u.user.balance_sats,
|
balance: u.user.balance_sats,
|
||||||
max_withdrawable: max,
|
max_withdrawable: max,
|
||||||
user_identifier: u.identifier,
|
user_identifier: u.identifier,
|
||||||
network_max_fee_bps: networkFeeBps,
|
network_max_fee_bps: 0,
|
||||||
network_max_fee_fixed: networkFeeFixed,
|
network_max_fee_fixed: networkFeeFixed,
|
||||||
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] }),
|
||||||
|
|
@ -225,13 +225,13 @@ 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, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
|
const { max, /* networkFeeBps, */ networkFeeFixed, 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: networkFeeBps,
|
network_max_fee_bps: 0,
|
||||||
network_max_fee_fixed: networkFeeFixed,
|
network_max_fee_fixed: networkFeeFixed,
|
||||||
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] }),
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@ export default class {
|
||||||
}
|
}
|
||||||
log = getLogger({ appName: userAddress.linkedApplication.name })
|
log = getLogger({ appName: userAddress.linkedApplication.name })
|
||||||
const isAppUserPayment = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id
|
const isAppUserPayment = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id
|
||||||
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount, isAppUserPayment)
|
let fee = this.paymentManager.getReceiveServiceFee(Types.UserOperationType.INCOMING_TX, amount, isAppUserPayment)
|
||||||
if (userAddress.linkedApplication && userAddress.linkedApplication.owner.user_id === userAddress.user.user_id) {
|
if (userAddress.linkedApplication && userAddress.linkedApplication.owner.user_id === userAddress.user.user_id) {
|
||||||
fee = 0
|
fee = 0
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +272,7 @@ export default class {
|
||||||
}
|
}
|
||||||
log = getLogger({ appName: userInvoice.linkedApplication.name })
|
log = getLogger({ appName: userInvoice.linkedApplication.name })
|
||||||
const isAppUserPayment = userInvoice.user.user_id !== userInvoice.linkedApplication.owner.user_id
|
const isAppUserPayment = userInvoice.user.user_id !== userInvoice.linkedApplication.owner.user_id
|
||||||
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount, isAppUserPayment)
|
let fee = this.paymentManager.getReceiveServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount, isAppUserPayment)
|
||||||
if (userInvoice.linkedApplication && userInvoice.linkedApplication.owner.user_id === userInvoice.user.user_id) {
|
if (userInvoice.linkedApplication && userInvoice.linkedApplication.owner.user_id === userInvoice.user.user_id) {
|
||||||
fee = 0
|
fee = 0
|
||||||
}
|
}
|
||||||
|
|
@ -437,7 +437,7 @@ export default class {
|
||||||
async ResetNostr() {
|
async ResetNostr() {
|
||||||
const apps = await this.storage.applicationStorage.GetApplications()
|
const apps = await this.storage.applicationStorage.GetApplications()
|
||||||
const nextRelay = this.settings.getSettings().nostrRelaySettings.relays[0]
|
const nextRelay = this.settings.getSettings().nostrRelaySettings.relays[0]
|
||||||
const fees = this.paymentManager.GetAllFees()
|
const fees = this.paymentManager.GetFees()
|
||||||
for (const app of apps) {
|
for (const app of apps) {
|
||||||
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay, fees })
|
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay, fees })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export class LiquidityManager {
|
||||||
if (remote > amount) {
|
if (remote > amount) {
|
||||||
return 'lnd'
|
return 'lnd'
|
||||||
}
|
}
|
||||||
const providerCanHandle = await this.liquidityProvider.CanProviderHandle({ action: 'receive', amount })
|
const providerCanHandle = this.liquidityProvider.IsReady()
|
||||||
if (!providerCanHandle) {
|
if (!providerCanHandle) {
|
||||||
return 'lnd'
|
return 'lnd'
|
||||||
}
|
}
|
||||||
|
|
@ -81,24 +81,24 @@ export class LiquidityManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeOutInvoicePayment = async (amount: number): Promise<{ use: 'lnd' } | { use: 'provider', feeLimit: number }> => {
|
beforeOutInvoicePayment = async (amount: number, localServiceFee: number): Promise<'lnd' | 'provider'> => {
|
||||||
const providerReady = this.liquidityProvider.IsReady()
|
const providerReady = this.liquidityProvider.IsReady()
|
||||||
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
if (!providerReady) {
|
if (!providerReady) {
|
||||||
throw new Error("cannot use liquidity provider, it is not ready")
|
throw new Error("cannot use liquidity provider, it is not ready")
|
||||||
}
|
}
|
||||||
const feeLimit = this.liquidityProvider.CalculateExpectedFeeLimit(amount)
|
// const feeLimit = this.liquidityProvider.CalculateExpectedFeeLimit(amount)
|
||||||
return { use: 'provider', feeLimit }
|
return 'provider'
|
||||||
}
|
}
|
||||||
if (!providerReady) {
|
if (!providerReady) {
|
||||||
return { use: 'lnd' }
|
return 'lnd'
|
||||||
}
|
}
|
||||||
const canHandle = await this.liquidityProvider.CanProviderHandle({ action: 'spend', amount })
|
const canHandle = await this.liquidityProvider.CanProviderPay(amount, localServiceFee)
|
||||||
if (!canHandle) {
|
if (!canHandle) {
|
||||||
return { use: 'lnd' }
|
return 'lnd'
|
||||||
}
|
}
|
||||||
const feeLimit = this.liquidityProvider.CalculateExpectedFeeLimit(amount)
|
// const feeLimit = this.liquidityProvider.CalculateExpectedFeeLimit(amount)
|
||||||
return { use: 'provider', feeLimit }
|
return 'provider'
|
||||||
}
|
}
|
||||||
|
|
||||||
afterOutInvoicePaid = async () => { }
|
afterOutInvoicePaid = async () => { }
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { InvoicePaidCb } from '../lnd/settings.js'
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
import SettingsManager from './settingsManager.js'
|
import SettingsManager from './settingsManager.js'
|
||||||
import { LiquiditySettings } from './settings.js'
|
import { LiquiditySettings } from './settings.js'
|
||||||
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
|
||||||
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
||||||
export class LiquidityProvider {
|
export class LiquidityProvider {
|
||||||
getSettings: () => LiquiditySettings
|
getSettings: () => LiquiditySettings
|
||||||
|
|
@ -27,7 +26,7 @@ export class LiquidityProvider {
|
||||||
queue: ((state: 'ready') => void)[] = []
|
queue: ((state: 'ready') => void)[] = []
|
||||||
utils: Utils
|
utils: Utils
|
||||||
pendingPayments: Record<string, number> = {}
|
pendingPayments: Record<string, number> = {}
|
||||||
feesCache: { networkFeeBps: number, networkFeeFixed: number, serviceFeeBps: number } | null = null
|
feesCache: Types.CumulativeFees | null = null
|
||||||
lastSeenBeacon = 0
|
lastSeenBeacon = 0
|
||||||
latestReceivedBalance = 0
|
latestReceivedBalance = 0
|
||||||
incrementProviderBalance: (balance: number) => Promise<void>
|
incrementProviderBalance: (balance: number) => Promise<void>
|
||||||
|
|
@ -132,7 +131,7 @@ export class LiquidityProvider {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
this.feesCache = {
|
this.feesCache = {
|
||||||
networkFeeBps: res.network_max_fee_bps,
|
// networkFeeBps: res.network_max_fee_bps,
|
||||||
networkFeeFixed: res.network_max_fee_fixed,
|
networkFeeFixed: res.network_max_fee_fixed,
|
||||||
serviceFeeBps: res.service_fee_bps
|
serviceFeeBps: res.service_fee_bps
|
||||||
}
|
}
|
||||||
|
|
@ -153,10 +152,15 @@ export class LiquidityProvider {
|
||||||
if (!this.IsReady() || !this.feesCache) {
|
if (!this.IsReady() || !this.feesCache) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
const { networkFeeBps, networkFeeFixed, serviceFeeBps } = this.feesCache
|
const balance = this.latestReceivedBalance
|
||||||
const totalBps = networkFeeBps + serviceFeeBps
|
const { /* networkFeeBps, */ networkFeeFixed, serviceFeeBps } = this.feesCache
|
||||||
|
const div = 1 + (serviceFeeBps / 10000)
|
||||||
|
const maxWithoutFixed = Math.floor(balance / div)
|
||||||
|
const fee = balance - maxWithoutFixed
|
||||||
|
return balance - Math.max(fee, networkFeeFixed)
|
||||||
|
/* const totalBps = networkFeeBps + serviceFeeBps
|
||||||
const div = 1 + (totalBps / 10000)
|
const div = 1 + (totalBps / 10000)
|
||||||
return Math.floor((this.latestReceivedBalance - networkFeeFixed) / div)
|
return Math.floor((this.latestReceivedBalance - networkFeeFixed) / div) */
|
||||||
}
|
}
|
||||||
|
|
||||||
GetLatestBalance = () => {
|
GetLatestBalance = () => {
|
||||||
|
|
@ -170,24 +174,39 @@ export class LiquidityProvider {
|
||||||
return Object.values(this.pendingPayments).reduce((a, b) => a + b, 0)
|
return Object.values(this.pendingPayments).reduce((a, b) => a + b, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
CalculateExpectedFeeLimit = (amount: number) => {
|
GetServiceFee = (amount: number) => {
|
||||||
const fees = this.GetFees()
|
const fees = this.GetFees()
|
||||||
const serviceFeeRate = fees.serviceFeeBps / 10000
|
const serviceFeeRate = fees.serviceFeeBps / 10000
|
||||||
const serviceFee = Math.ceil(serviceFeeRate * amount)
|
const serviceFee = Math.ceil(serviceFeeRate * amount)
|
||||||
const networkMaxFeeRate = fees.networkFeeBps / 10000
|
return Math.max(serviceFee, fees.networkFeeFixed)
|
||||||
const networkFeeLimit = Math.ceil(amount * networkMaxFeeRate + fees.networkFeeFixed)
|
|
||||||
return serviceFee + networkFeeLimit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CanProviderHandle = async (req: LiquidityRequest): Promise<boolean> => {
|
/* CalculateExpectedFeeLimit = (amount: number) => {
|
||||||
|
const fees = this.GetFees()
|
||||||
|
const serviceFeeRate = fees.serviceFeeBps / 10000
|
||||||
|
const serviceFee = Math.ceil(serviceFeeRate * amount)
|
||||||
|
const networkMaxFeeRate = fees.networkFeeBps / 10000
|
||||||
|
const networkFeeLimit = Math.ceil(amount * networkMaxFeeRate + fees.networkFeeFixed)
|
||||||
|
return serviceFee + networkFeeLimit
|
||||||
|
} */
|
||||||
|
|
||||||
|
CanProviderPay = async (amount: number, localServiceFee: number): Promise<boolean> => {
|
||||||
if (!this.IsReady()) {
|
if (!this.IsReady()) {
|
||||||
this.log("provider is not ready")
|
this.log("provider is not ready")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const maxW = this.GetMaxWithdrawable()
|
const maxW = this.GetMaxWithdrawable()
|
||||||
if (req.action === 'spend' && maxW < req.amount) {
|
if (maxW < amount) {
|
||||||
|
this.log("provider does not have enough funds to pay the invoice")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const providerServiceFee = this.GetServiceFee(amount)
|
||||||
|
if (localServiceFee < providerServiceFee) {
|
||||||
|
this.log(`local service fee ${localServiceFee} is less than the provider's service fee ${providerServiceFee}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,13 +229,14 @@ export class LiquidityProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system', feeLimit?: number) => {
|
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 feeLimitToUse = feeLimit ? feeLimit : this.CalculateExpectedFeeLimit(decodedAmount)
|
// const feeLimitToUse = feeLimit ? feeLimit : this.CalculateExpectedFeeLimit(decodedAmount)
|
||||||
this.pendingPayments[invoice] = decodedAmount + feeLimitToUse
|
const providerServiceFee = this.GetServiceFee(decodedAmount)
|
||||||
|
this.pendingPayments[invoice] = decodedAmount + providerServiceFee
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
if (!this.pendingPaymentsAck[invoice]) {
|
if (!this.pendingPaymentsAck[invoice]) {
|
||||||
return
|
return
|
||||||
|
|
@ -225,7 +245,7 @@ export class LiquidityProvider {
|
||||||
this.lastSeenBeacon = 0
|
this.lastSeenBeacon = 0
|
||||||
}, 1000 * 10)
|
}, 1000 * 10)
|
||||||
this.pendingPaymentsAck[invoice] = true
|
this.pendingPaymentsAck[invoice] = true
|
||||||
const res = await this.client.PayInvoice({ invoice, amount: 0, fee_limit_sats: feeLimitToUse })
|
const res = await this.client.PayInvoice({ invoice, amount: 0,/* fee_limit_sats: feeLimitToUse */ })
|
||||||
delete this.pendingPaymentsAck[invoice]
|
delete this.pendingPaymentsAck[invoice]
|
||||||
if (res.status === 'ERROR') {
|
if (res.status === 'ERROR') {
|
||||||
this.log("error paying invoice", res.reason)
|
this.log("error paying invoice", res.reason)
|
||||||
|
|
|
||||||
|
|
@ -162,23 +162,40 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getServiceFee(action: Types.UserOperationType, amount: number, appUser: boolean): number {
|
getReceiveServiceFee = (action: Types.UserOperationType, amount: number, appUser: 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 Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingTxFee * amount)
|
||||||
case Types.UserOperationType.OUTGOING_TX:
|
|
||||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingTxFee * amount)
|
|
||||||
case Types.UserOperationType.INCOMING_INVOICE:
|
case Types.UserOperationType.INCOMING_INVOICE:
|
||||||
if (appUser) {
|
if (appUser) {
|
||||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppUserInvoiceFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppUserInvoiceFee * amount)
|
||||||
}
|
}
|
||||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppInvoiceFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppInvoiceFee * amount)
|
||||||
case Types.UserOperationType.OUTGOING_INVOICE:
|
case Types.UserOperationType.INCOMING_USER_TO_USER:
|
||||||
if (appUser) {
|
if (appUser) {
|
||||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
||||||
}
|
}
|
||||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppInvoiceFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.appToUserFee * amount)
|
||||||
case Types.UserOperationType.OUTGOING_USER_TO_USER || Types.UserOperationType.INCOMING_USER_TO_USER:
|
default:
|
||||||
|
throw new Error("Unknown receive action type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInvoicePaymentServiceFee = (amount: number, appUser: boolean): number => {
|
||||||
|
if (appUser) {
|
||||||
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee * amount)
|
||||||
|
}
|
||||||
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppInvoiceFee * amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
getSendServiceFee = (action: Types.UserOperationType, amount: number, appUser: boolean): number => {
|
||||||
|
switch (action) {
|
||||||
|
case Types.UserOperationType.OUTGOING_TX:
|
||||||
|
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.feeFixedLimit)
|
||||||
|
case Types.UserOperationType.OUTGOING_USER_TO_USER:
|
||||||
if (appUser) {
|
if (appUser) {
|
||||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +205,32 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* getServiceFee(action: Types.UserOperationType, amount: number, appUser: boolean): number {
|
||||||
|
switch (action) {
|
||||||
|
case Types.UserOperationType.INCOMING_TX:
|
||||||
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingTxFee * amount)
|
||||||
|
case Types.UserOperationType.OUTGOING_TX:
|
||||||
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingTxFee * 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)
|
||||||
|
case Types.UserOperationType.OUTGOING_INVOICE:
|
||||||
|
if (appUser) {
|
||||||
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee * amount)
|
||||||
|
}
|
||||||
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppInvoiceFee * amount)
|
||||||
|
case Types.UserOperationType.OUTGOING_USER_TO_USER || Types.UserOperationType.INCOMING_USER_TO_USER:
|
||||||
|
if (appUser) {
|
||||||
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
||||||
|
}
|
||||||
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.appToUserFee * amount)
|
||||||
|
default:
|
||||||
|
throw new Error("Unknown service action type")
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
async SetMockInvoiceAsPaid(req: Types.SetMockInvoiceAsPaidRequest) {
|
async SetMockInvoiceAsPaid(req: Types.SetMockInvoiceAsPaidRequest) {
|
||||||
if (!this.settings.getSettings().lndSettings.mockLnd) {
|
if (!this.settings.getSettings().lndSettings.mockLnd) {
|
||||||
throw new Error("mock disabled, cannot set invoice as paid")
|
throw new Error("mock disabled, cannot set invoice as paid")
|
||||||
|
|
@ -234,23 +277,29 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GetAllFees = (): Types.CumulativeFees => {
|
GetFees = (): Types.CumulativeFees => {
|
||||||
const { outgoingAppUserInvoiceFeeBps } = this.settings.getSettings().serviceFeeSettings
|
const { outgoingAppUserInvoiceFeeBps } = this.settings.getSettings().serviceFeeSettings
|
||||||
if (this.lnd.liquidProvider.IsReady()) {
|
/* if (this.lnd.liquidProvider.IsReady()) {
|
||||||
const fees = this.lnd.liquidProvider.GetFees()
|
const fees = this.lnd.liquidProvider.GetFees()
|
||||||
const networkFeeBps = fees.networkFeeBps + fees.serviceFeeBps
|
const networkFeeBps = fees.networkFeeBps + fees.serviceFeeBps
|
||||||
return { networkFeeBps, networkFeeFixed: fees.networkFeeFixed, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
|
return { networkFeeBps, networkFeeFixed: fees.networkFeeFixed, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
|
||||||
}
|
} */
|
||||||
const { feeFixedLimit, feeRateBps } = this.settings.getSettings().lndSettings
|
const { feeFixedLimit } = this.settings.getSettings().lndSettings
|
||||||
return { networkFeeBps: feeRateBps, networkFeeFixed: feeFixedLimit, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
|
return { networkFeeFixed: feeFixedLimit, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
|
||||||
}
|
}
|
||||||
|
|
||||||
GetMaxPayableInvoice(balance: number): Types.CumulativeFees & { max: number } {
|
GetMaxPayableInvoice(balance: number): Types.CumulativeFees & { max: number } {
|
||||||
const { networkFeeBps, networkFeeFixed, serviceFeeBps } = this.GetAllFees()
|
const { networkFeeFixed, serviceFeeBps } = this.GetFees()
|
||||||
const totalBps = networkFeeBps + serviceFeeBps
|
const div = 1 + (serviceFeeBps / 10000)
|
||||||
const div = 1 + (totalBps / 10000)
|
const maxWithoutFixed = Math.floor(balance / div)
|
||||||
const max = Math.floor((balance - networkFeeFixed) / div)
|
const fee = balance - maxWithoutFixed
|
||||||
return { max, serviceFeeBps, networkFeeBps, networkFeeFixed }
|
const max = balance - Math.max(fee, networkFeeFixed)
|
||||||
|
return { max, networkFeeFixed, serviceFeeBps }
|
||||||
|
|
||||||
|
/* const totalBps = networkFeeBps + serviceFeeBps
|
||||||
|
const div = 1 + (totalBps / 10000)
|
||||||
|
const max = Math.floor((balance - networkFeeFixed) / div)
|
||||||
|
return { max, serviceFeeBps, networkFeeBps, networkFeeFixed } */
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
@ -274,10 +323,10 @@ export default class {
|
||||||
}
|
}
|
||||||
const payAmount = req.amount !== 0 ? req.amount : Number(decoded.numSatoshis)
|
const payAmount = req.amount !== 0 ? req.amount : Number(decoded.numSatoshis)
|
||||||
const isAppUserPayment = userId !== linkedApplication.owner.user_id
|
const isAppUserPayment = userId !== linkedApplication.owner.user_id
|
||||||
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount, isAppUserPayment)
|
const serviceFee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount, isAppUserPayment)
|
||||||
if (req.fee_limit_sats && req.fee_limit_sats < serviceFee) {
|
/* if (req.fee_limit_sats && req.fee_limit_sats < serviceFee) {
|
||||||
throw new Error("fee limit provided is too low to cover service fees")
|
throw new Error("fee limit provided is too low to cover service fees")
|
||||||
}
|
} */
|
||||||
const internalInvoice = await this.storage.paymentStorage.GetInvoiceOwner(req.invoice)
|
const internalInvoice = await this.storage.paymentStorage.GetInvoiceOwner(req.invoice)
|
||||||
if (internalInvoice && internalInvoice.paid_at_unix > 0) {
|
if (internalInvoice && internalInvoice.paid_at_unix > 0) {
|
||||||
throw new Error("this invoice was already paid")
|
throw new Error("this invoice was already paid")
|
||||||
|
|
@ -290,10 +339,11 @@ export default class {
|
||||||
if (internalInvoice) {
|
if (internalInvoice) {
|
||||||
paymentInfo = await this.PayInternalInvoice(userId, internalInvoice, { payAmount, serviceFee }, linkedApplication, req.debit_npub)
|
paymentInfo = await this.PayInternalInvoice(userId, internalInvoice, { payAmount, serviceFee }, linkedApplication, req.debit_npub)
|
||||||
} else {
|
} else {
|
||||||
paymentInfo = await this.PayExternalInvoice(userId, req.invoice, { payAmount, serviceFee, amountForLnd: req.amount, feeLimit: req.fee_limit_sats }, linkedApplication, req.debit_npub, ack)
|
paymentInfo = await this.PayExternalInvoice(userId, req.invoice, { payAmount, serviceFee, amountForLnd: req.amount, /* feeLimit: req.fee_limit_sats */ }, linkedApplication, req.debit_npub, ack)
|
||||||
}
|
}
|
||||||
if (isAppUserPayment && serviceFee > 0) {
|
const feeDiff = serviceFee - paymentInfo.networkFee
|
||||||
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee, "fees")
|
if (isAppUserPayment && feeDiff > 0) {
|
||||||
|
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, feeDiff, "fees")
|
||||||
}
|
}
|
||||||
const user = await this.storage.userStorage.GetUser(userId)
|
const user = await this.storage.userStorage.GetUser(userId)
|
||||||
this.storage.eventsLog.LogEvent({ type: 'invoice_payment', userId, appId: linkedApplication.app_id, appUserId: "", balance: user.balance_sats, data: req.invoice, amount: payAmount })
|
this.storage.eventsLog.LogEvent({ type: 'invoice_payment', userId, appId: linkedApplication.app_id, appUserId: "", balance: user.balance_sats, data: req.invoice, amount: payAmount })
|
||||||
|
|
@ -310,22 +360,22 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUse = async (payAmount: number, inputLimit: number | undefined): Promise<{ use: 'lnd' | 'provider', feeLimit: number }> => {
|
/* getUse = async (payAmount: number, localServiceFee: number): Promise<{ use: 'lnd' | 'provider', feeLimit: number }> => {
|
||||||
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount)
|
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount, localServiceFee)
|
||||||
if (use.use === 'lnd') {
|
if (use === 'lnd') {
|
||||||
const lndFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
const lndFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
||||||
if (inputLimit && inputLimit < lndFeeLimit) {
|
if (inputLimit && inputLimit < lndFeeLimit) {
|
||||||
this.log("WARNING requested fee limit is lower than suggested, payment might fail")
|
this.log("WARNING requested fee limit is lower than suggested, payment might fail")
|
||||||
|
}
|
||||||
|
return { use: 'lnd', feeLimit: inputLimit || lndFeeLimit }
|
||||||
}
|
}
|
||||||
return { use: 'lnd', feeLimit: inputLimit || lndFeeLimit }
|
if (inputLimit && inputLimit < use.feeLimit) {
|
||||||
}
|
this.log("WARNING requested fee limit is lower than suggested by provider, payment might fail")
|
||||||
if (inputLimit && inputLimit < use.feeLimit) {
|
}
|
||||||
this.log("WARNING requested fee limit is lower than suggested by provider, payment might fail")
|
return { use: 'provider', feeLimit: inputLimit || use.feeLimit }
|
||||||
}
|
} */
|
||||||
return { use: 'provider', feeLimit: inputLimit || use.feeLimit }
|
|
||||||
}
|
|
||||||
|
|
||||||
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number, feeLimit?: number }, linkedApplication: Application, debitNpub?: string, ack?: (op: Types.UserOperation) => void) {
|
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number, /* feeLimit?: number */ }, linkedApplication: Application, debitNpub?: string, ack?: (op: Types.UserOperation) => void) {
|
||||||
if (this.settings.getSettings().serviceSettings.disableExternalPayments) {
|
if (this.settings.getSettings().serviceSettings.disableExternalPayments) {
|
||||||
throw new Error("something went wrong sending payment, please try again later")
|
throw new Error("something went wrong sending payment, please try again later")
|
||||||
}
|
}
|
||||||
|
|
@ -341,33 +391,36 @@ export default class {
|
||||||
|
|
||||||
const { amountForLnd, payAmount, serviceFee } = amounts
|
const { amountForLnd, payAmount, serviceFee } = amounts
|
||||||
const totalAmountToDecrement = payAmount + serviceFee
|
const totalAmountToDecrement = payAmount + serviceFee
|
||||||
|
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount, serviceFee)
|
||||||
/* const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
/* const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
||||||
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount) */
|
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount) */
|
||||||
const remainingLimit = amounts.feeLimit ? amounts.feeLimit - serviceFee : undefined
|
// const remainingLimit = amounts.feeLimit ? amounts.feeLimit - serviceFee : undefined
|
||||||
const { use, feeLimit: routingFeeLimit } = await this.getUse(payAmount, remainingLimit)
|
// const { use, feeLimit: routingFeeLimit } = await this.getUse(payAmount, remainingLimit)
|
||||||
const provider = use === 'provider' ? this.lnd.liquidProvider.GetProviderDestination() : undefined
|
const provider = use === 'provider' ? this.lnd.liquidProvider.GetProviderDestination() : undefined
|
||||||
const pendingPayment = await this.storage.StartTransaction(async tx => {
|
const pendingPayment = await this.storage.StartTransaction(async tx => {
|
||||||
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice, tx)
|
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement, invoice, tx)
|
||||||
return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: routingFeeLimit }, linkedApplication, provider, tx, debitNpub)
|
return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: 0 }, linkedApplication, provider, tx, debitNpub)
|
||||||
}, "payment started")
|
}, "payment started")
|
||||||
this.log("ready to pay")
|
this.log("ready to pay")
|
||||||
const opId = `${Types.UserOperationType.OUTGOING_INVOICE}-${pendingPayment.serial_id}`
|
const opId = `${Types.UserOperationType.OUTGOING_INVOICE}-${pendingPayment.serial_id}`
|
||||||
const op = this.newInvoicePaymentOperation({ invoice, opId, amount: payAmount, networkFee: routingFeeLimit, serviceFee: serviceFee, confirmed: false })
|
const op = this.newInvoicePaymentOperation({ invoice, opId, amount: payAmount, networkFee: 0, serviceFee: serviceFee, confirmed: false })
|
||||||
ack?.(op)
|
ack?.(op)
|
||||||
try {
|
try {
|
||||||
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit, payAmount, { useProvider: use === 'provider', from: 'user' }, index => {
|
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, serviceFee, payAmount, { useProvider: use === 'provider', from: 'user' }, index => {
|
||||||
this.storage.paymentStorage.SetExternalPaymentIndex(pendingPayment.serial_id, index)
|
this.storage.paymentStorage.SetExternalPaymentIndex(pendingPayment.serial_id, index)
|
||||||
})
|
})
|
||||||
if (routingFeeLimit - payment.feeSat > 0) {
|
/* const feeDiff = serviceFee - payment.feeSat
|
||||||
this.log("refund routing fee", routingFeeLimit, payment.feeSat, "sats")
|
if (feeDiff > 0) {
|
||||||
|
// this.log("refund routing fee", routingFeeLimit, payment.feeSat, "sats")
|
||||||
|
this.log("")
|
||||||
await this.storage.userStorage.IncrementUserBalance(userId, routingFeeLimit - payment.feeSat, "routing_fee_refund:" + invoice)
|
await this.storage.userStorage.IncrementUserBalance(userId, routingFeeLimit - payment.feeSat, "routing_fee_refund:" + invoice)
|
||||||
}
|
} */
|
||||||
|
|
||||||
await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, payment.feeSat, serviceFee, true, payment.providerDst)
|
await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, payment.feeSat, serviceFee, true, payment.providerDst)
|
||||||
return { preimage: payment.paymentPreimage, amtPaid: payment.valueSat, networkFee: payment.feeSat, serialId: pendingPayment.serial_id }
|
return { preimage: payment.paymentPreimage, amtPaid: payment.valueSat, networkFee: payment.feeSat, serialId: pendingPayment.serial_id }
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await this.storage.userStorage.IncrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, "payment_refund:" + invoice)
|
await this.storage.userStorage.IncrementUserBalance(userId, totalAmountToDecrement, "payment_refund:" + invoice)
|
||||||
await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, 0, 0, false)
|
await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, 0, 0, false)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +456,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 serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_TX, req.amoutSats, false)
|
const serviceFee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_TX, req.amoutSats, false)
|
||||||
const isAppUserPayment = ctx.user_id !== app.owner.user_id
|
const isAppUserPayment = ctx.user_id !== app.owner.user_id
|
||||||
const internalAddress = await this.storage.paymentStorage.GetAddressOwner(req.address)
|
const internalAddress = await this.storage.paymentStorage.GetAddressOwner(req.address)
|
||||||
let txId = ""
|
let txId = ""
|
||||||
|
|
@ -772,7 +825,7 @@ export default class {
|
||||||
throw new Error("not enough balance to send payment")
|
throw new Error("not enough balance to send payment")
|
||||||
}
|
}
|
||||||
const isAppUserPayment = fromUser.user_id !== linkedApplication.owner.user_id
|
const isAppUserPayment = fromUser.user_id !== linkedApplication.owner.user_id
|
||||||
let fee = this.getServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount, isAppUserPayment)
|
let fee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount, isAppUserPayment)
|
||||||
const toDecrement = amount + fee
|
const toDecrement = amount + fee
|
||||||
const paymentEntry = await this.storage.paymentStorage.AddPendingUserToUserPayment(fromUserId, toUserId, amount, fee, linkedApplication, tx)
|
const paymentEntry = await this.storage.paymentStorage.AddPendingUserToUserPayment(fromUserId, toUserId, amount, fee, linkedApplication, tx)
|
||||||
await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, toDecrement, `${toUserId}:${paymentEntry.serial_id}`, tx)
|
await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, toDecrement, `${toUserId}:${paymentEntry.serial_id}`, tx)
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ export const initBootstrappedInstance = async (T: TestBase) => {
|
||||||
bootstrapped.liquidityProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
bootstrapped.liquidityProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||||
await new Promise<void>(res => {
|
await new Promise<void>(res => {
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
const canHandle = await bootstrapped.liquidityProvider.CanProviderHandle({ action: 'receive', amount: 2000 })
|
const canHandle = bootstrapped.liquidityProvider.IsReady()
|
||||||
console.log("can handle", canHandle)
|
console.log("can handle", canHandle)
|
||||||
if (canHandle) {
|
if (canHandle) {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue