This commit is contained in:
hatim 2023-05-12 19:53:57 +02:00
parent 9555b7be51
commit 80933b1ecf
6 changed files with 95 additions and 41 deletions

View file

@ -3,14 +3,22 @@ LND_CERT_PATH=C:\Users\user\.polar\networks\2\volumes\lnd\alice\tls.cert
LND_MACAROON_PATH=C:\Users\user\.polar\networks\2\volumes\lnd\alice\data\chain\bitcoin\regtest\admin.macaroon LND_MACAROON_PATH=C:\Users\user\.polar\networks\2\volumes\lnd\alice\data\chain\bitcoin\regtest\admin.macaroon
DATABASE_FILE=db.sqlite DATABASE_FILE=db.sqlite
JWT_SECRET=bigsecrethere JWT_SECRET=bigsecrethere
LIMIT_FEE_RATE_MILLISATS=6 OUTBOUND_MAX_FEE_BPS=60
LIMIT_FEE_FIXED_SATS=100 OUTBOUND_MAX_FEE_EXTRA_SATS=100
SERVICE_FEE_INCOMING_TX_PERCENT=0 INCOMING_CHAIN_FEE_ROOT_BPS=0
SERVICE_FEE_OUTGOING_TX_PERCENT=0 OUTGOING_CHAIN_FEE_ROOT_BPS=60 #this is applied only to withdrawls from application wallets
SERVICE_FEE_INCOMING_INVOICE_PERCENT=0 INCOMING_INVOICE_FEE_ROOT_BPS=0
SERVICE_FEE_OUTGOING_INVOICE_PERCENT=0 OUTGOING_INVOICE_FEE_ROOT_BPS=60 #this is applied only to withdrawals from application wallets
INCOMING_INVOICE_FEE_USER_BPS=0 #defined by app this is just default
OUTGOING_INVOICE_FEE_USER_BPS=60 #defined by app this is just default
TX_FEE_INTERNAL_ROOT_BPS=60 #this is applied only to withdrawls from application wallets
TX_FEE_INTERNAL_USER_BPS=60 #defined by app this is just default
NOSTR_PUBLIC_KEY= NOSTR_PUBLIC_KEY=
NOSTR_PRIVATE_KEY= NOSTR_PRIVATE_KEY=
NOSTR_RELAYS=wss://nostr-relay.wlvs.space NOSTR_RELAYS=wss://nostr-relay.wlvs.space
NOSTR_ALLOWED_PUBS= NOSTR_ALLOWED_PUBS=
SERVICE_URL=http://localhost:8080 SERVICE_URL=http://localhost:8080
ADMIN_TOKEN=
ALLOW_BALANCE_MIGRATION=false
PORT=8080
MOCK_LND=false

View file

@ -8,8 +8,8 @@ export const LoadLndSettingsFromEnv = (test = false): LndSettings => {
const lndAddr = EnvMustBeNonEmptyString("LND_ADDRESS") const lndAddr = EnvMustBeNonEmptyString("LND_ADDRESS")
const lndCertPath = EnvMustBeNonEmptyString("LND_CERT_PATH") const lndCertPath = EnvMustBeNonEmptyString("LND_CERT_PATH")
const lndMacaroonPath = EnvMustBeNonEmptyString("LND_MACAROON_PATH") const lndMacaroonPath = EnvMustBeNonEmptyString("LND_MACAROON_PATH")
const feeRateLimit = EnvMustBeInteger("LIMIT_FEE_RATE_MILLISATS") / 1000 const feeRateLimit = EnvMustBeInteger("OUTBOUND_MAX_FEE_BPS") / 10000
const feeFixedLimit = EnvMustBeInteger("LIMIT_FEE_FIXED_SATS") const feeFixedLimit = EnvMustBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS")
const mockLnd = EnvCanBeBoolean("MOCK_LND") const mockLnd = EnvCanBeBoolean("MOCK_LND")
return { lndAddr, lndCertPath, lndMacaroonPath, feeRateLimit, feeFixedLimit, mockLnd } return { lndAddr, lndCertPath, lndMacaroonPath, feeRateLimit, feeFixedLimit, mockLnd }
} }

View file

