addressbook

This commit is contained in:
boufni95 2023-11-18 22:22:47 +01:00
parent aeae71baf8
commit 43fc36ac01
2 changed files with 78 additions and 48 deletions

View file

@ -53,7 +53,7 @@ export default class {
this.storage = new Storage(settings.storageSettings) this.storage = new Storage(settings.storageSettings)
this.lnd = NewLightningHandler(settings.lndSettings, this.addressPaidCb, this.invoicePaidCb) this.lnd = NewLightningHandler(settings.lndSettings, this.addressPaidCb, this.invoicePaidCb)
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings) this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.addressPaidCb, this.invoicePaidCb)
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings) this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager) this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager) this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)

View file

@ -1,4 +1,5 @@
import { bech32 } from 'bech32' import { bech32 } from 'bech32'
import crypto from 'crypto'
import Storage from '../storage/index.js' import Storage from '../storage/index.js'
import * as Types from '../../../proto/autogenerated/ts/types.js' import * as Types from '../../../proto/autogenerated/ts/types.js'
import { MainSettings } from './settings.js' import { MainSettings } from './settings.js'
@ -6,6 +7,10 @@ import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorag
import { LightningHandler } from '../lnd/index.js' import { LightningHandler } from '../lnd/index.js'
import { Application } from '../storage/entity/Application.js' import { Application } from '../storage/entity/Application.js'
import { getLogger } from '../helpers/logger.js' import { getLogger } from '../helpers/logger.js'
import { UserReceivingAddress } from '../storage/entity/UserReceivingAddress.js'
import { AddressPaidCb, InvoicePaidCb, PaidInvoice } from '../lnd/settings.js'
import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js'
import { SendCoinsResponse } from '../../../proto/lnd/lightning.js'
interface UserOperationInfo { interface UserOperationInfo {
serial_id: number serial_id: number
paid_amount: number paid_amount: number
@ -22,10 +27,14 @@ export default class {
storage: Storage storage: Storage
settings: MainSettings settings: MainSettings
lnd: LightningHandler lnd: LightningHandler
constructor(storage: Storage, lnd: LightningHandler, settings: MainSettings) { addressPaidCb: AddressPaidCb
invoicePaidCb: InvoicePaidCb
constructor(storage: Storage, lnd: LightningHandler, settings: MainSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
this.storage = storage this.storage = storage
this.settings = settings this.settings = settings
this.lnd = lnd this.lnd = lnd
this.addressPaidCb = addressPaidCb
this.invoicePaidCb = invoicePaidCb
} }
getServiceFee(action: Types.UserOperationType, amount: number, appUser: boolean): number { getServiceFee(action: Types.UserOperationType, amount: number, appUser: boolean): number {
@ -116,7 +125,13 @@ export default class {
amount: Number(decoded.numSatoshis) amount: Number(decoded.numSatoshis)
} }
} }
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication?: Application): Promise<Types.PayInvoiceResponse> {
async PayInvoiceInternal(app: Application, userId: string, invoice: UserReceivingInvoice, amount: number, action: Types.UserOperationType): Promise<Types.PayAddressResponse> {
this.invoicePaidCb(invoice.invoice, amount)
return { txId: "" }
}
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application): Promise<Types.PayInvoiceResponse> {
const decoded = await this.lnd.DecodeInvoice(req.invoice) const decoded = await this.lnd.DecodeInvoice(req.invoice)
if (decoded.numSatoshis !== 0 && req.amount !== 0) { if (decoded.numSatoshis !== 0 && req.amount !== 0) {
throw new Error("invoice has value, do not provide amount the the request") throw new Error("invoice has value, do not provide amount the the request")
@ -125,55 +140,70 @@ 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)
if (!linkedApplication) {
throw new Error("only application operations are supported") // TODO - make this check obsolete
}
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.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount, isAppUserPayment)
const totalAmountToDecrement = payAmount + serviceFee const totalAmountToDecrement = payAmount + serviceFee
const internalInvoice = await this.storage.paymentStorage.GetInvoiceOwner(req.invoice)
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount) let payment: PaidInvoice | null = null
await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit) if (!internalInvoice) {
let payment const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
try { await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit)
payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit) try {
await this.storage.userStorage.UnlockUser(userId) payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit)
} catch (err) { await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + payment.feeSat)
await this.storage.userStorage.UnlockUser(userId) await this.storage.userStorage.UnlockUser(userId)
throw err } catch (err) {
await this.storage.userStorage.UnlockUser(userId)
throw err
}
} else {
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement)
this.invoicePaidCb(req.invoice, payAmount)
} }
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + Number(payment.feeSat))
if (isAppUserPayment && serviceFee > 0) { if (isAppUserPayment && serviceFee > 0) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee) await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee)
} }
await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, Number(payment.feeSat), serviceFee) const routingFees = payment ? payment.feeSat : 0
await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, routingFees, serviceFee)
return { return {
preimage: payment.paymentPreimage, preimage: payment ? payment.paymentPreimage : "",
amount_paid: Number(payment.valueSat) amount_paid: payment ? Number(payment.valueSat) : payAmount
} }
} }
async PayAddress(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
const userId = ctx.user_id async PayAddress(userId: string, req: Types.PayAddressRequest, linkedApplication: Application): Promise<Types.PayAddressResponse> {
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id) const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_TX, req.amoutSats, false)
const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, 1) const isAppUserPayment = userId !== linkedApplication.owner.user_id
const vBytes = Math.ceil(Number(estimate.feeSat / estimate.satPerVbyte)) const internalAddress = await this.storage.paymentStorage.GetAddressOwner(req.address)
const chainFees = vBytes * req.satsPerVByte let txId = ""
const total = req.amoutSats + chainFees let chainFees = 0
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, req.amoutSats, false) if (!internalAddress) {
await this.lockUserWithMinBalance(userId, total + serviceFee) const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, 1)
let payment const vBytes = Math.ceil(Number(estimate.feeSat / estimate.satPerVbyte))
try { chainFees = vBytes * req.satsPerVByte
payment = await this.lnd.PayAddress(req.address, req.amoutSats, req.satsPerVByte) const total = req.amoutSats + chainFees
await this.storage.userStorage.UnlockUser(userId) await this.lockUserWithMinBalance(userId, total + serviceFee)
} catch (err) { try {
await this.storage.userStorage.UnlockUser(userId) const payment = await this.lnd.PayAddress(req.address, req.amoutSats, req.satsPerVByte)
throw err txId = payment.txid
await this.storage.userStorage.DecrementUserBalance(userId, total + serviceFee)
await this.storage.userStorage.UnlockUser(userId)
} catch (err) {
await this.storage.userStorage.UnlockUser(userId)
throw err
}
} else {
await this.storage.userStorage.DecrementUserBalance(userId, req.amoutSats + serviceFee)
this.addressPaidCb({ hash: crypto.randomBytes(32).toString("hex"), index: 0 }, req.address, req.amoutSats)
} }
await this.storage.userStorage.DecrementUserBalance(userId, total + serviceFee)
await this.storage.paymentStorage.AddUserTransactionPayment(userId, req.address, payment.txid, 0, req.amoutSats, chainFees, serviceFee) if (isAppUserPayment && serviceFee > 0) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee)
}
await this.storage.paymentStorage.AddUserTransactionPayment(userId, req.address, txId, 0, req.amoutSats, chainFees, serviceFee)
return { return {
txId: payment.txid txId: txId
} }
} }
@ -211,8 +241,11 @@ export default class {
async HandleLnurlWithdraw(k1: string, invoice: string): Promise<void> { async HandleLnurlWithdraw(k1: string, invoice: string): Promise<void> {
const key = await this.storage.paymentStorage.UseUserEphemeralKey(k1, 'withdraw') const key = await this.storage.paymentStorage.UseUserEphemeralKey(k1, 'withdraw')
if (!key.linkedApplication) {
throw new Error("found lnurl key entry with no linked application")
}
try { try {
await this.PayInvoice(key.user.user_id, { invoice: invoice, amount: 0 }) await this.PayInvoice(key.user.user_id, { invoice: invoice, amount: 0 }, key.linkedApplication)
} catch (err: any) { } catch (err: any) {
console.error("error sending payment for lnurl withdraw to ", key.user.user_id, err) console.error("error sending payment for lnurl withdraw to ", key.user.user_id, err)
throw new Error("failed to pay invoice") throw new Error("failed to pay invoice")
@ -330,18 +363,13 @@ export default class {
} }
} }
async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number, linkedApplication?: Application) { async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number, linkedApplication: Application): Promise<number> {
if (!linkedApplication) { let sentAmount = 0
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 payment")
}
if (!linkedApplication) {
throw new Error("only application operations are supported") // TODO - make this check obsolete
} }
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.getServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount, isAppUserPayment)
@ -352,7 +380,9 @@ export default class {
if (isAppUserPayment && fee > 0) { if (isAppUserPayment && fee > 0) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee) await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee)
} }
sentAmount = toIncrement
}) })
return sentAmount
} }
encodeLnurl(base: string) { encodeLnurl(base: string) {