addressbook
This commit is contained in:
parent
aeae71baf8
commit
43fc36ac01
2 changed files with 78 additions and 48 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
let payment: PaidInvoice | null = null
|
||||||
|
if (!internalInvoice) {
|
||||||
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
||||||
await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit)
|
await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit)
|
||||||
let payment
|
|
||||||
try {
|
try {
|
||||||
payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit)
|
payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit)
|
||||||
|
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + payment.feeSat)
|
||||||
await this.storage.userStorage.UnlockUser(userId)
|
await this.storage.userStorage.UnlockUser(userId)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await this.storage.userStorage.UnlockUser(userId)
|
await this.storage.userStorage.UnlockUser(userId)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + Number(payment.feeSat))
|
} else {
|
||||||
|
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement)
|
||||||
|
this.invoicePaidCb(req.invoice, payAmount)
|
||||||
|
}
|
||||||
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 isAppUserPayment = userId !== linkedApplication.owner.user_id
|
||||||
|
const internalAddress = await this.storage.paymentStorage.GetAddressOwner(req.address)
|
||||||
|
let txId = ""
|
||||||
|
let chainFees = 0
|
||||||
|
if (!internalAddress) {
|
||||||
const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, 1)
|
const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, 1)
|
||||||
const vBytes = Math.ceil(Number(estimate.feeSat / estimate.satPerVbyte))
|
const vBytes = Math.ceil(Number(estimate.feeSat / estimate.satPerVbyte))
|
||||||
const chainFees = vBytes * req.satsPerVByte
|
chainFees = vBytes * req.satsPerVByte
|
||||||
const total = req.amoutSats + chainFees
|
const total = req.amoutSats + chainFees
|
||||||
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, req.amoutSats, false)
|
|
||||||
await this.lockUserWithMinBalance(userId, total + serviceFee)
|
await this.lockUserWithMinBalance(userId, total + serviceFee)
|
||||||
let payment
|
|
||||||
try {
|
try {
|
||||||
payment = await this.lnd.PayAddress(req.address, req.amoutSats, req.satsPerVByte)
|
const payment = await this.lnd.PayAddress(req.address, req.amoutSats, req.satsPerVByte)
|
||||||
|
txId = payment.txid
|
||||||
|
await this.storage.userStorage.DecrementUserBalance(userId, total + serviceFee)
|
||||||
await this.storage.userStorage.UnlockUser(userId)
|
await this.storage.userStorage.UnlockUser(userId)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await this.storage.userStorage.UnlockUser(userId)
|
await this.storage.userStorage.UnlockUser(userId)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
await this.storage.userStorage.DecrementUserBalance(userId, total + serviceFee)
|
} else {
|
||||||
await this.storage.paymentStorage.AddUserTransactionPayment(userId, req.address, payment.txid, 0, req.amoutSats, chainFees, serviceFee)
|
await this.storage.userStorage.DecrementUserBalance(userId, req.amoutSats + serviceFee)
|
||||||
|
this.addressPaidCb({ hash: crypto.randomBytes(32).toString("hex"), index: 0 }, req.address, req.amoutSats)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue