This commit is contained in:
boufni95 2025-12-15 17:13:50 +00:00
parent fd7a5eb1d6
commit e2dec7d9b3
14 changed files with 138 additions and 49 deletions

View file

@ -9,6 +9,7 @@
#LND_CERT_PATH=~/.lnd/tls.cert
#LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
#LND_LOG_DIR=~/.lnd/logs/bitcoin/mainnet/lnd.log
#BTC_NETWORK=mainnet
#BOOTSTRAP_PEER
# A trusted peer that will hold a node-level account until channel automation becomes affordable
@ -49,8 +50,9 @@
#LIGHTNING
# Maximum amount in network fees passed to LND when it pays an external invoice
# BPS are basis points, 100 BPS = 1%
#OUTBOUND_MAX_FEE_BPS=60
#OUTBOUND_MAX_FEE_EXTRA_SATS=100
#OUTBOUND_MAX_FEE_BPS=60 // deprecated
#OUTBOUND_MAX_FEE_EXTRA_SATS=100 // deprecated use OUTBOUND_FEE_FLOOR_SATS instead
#OUTBOUND_FEE_FLOOR_SATS=10
# If the back-end doesn't have adequate channel capacity, buy one from an LSP
# Will execute when it costs less than 1% of balance and uses a trusted peer
#BOOTSTRAP=1

View file