@ -79,7 +79,7 @@ export default class {
userId: u.user.user_id, userId: u.user.user_id,
balance: u.user.balance_sats balance: u.user.balance_sats
}, },
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats) max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true)
} }
} }
@ -91,9 +91,10 @@ export default class {
} }
async AddAppUserInvoice(appId: string, req: Types.AddAppUserInvoiceRequest): Promise<Types.NewInvoiceResponse> { async AddAppUserInvoice(appId: string, req: Types.AddAppUserInvoiceRequest): Promise<Types.NewInvoiceResponse> {
const app = await this.storage.applicationStorage.GetApplication(appId)
const receiver = await this.storage.applicationStorage.GetApplicationUser(appId, req.receiver_identifier) const receiver = await this.storage.applicationStorage.GetApplicationUser(appId, req.receiver_identifier)
const payer = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.payer_identifier, 0) const payer = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.payer_identifier, 0)
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user } const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts) const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
return { return {
invoice: appUserInvoice.invoice invoice: appUserInvoice.invoice
@ -102,7 +103,7 @@ export default class {
async GetAppUser(appId: string, req: Types.GetAppUserRequest): Promise<Types.AppUser> { async GetAppUser(appId: string, req: Types.GetAppUserRequest): Promise<Types.AppUser> {
const user = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier) const user = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier)
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats) const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
console.log(max, user.user.balance_sats) console.log(max, user.user.balance_sats)
return { return {
max_withdrawable: max, identifier: req.user_identifier, info: { max_withdrawable: max, identifier: req.user_identifier, info: {
@ -127,7 +128,7 @@ export default class {
async SendAppUserToAppPayment(appId: string, req: Types.SendAppUserToAppPaymentRequest): Promise<void> { async SendAppUserToAppPayment(appId: string, req: Types.SendAppUserToAppPaymentRequest): Promise<void> {
const fromUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.from_user_identifier) const fromUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.from_user_identifier)
const app = await this.storage.applicationStorage.GetApplication(appId) const app = await this.storage.applicationStorage.GetApplication(appId)
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, app.owner.user_id, req.amount) await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, app.owner.user_id, req.amount, app)
} }
async GetAppUserLNURLInfo(appId: string, req: Types.GetAppUserLNURLInfoRequest): Promise<Types.LnurlPayInfoResponse> { async GetAppUserLNURLInfo(appId: string, req: Types.GetAppUserLNURLInfoRequest): Promise<Types.LnurlPayInfoResponse> {
const user = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier) const user = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier)

View file

@ -14,11 +14,14 @@ export const LoadMainSettingsFromEnv = (test = false): MainSettings => {
lndSettings: LoadLndSettingsFromEnv(test), lndSettings: LoadLndSettingsFromEnv(test),
storageSettings: LoadStorageSettingsFromEnv(test), storageSettings: LoadStorageSettingsFromEnv(test),
jwtSecret: EnvMustBeNonEmptyString("JWT_SECRET"), jwtSecret: EnvMustBeNonEmptyString("JWT_SECRET"),
incomingTxFee: EnvMustBeInteger("SERVICE_FEE_INCOMING_TX_PERCENT") / 100, incomingTxFee: EnvMustBeInteger("INCOMING_CHAIN_FEE_ROOT_BPS") / 10000,
outgoingTxFee: EnvMustBeInteger("SERVICE_FEE_OUTGOING_TX_PERCENT") / 100, outgoingTxFee: EnvMustBeInteger("OUTGOING_CHAIN_FEE_ROOT_BPS") / 10000,
incomingInvoiceFee: EnvMustBeInteger("SERVICE_FEE_INCOMING_INVOICE_PERCENT") / 100, incomingAppInvoiceFee: EnvMustBeInteger("INCOMING_INVOICE_FEE_ROOT_BPS") / 10000,
outgoingInvoiceFee: EnvMustBeInteger("SERVICE_FEE_OUTGOING_INVOICE_PERCENT") / 100, outgoingAppInvoiceFee: EnvMustBeInteger("OUTGOING_INVOICE_FEE_ROOT_BPS") / 10000,
userToUserFee: EnvMustBeInteger("SERVICE_FEE_USER_TO_USER_PERCENT") / 100, incomingAppUserInvoiceFee: EnvMustBeInteger("INCOMING_INVOICE_FEE_USER_BPS") / 10000,
outgoingAppUserInvoiceFee: EnvMustBeInteger("OUTGOING_INVOICE_FEE_USER_BPS") / 10000,
userToUserFee: EnvMustBeInteger("TX_FEE_INTERNAL_USER_BPS") / 10000,
appToUserFee: EnvMustBeInteger("TX_FEE_INTERNAL_ROOT_BPS") / 10000,
serviceUrl: EnvMustBeNonEmptyString("SERVICE_URL"), serviceUrl: EnvMustBeNonEmptyString("SERVICE_URL"),
servicePort: EnvMustBeInteger("PORT") servicePort: EnvMustBeInteger("PORT")
} }
@ -57,7 +60,7 @@ export default class {
this.storage.StartTransaction(async tx => { this.storage.StartTransaction(async tx => {
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx) const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
if (!userAddress) { return } if (!userAddress) { return }
const fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount) const fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount, false)
try { try {
// This call will fail if the transaction is already registered // This call will fail if the transaction is already registered
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, tx) const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, tx)
@ -72,12 +75,21 @@ export default class {
this.storage.StartTransaction(async tx => { this.storage.StartTransaction(async tx => {
const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx) const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx)
if (!userInvoice || userInvoice.paid_at_unix > 0) { return } if (!userInvoice || userInvoice.paid_at_unix > 0) { return }
const fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount) if (!userInvoice.linkedApplication) {
console.error("an invoice was paid, that has no linked application")
return
}
const isAppUserPayment = userInvoice.user.user_id !== userInvoice.linkedApplication.owner.user_id
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount, isAppUserPayment)
if (userInvoice.linkedApplication && userInvoice.linkedApplication.owner.user_id === userInvoice.user.user_id) {
fee = 0
}
try { try {
// This call will fail if the invoice is already registered // This call will fail if the invoice is already registered
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, tx) await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, tx)
await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, tx) await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, tx)
if (userInvoice.linkedApplication) { if (isAppUserPayment && fee > 0) {
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, tx) await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, tx)
} }
await this.triggerPaidCallback(userInvoice.callbackUrl) await this.triggerPaidCallback(userInvoice.callbackUrl)

View file

@ -22,18 +22,27 @@ export default class {
this.settings = settings this.settings = settings
this.lnd = lnd this.lnd = lnd
} }
getServiceFee(action: Types.UserOperationType, amount: number): number { getServiceFee(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.incomingTxFee * amount) return Math.ceil(this.settings.incomingTxFee * amount)
case Types.UserOperationType.OUTGOING_TX: case Types.UserOperationType.OUTGOING_TX:
return Math.ceil(this.settings.outgoingTxFee * amount) return Math.ceil(this.settings.outgoingTxFee * amount)
case Types.UserOperationType.INCOMING_INVOICE: case Types.UserOperationType.INCOMING_INVOICE:
return Math.ceil(this.settings.incomingInvoiceFee * amount) if (appUser) {
return Math.ceil(this.settings.incomingAppUserInvoiceFee * amount)
}
return Math.ceil(this.settings.incomingAppInvoiceFee * amount)
case Types.UserOperationType.OUTGOING_INVOICE: case Types.UserOperationType.OUTGOING_INVOICE:
return Math.ceil(this.settings.outgoingInvoiceFee * amount) if (appUser) {
return Math.ceil(this.settings.outgoingAppUserInvoiceFee * amount)
}
return Math.ceil(this.settings.outgoingAppInvoiceFee * amount)
case Types.UserOperationType.OUTGOING_USER_TO_USER || Types.UserOperationType.INCOMING_USER_TO_USER: case Types.UserOperationType.OUTGOING_USER_TO_USER || Types.UserOperationType.INCOMING_USER_TO_USER:
return Math.ceil(this.settings.userToUserFee * amount) if (appUser) {
return Math.ceil(this.settings.userToUserFee * amount)
}
return Math.ceil(this.settings.appToUserFee * amount)
default: default:
throw new Error("Unknown service action type") throw new Error("Unknown service action type")
} }
@ -86,8 +95,13 @@ export default class {
}) })
} }
GetMaxPayableInvoice(balance: number): number { GetMaxPayableInvoice(balance: number, appUser: boolean): number {
const maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.outgoingInvoiceFee))) let maxWithinServiceFee = 0
if (appUser) {
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.outgoingAppUserInvoiceFee)))
} else {
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.outgoingAppInvoiceFee)))
}
return this.lnd.GetMaxWithinLimit(maxWithinServiceFee) return this.lnd.GetMaxWithinLimit(maxWithinServiceFee)
} }
async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> { async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> {
@ -105,17 +119,19 @@ export default class {
throw new Error("invoice has no value, an amount must be provided in the request") throw new Error("invoice has no value, an amount must be provided in the request")
} }
const payAmount = req.amount !== 0 ? req.amount : Number(decoded.numSatoshis) const payAmount = req.amount !== 0 ? req.amount : Number(decoded.numSatoshis)
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount) if (!linkedApplication) {
throw new Error("only application operations are supported") // TODO - make this check obsolete
}
const isAppUserPayment = userId !== linkedApplication.owner.user_id
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount, isAppUserPayment)
const totalAmountToDecrement = payAmount + serviceFee const totalAmountToDecrement = payAmount + serviceFee
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount) const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit) await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit)
const payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit) const payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit)
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + Number(payment.feeSat)) await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + Number(payment.feeSat))
if (linkedApplication) { if (isAppUserPayment && serviceFee > 0) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee) await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee)
} else {
//const appOwner = await this.storage.applicationStorage.IsApplicationOwner(userId)
} }
await this.storage.userStorage.UnlockUser(userId) await this.storage.userStorage.UnlockUser(userId)
await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, Number(payment.feeSat), serviceFee) await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, Number(payment.feeSat), serviceFee)
@ -125,12 +141,18 @@ export default class {
} }
} }
async PayAddress(userId: string, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> { async PayAddress(userId: string, req: Types.PayAddressRequest, linkedApplication?: Application): Promise<Types.PayAddressResponse> {
const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, req.targetConf) const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, req.targetConf)
const satPerVByte = Number(estimate.satPerVbyte) const satPerVByte = Number(estimate.satPerVbyte)
const chainFees = Number(estimate.feeSat) const chainFees = Number(estimate.feeSat)
const total = req.amoutSats + chainFees const total = req.amoutSats + chainFees
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, req.amoutSats) if (!linkedApplication) {
throw new Error("only application operations are supported") // TODO - make this check obsolete
}
if (userId !== linkedApplication.owner.user_id) {
throw new Error("chain operations only supported for applications")
}
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, req.amoutSats, false)
await this.lockUserWithMinBalance(userId, total + serviceFee) await this.lockUserWithMinBalance(userId, total + serviceFee)
const payment = await this.lnd.PayAddress(req.address, req.amoutSats, satPerVByte) const payment = await this.lnd.PayAddress(req.address, req.amoutSats, satPerVByte)
await this.storage.userStorage.DecrementUserBalance(userId, total + serviceFee) await this.storage.userStorage.DecrementUserBalance(userId, total + serviceFee)
@ -154,7 +176,8 @@ export default class {
} }
async GetLnurlWithdrawInfo(balanceCheckK1: string): Promise<Types.LnurlWithdrawInfoResponse> { async GetLnurlWithdrawInfo(balanceCheckK1: string): Promise<Types.LnurlWithdrawInfoResponse> {
const key = await this.storage.paymentStorage.UseUserEphemeralKey(balanceCheckK1, 'balanceCheck') throw new Error("LNURL withdraw currenlty not supported for non application users")
/*const key = await this.storage.paymentStorage.UseUserEphemeralKey(balanceCheckK1, 'balanceCheck')
const maxWithdrawable = this.GetMaxPayableInvoice(key.user.balance_sats) const maxWithdrawable = this.GetMaxPayableInvoice(key.user.balance_sats)
const callbackK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'withdraw') const callbackK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'withdraw')
const newBalanceCheckK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'balanceCheck') const newBalanceCheckK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'balanceCheck')
@ -165,10 +188,10 @@ export default class {
defaultDescription: "lnurl withdraw from lightning.pub", defaultDescription: "lnurl withdraw from lightning.pub",
k1: callbackK1.key, k1: callbackK1.key,
maxWithdrawable: maxWithdrawable * 1000, maxWithdrawable: maxWithdrawable * 1000,
minWithdrawable: 0, minWithdrawable: 10000,
balanceCheck: this.balanceCheckUrl(newBalanceCheckK1.key), balanceCheck: this.balanceCheckUrl(newBalanceCheckK1.key),
payLink: `${this.settings.serviceUrl}/api/guest/lnurl_pay/info?k1=${payInfoK1.key}`, payLink: `${this.settings.serviceUrl}/api/guest/lnurl_pay/info?k1=${payInfoK1.key}`,
} }*/
} }
async HandleLnurlWithdraw(k1: string, invoice: string): Promise<void> { async HandleLnurlWithdraw(k1: string, invoice: string): Promise<void> {
@ -188,7 +211,7 @@ export default class {
tag: 'payRequest', tag: 'payRequest',
callback: `${url}?k1=${payK1.key}`, callback: `${url}?k1=${payK1.key}`,
maxSendable: 10000000000, maxSendable: 10000000000,
minSendable: 0, minSendable: 10000,
metadata: defaultLnurlPayMetadata metadata: defaultLnurlPayMetadata
} }
} }
@ -200,7 +223,7 @@ export default class {
tag: 'payRequest', tag: 'payRequest',
callback: `${this.settings.serviceUrl}/api/guest/lnurl_pay/handle?k1=${payK1.key}`, callback: `${this.settings.serviceUrl}/api/guest/lnurl_pay/handle?k1=${payK1.key}`,
maxSendable: 10000000, maxSendable: 10000000,
minSendable: 0, minSendable: 10000,
metadata: defaultLnurlPayMetadata metadata: defaultLnurlPayMetadata
} }
} }
@ -263,18 +286,25 @@ export default class {
} }
async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number, linkedApplication?: Application) { async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number, linkedApplication?: Application) {
if (!linkedApplication) {
throw new Error("only application operations are supported") // TODO - make this check obsolete
}
await this.storage.StartTransaction(async tx => { await this.storage.StartTransaction(async tx => {
const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx) const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx)
const toUser = await this.storage.userStorage.GetUser(toUserId, tx) const toUser = await this.storage.userStorage.GetUser(toUserId, tx)
if (fromUser.balance_sats < amount) { if (fromUser.balance_sats < amount) {
throw new Error("not enough balance to send user to user payment") throw new Error("not enough balance to send user to user payment")
} }
const fee = this.getServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount) if (!linkedApplication) {
throw new Error("only application operations are supported") // TODO - make this check obsolete
}
const isAppUserPayment = fromUser.user_id !== linkedApplication.owner.user_id
let fee = this.getServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount, isAppUserPayment)
const toIncrement = amount - fee const toIncrement = amount - fee
await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, amount, tx) await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, amount, tx)
await this.storage.userStorage.IncrementUserBalance(toUser.user_id, toIncrement, tx) await this.storage.userStorage.IncrementUserBalance(toUser.user_id, toIncrement, tx)
await this.storage.paymentStorage.AddUserToUserPayment(fromUserId, toUserId, amount, fee) await this.storage.paymentStorage.AddUserToUserPayment(fromUserId, toUserId, amount, fee)
if (linkedApplication) { if (isAppUserPayment && fee > 0) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee) await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee)
} }
}) })

View file

@ -6,9 +6,12 @@ export type MainSettings = {
jwtSecret: string jwtSecret: string
incomingTxFee: number incomingTxFee: number
outgoingTxFee: number outgoingTxFee: number
incomingInvoiceFee: number incomingAppInvoiceFee: number
outgoingInvoiceFee: number incomingAppUserInvoiceFee: number
outgoingAppInvoiceFee: number
outgoingAppUserInvoiceFee: number
userToUserFee: number userToUserFee: number
appToUserFee: number
serviceUrl: string serviceUrl: string
servicePort: number servicePort: number