@ -1169,7 +1169,7 @@ The nostr server will send back a message response, and inside the body there wi
- __invitation_link__: _string_
### CumulativeFees
- __networkFeeFixed__: _number_
- __outboundFeeFloor__: _number_
- __serviceFeeBps__: _number_
### DebitAuthorization
@ -1601,6 +1601,7 @@ The nostr server will send back a message response, and inside the body there wi
- __swap_operation_id__: _string_
### SwapsList
- __quotes__: ARRAY of: _[TransactionSwapQuote](#TransactionSwapQuote)_
- __swaps__: ARRAY of: _[SwapOperation](#SwapOperation)_
### TransactionSwapQuote

View file

@ -230,8 +230,8 @@ type CreateOneTimeInviteLinkResponse struct {
Invitation_link string `json:"invitation_link"`
}
type CumulativeFees struct {
Networkfeefixed int64 `json:"networkFeeFixed"`
Servicefeebps int64 `json:"serviceFeeBps"`
Outboundfeefloor int64 `json:"outboundFeeFloor"`
Servicefeebps int64 `json:"serviceFeeBps"`
}
type DebitAuthorization struct {
Authorized bool `json:"authorized"`
@ -662,7 +662,8 @@ type SwapOperation struct {
Swap_operation_id string `json:"swap_operation_id"`
}
type SwapsList struct {
Swaps []SwapOperation `json:"swaps"`
Quotes []TransactionSwapQuote `json:"quotes"`
Swaps []SwapOperation `json:"swaps"`
}
type TransactionSwapQuote struct {
Chain_fee_sats int64 `json:"chain_fee_sats"`

View file

@ -1303,21 +1303,21 @@ export const CreateOneTimeInviteLinkResponseValidate = (o?: CreateOneTimeInviteL
}
export type CumulativeFees = {
networkFeeFixed: number
outboundFeeFloor: number
serviceFeeBps: number
}
export const CumulativeFeesOptionalFields: [] = []
export type CumulativeFeesOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
networkFeeFixed_CustomCheck?: (v: number) => boolean
outboundFeeFloor_CustomCheck?: (v: number) => boolean
serviceFeeBps_CustomCheck?: (v: number) => boolean
}
export const CumulativeFeesValidate = (o?: CumulativeFees, opts: CumulativeFeesOptions = {}, path: string = 'CumulativeFees::root.'): Error | null => {
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.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 (typeof o.outboundFeeFloor !== 'number') return new Error(`${path}.outboundFeeFloor: is not a number`)
if (opts.outboundFeeFloor_CustomCheck && !opts.outboundFeeFloor_CustomCheck(o.outboundFeeFloor)) return new Error(`${path}.outboundFeeFloor: custom check failed`)
if (typeof o.serviceFeeBps !== 'number') return new Error(`${path}.serviceFeeBps: is not a number`)
if (opts.serviceFeeBps_CustomCheck && !opts.serviceFeeBps_CustomCheck(o.serviceFeeBps)) return new Error(`${path}.serviceFeeBps: custom check failed`)
@ -3887,11 +3887,14 @@ export const SwapOperationValidate = (o?: SwapOperation, opts: SwapOperationOpti
}
export type SwapsList = {
quotes: TransactionSwapQuote[]
swaps: SwapOperation[]
}
export const SwapsListOptionalFields: [] = []
export type SwapsListOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
quotes_ItemOptions?: TransactionSwapQuoteOptions
quotes_CustomCheck?: (v: TransactionSwapQuote[]) => boolean
swaps_ItemOptions?: SwapOperationOptions
swaps_CustomCheck?: (v: SwapOperation[]) => boolean
}
@ -3899,6 +3902,13 @@ export const SwapsListValidate = (o?: SwapsList, opts: SwapsListOptions = {}, pa
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 (!Array.isArray(o.quotes)) return new Error(`${path}.quotes: is not an array`)
for (let index = 0; index < o.quotes.length; index++) {
const quotesErr = TransactionSwapQuoteValidate(o.quotes[index], opts.quotes_ItemOptions, `${path}.quotes[${index}]`)
if (quotesErr !== null) return quotesErr
}
if (opts.quotes_CustomCheck && !opts.quotes_CustomCheck(o.quotes)) return new Error(`${path}.quotes: custom check failed`)
if (!Array.isArray(o.swaps)) return new Error(`${path}.swaps: is not an array`)
for (let index = 0; index < o.swaps.length; index++) {
const swapsErr = SwapOperationValidate(o.swaps[index], opts.swaps_ItemOptions, `${path}.swaps[${index}]`)

View file

@ -853,10 +853,11 @@ message SwapOperation {
message SwapsList {
repeated SwapOperation swaps = 1;
repeated TransactionSwapQuote quotes = 2;
}
message CumulativeFees {
int64 networkFeeFixed = 2;
int64 outboundFeeFloor = 2;
int64 serviceFeeBps = 3;
}

View file

@ -14,6 +14,7 @@ import ws from 'ws';
import { getLogger, PubLogger, ERROR } from '../helpers/logger.js';
import SettingsManager from '../main/settingsManager.js';
import * as Types from '../../../proto/autogenerated/ts/types.js';
import { BTCNetwork } from '../main/settings.js';
type InvoiceSwapResponse = { id: string, claimPublicKey: string, swapTree: string }
type InvoiceSwapInfo = { paymentHash: string, keys: ECPairInterface }
@ -43,7 +44,6 @@ type TransactionSwapResponse = {
}
type TransactionSwapInfo = { destinationAddress: string, preimage: Buffer, keys: ECPairInterface, chainFee: number }
export type TransactionSwapData = { createdResponse: TransactionSwapResponse, info: TransactionSwapInfo }
const network = Networks.bitcoinMainnet
export class Swaps {
reverseSwaps: ReverseSwaps
submarineSwaps: SubmarineSwaps
@ -52,6 +52,8 @@ export class Swaps {
this.submarineSwaps = new SubmarineSwaps(settings)
}
Stop = () => { }
GetKeys = (privateKey: string) => {
const keys = ECPairFactory(ecc).fromPrivateKey(Buffer.from(privateKey, 'hex'))
return keys
@ -230,17 +232,11 @@ export class ReverseSwaps {
const url = `${this.settings.getSettings().swapsSettings.boltzHttpUrl}/v2/swap/reverse`
const req: any = {
onchainAmount: txAmount,
// invoiceAmount,
to: 'BTC',
from: 'BTC',
claimPublicKey: Buffer.from(keys.publicKey).toString('hex'),
preimageHash: createHash('sha256').update(preimage).digest('hex'),
}
/* if (amount.type === Types.TransactionSwapRequest_amount_type.INVOICE_AMOUNT_SATS) {
req.invoiceAmount = amount.invoice_amount_sats
} else if (amount.type === Types.TransactionSwapRequest_amount_type.TRANSACTION_AMOUNT_SATS) {
req.onchainAmount = amount.transaction_amount_sats
} */
const createdResponseRes = await loggedPost<TransactionSwapResponse>(this.log, url, req)
if (!createdResponseRes.ok) {
return createdResponseRes
@ -262,11 +258,21 @@ export class ReverseSwaps {
webSocket.on('open', () => {
webSocket.send(JSON.stringify(subReq))
})
let txId = ""
let txId = "", isDone = false
const done = () => {
isDone = true
webSocket.close()
swapDone({ ok: true, txId })
}
webSocket.on('error', (err) => {
this.log(ERROR, 'Error in WebSocket', err.message)
})
webSocket.on('close', () => {
if (!isDone) {
this.log(ERROR, 'WebSocket closed before swap was done');
swapDone({ ok: false, error: 'WebSocket closed before swap was done' })
}
})
webSocket.on('message', async (rawMsg) => {
try {
const result = await this.handleSwapTransactionMessage(rawMsg, data, done)
@ -275,6 +281,7 @@ export class ReverseSwaps {
}
} catch (err: any) {
this.log(ERROR, 'Error handling transaction WebSocket message', err.message)
isDone = true
webSocket.close()
swapDone({ ok: false, error: err.message })
return
@ -336,7 +343,7 @@ export class ReverseSwaps {
this.log(ERROR, 'No swap output found in lockup transaction');
return { ok: false, error: 'No swap output found in lockup transaction' }
}
const network = getNetwork(this.settings.getSettings().lndSettings.network)
// Create a claim transaction to be signed cooperatively via a key path spend
const claimTx = constructClaimTransaction(
[
@ -437,3 +444,16 @@ const loggedGet = async <T>(log: PubLogger, url: string): Promise<{ ok: true, da
return { ok: false, error: err.message }
}
}
const getNetwork = (network: BTCNetwork): Network => {
switch (network) {
case 'mainnet':
return Networks.bitcoinMainnet
case 'testnet':
return Networks.bitcoinTestnet
case 'regtest':
return Networks.bitcoinRegtest
default:
throw new Error(`Invalid network: ${network}`)
}
}

View file

@ -69,14 +69,14 @@ export default class {
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
}
const nostrSettings = this.settings.getSettings().nostrRelaySettings
const { max, networkFeeFixed, serviceFeeBps } = this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats)
const { max, outboundFeeFloor, serviceFeeBps } = this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats)
return {
userId: ctx.user_id,
balance: user.balance_sats,
max_withdrawable: max,
user_identifier: appUser.identifier,
network_max_fee_bps: 0,
network_max_fee_fixed: networkFeeFixed,
network_max_fee_fixed: outboundFeeFloor,
service_fee_bps: serviceFeeBps,
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: appUser.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),

View file

@ -167,7 +167,7 @@ export default class {
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 })
const { max, networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats)
const { max, outboundFeeFloor, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats)
return {
identifier: u.identifier,
info: {
@ -176,7 +176,7 @@ export default class {
max_withdrawable: max,
user_identifier: u.identifier,
network_max_fee_bps: 0,
network_max_fee_fixed: networkFeeFixed,
network_max_fee_fixed: outboundFeeFloor,
service_fee_bps: serviceFeeBps,
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: u.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
@ -225,14 +225,14 @@ export default class {
const app = await this.storage.applicationStorage.GetApplication(appId)
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
const nostrSettings = this.settings.getSettings().nostrRelaySettings
const { max, networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
const { max, outboundFeeFloor, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
return {
max_withdrawable: max, identifier: req.user_identifier, info: {
userId: user.user.user_id, balance: user.user.balance_sats,
max_withdrawable: max,
user_identifier: user.identifier,
network_max_fee_bps: 0,
network_max_fee_fixed: networkFeeFixed,
network_max_fee_fixed: outboundFeeFloor,
service_fee_bps: serviceFeeBps,
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: user.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),

View file

@ -170,7 +170,8 @@ export default class {
NewBlockHandler = async (height: number) => {
let confirmed: (PendingTx & { confs: number; })[]
let log = getLogger({})
this.storage.paymentStorage.DeleteExpiredTransactionSwaps(height)
.catch(err => log(ERROR, "failed to delete expired transaction swaps", err.message || err))
try {
const balanceEvents = await this.paymentManager.GetLndBalance()
await this.metricsManager.NewBlockCb(height, balanceEvents)

View file

@ -131,7 +131,7 @@ export class LiquidityProvider {
return res
}
this.feesCache = {
networkFeeFixed: res.network_max_fee_fixed,
outboundFeeFloor: res.network_max_fee_fixed,
serviceFeeBps: res.service_fee_bps
}
this.latestReceivedBalance = res.balance
@ -152,11 +152,11 @@ export class LiquidityProvider {
return 0
}
const balance = this.latestReceivedBalance
const { networkFeeFixed, serviceFeeBps } = this.feesCache
const { outboundFeeFloor, serviceFeeBps } = this.feesCache
const div = 1 + (serviceFeeBps / 10000)
const maxWithoutFixed = Math.floor(balance / div)
const fee = balance - maxWithoutFixed
return balance - Math.max(fee, networkFeeFixed)
return balance - Math.max(fee, outboundFeeFloor)
}
GetLatestBalance = () => {
@ -174,7 +174,7 @@ export class LiquidityProvider {
const fees = f ? f : this.GetFees()
const serviceFeeRate = fees.serviceFeeBps / 10000
const serviceFee = Math.ceil(serviceFeeRate * amount)
return Math.max(serviceFee, fees.networkFeeFixed)
return Math.max(serviceFee, fees.outboundFeeFloor)
}
CanProviderPay = async (amount: number, localServiceFee: number): Promise<boolean> => {

View file

@ -55,6 +55,7 @@ export default class {
liquidityManager: LiquidityManager
utils: Utils
swaps: Swaps
invoiceLock: InvoiceLock
constructor(storage: Storage, lnd: LND, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
this.storage = storage
this.settings = settings
@ -65,9 +66,13 @@ export default class {
this.swaps = new Swaps(settings)
this.addressPaidCb = addressPaidCb
this.invoicePaidCb = invoicePaidCb
this.invoiceLock = new InvoiceLock()
}
Stop() {
this.watchDog.Stop()
this.swaps.Stop()
}
checkPendingPayments = async () => {
@ -192,7 +197,7 @@ export default class {
throw new Error("Sending a transaction is not supported")
case Types.UserOperationType.OUTGOING_INVOICE:
const fee = this.getInvoicePaymentServiceFee(amount, appUser)
return Math.max(fee, this.settings.getSettings().lndSettings.feeFixedLimit)
return Math.max(fee, this.settings.getSettings().lndSettings.outboundFeeFloor)
case Types.UserOperationType.OUTGOING_USER_TO_USER:
if (appUser) {
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
@ -251,17 +256,17 @@ export default class {
GetFees = (): Types.CumulativeFees => {
const { outgoingAppUserInvoiceFeeBps } = this.settings.getSettings().serviceFeeSettings
const { feeFixedLimit } = this.settings.getSettings().lndSettings
return { networkFeeFixed: feeFixedLimit, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
const { outboundFeeFloor } = this.settings.getSettings().lndSettings
return { outboundFeeFloor, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
}
GetMaxPayableInvoice(balance: number): Types.CumulativeFees & { max: number } {
const { networkFeeFixed, serviceFeeBps } = this.GetFees()
const { outboundFeeFloor, serviceFeeBps } = this.GetFees()
const div = 1 + (serviceFeeBps / 10000)
const maxWithoutFixed = Math.floor(balance / div)
const fee = balance - maxWithoutFixed
const max = balance - Math.max(fee, networkFeeFixed)
return { max, networkFeeFixed, serviceFeeBps }
const max = balance - Math.max(fee, outboundFeeFloor)
return { max, outboundFeeFloor, serviceFeeBps }
}
async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> {
const decoded = await this.lnd.DecodeInvoice(req.invoice)
@ -277,10 +282,10 @@ export default class {
throw new Error("user is banned, cannot send payment")
}
if (req.expected_fees) {
const { networkFeeFixed, serviceFeeBps } = req.expected_fees
const serviceFixed = this.settings.getSettings().lndSettings.feeFixedLimit
const { outboundFeeFloor, serviceFeeBps } = req.expected_fees
const serviceFixed = this.settings.getSettings().lndSettings.outboundFeeFloor
const serviceBps = this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps
if (serviceFixed !== networkFeeFixed || serviceBps !== serviceFeeBps) {
if (serviceFixed !== outboundFeeFloor || serviceBps !== serviceFeeBps) {
throw new Error("fees do not match the expected fees")
}
}
@ -303,10 +308,20 @@ export default class {
throw new Error("this invoice was already paid")
}
let paymentInfo = { preimage: "", amtPaid: 0, networkFee: 0, serialId: 0 }
if (internalInvoice) {
paymentInfo = await this.PayInternalInvoice(userId, internalInvoice, { payAmount, serviceFee }, linkedApplication, req.debit_npub)
} else {
paymentInfo = await this.PayExternalInvoice(userId, req.invoice, { payAmount, serviceFee, amountForLnd: req.amount }, linkedApplication, { ...optionals, debitNpub: req.debit_npub })
if (this.invoiceLock.isLocked(req.invoice)) {
throw new Error("this invoice is already being paid")
}
this.invoiceLock.lock(req.invoice)
try {
if (internalInvoice) {
paymentInfo = await this.PayInternalInvoice(userId, internalInvoice, { payAmount, serviceFee }, linkedApplication, req.debit_npub)
} else {
paymentInfo = await this.PayExternalInvoice(userId, req.invoice, { payAmount, serviceFee, amountForLnd: req.amount }, linkedApplication, { ...optionals, debitNpub: req.debit_npub })
}
this.invoiceLock.unlock(req.invoice)
} catch (err) {
this.invoiceLock.unlock(req.invoice)
throw err
}
const feeDiff = serviceFee - paymentInfo.networkFee
if (isAppUserPayment && feeDiff > 0) {
@ -456,7 +471,7 @@ export default class {
async PayAddressWithSwap(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
this.log("paying external address")
if (!req.swap_operation_id) {
throw new Error("request a swap quote before payng an external address")
throw new Error("request a swap quote before paying an external address")
}
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
const txSwap = await this.storage.paymentStorage.GetTransactionSwap(req.swap_operation_id, ctx.app_user_id)
@ -547,6 +562,7 @@ export default class {
async ListSwaps(ctx: Types.UserContext): Promise<Types.SwapsList> {
const swaps = await this.storage.paymentStorage.ListCompletedSwaps(ctx.app_user_id)
const pendingSwaps = await this.storage.paymentStorage.ListPendingTransactionSwaps(ctx.app_user_id)
return {
swaps: swaps.map(s => {
const p = s.payment
@ -558,6 +574,17 @@ export default class {
address_paid: s.swap.address_paid,
failure_reason: s.swap.failure_reason,
}
}),
quotes: pendingSwaps.map(s => {
const serviceFee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_INVOICE, s.invoice_amount, true)
return {
swap_operation_id: s.swap_operation_id,
invoice_amount_sats: s.invoice_amount,
transaction_amount_sats: s.transaction_amount,
chain_fee_sats: s.chain_fee_sats,
service_fee_sats: serviceFee,
swap_fee_sats: s.swap_fee_sats,
}
})
}
}
@ -948,3 +975,16 @@ export default class {
}
}
class InvoiceLock {
locked: Record<string, boolean> = {}
lock(invoice: string) {
this.locked[invoice] = true
}
unlock(invoice: string) {
delete this.locked[invoice]
}
isLocked(invoice: string) {
return this.locked[invoice]
}
}

View file

@ -76,11 +76,13 @@ export type LndNodeSettings = {
lndCertPath: string // cold setting
lndMacaroonPath: string // cold setting
}
const networks = ['mainnet', 'testnet', 'regtest'] as const
export type BTCNetwork = (typeof networks)[number]
export type LndSettings = {
lndLogDir: string
feeFixedLimit: number
outboundFeeFloor: number
mockLnd: boolean
network: BTCNetwork
}
const resolveHome = (filepath: string) => {
@ -109,10 +111,14 @@ export const LoadLndNodeSettingsFromEnv = (dbEnv: Record<string, string | undefi
}
export const LoadLndSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LndSettings => {
const network = chooseEnv('BTC_NETWORK', dbEnv, 'mainnet', addToDb) as BTCNetwork
const limitOld = chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 10, addToDb)
const outboundFeeFloor = chooseEnvInt('OUTBOUND_FEE_FLOOR_SATS', dbEnv, limitOld, addToDb)
return {
lndLogDir: chooseEnv('LND_LOG_DIR', dbEnv, resolveHome("/.lnd/logs/bitcoin/mainnet/lnd.log"), addToDb),
feeFixedLimit: chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 10, addToDb),
mockLnd: false
outboundFeeFloor,
mockLnd: false,
network: networks.includes(network) ? network : 'mainnet'
}
}

View file

@ -45,6 +45,9 @@ export class TransactionSwap {
@Column()
ephemeral_public_key: string
// the private key is used on to perform a swap, it does not hold any funds once the swap is completed
// the swap should only last a few seconds, so it is not a security risk to store the private key in the database
// the key is stored here mostly for recovery purposes, in case something goes wrong with the swap
@Column()
ephemeral_private_key: string

View file

@ -494,6 +494,10 @@ export default class {
return this.dbs.Delete<TransactionSwap>('TransactionSwap', { timeout_block_height: LessThan(currentHeight) }, txId)
}
async ListPendingTransactionSwaps(appUserId: string, txId?: string) {
return this.dbs.Find<TransactionSwap>('TransactionSwap', { where: { used: false, app_user_id: appUserId } }, txId)
}
async ListCompletedSwaps(appUserId: string, txId?: string) {
const completed = await this.dbs.Find<TransactionSwap>('TransactionSwap', { where: { used: true, app_user_id: appUserId } }, txId)
const payments = await this.dbs.Find<UserInvoicePayment>('UserInvoicePayment', { where: { swap_operation_id: Not(IsNull()) } }, txId)