configurable sybil fee (#1577)
* configurable sybil fee * document getSybilFeePercent * fixes * remove null check * refine at the margins --------- Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
parent
fdd34b2eb3
commit
18700b4201
@ -167,10 +167,11 @@ All functions have the following signature: `function(args: Object, context: Obj
|
|||||||
- this function is called when an optimistic action is retried
|
- this function is called when an optimistic action is retried
|
||||||
- it's passed the original `invoiceId` and the `newInvoiceId`
|
- it's passed the original `invoiceId` and the `newInvoiceId`
|
||||||
- this function should update the rows created in `perform` to contain the new `newInvoiceId` and remark the row as `PENDING`
|
- this function should update the rows created in `perform` to contain the new `newInvoiceId` and remark the row as `PENDING`
|
||||||
- `invoiceablePeer`: returns the userId of the peer that's capable of generating an invoice so they can be paid for the action
|
- `getInvoiceablePeer`: returns the userId of the peer that's capable of generating an invoice so they can be paid for the action
|
||||||
- this is only used for p2p wrapped zaps currently
|
- this is only used for p2p wrapped zaps currently
|
||||||
- `describe`: returns a description as a string of the action
|
- `describe`: returns a description as a string of the action
|
||||||
- for actions that require generating an invoice, and for stackers that don't hide invoice descriptions, this is used in the invoice description
|
- for actions that require generating an invoice, and for stackers that don't hide invoice descriptions, this is used in the invoice description
|
||||||
|
- `getSybilFeePercent` (required if `getInvoiceablePeer` is implemented): returns the action sybil fee percent as a `BigInt` (eg. 30n for 30%)
|
||||||
|
|
||||||
#### Function arguments
|
#### Function arguments
|
||||||
|
|
||||||
@ -179,6 +180,7 @@ All functions have the following signature: `function(args: Object, context: Obj
|
|||||||
`context` contains the following fields:
|
`context` contains the following fields:
|
||||||
- `me`: the user performing the action (undefined if anonymous)
|
- `me`: the user performing the action (undefined if anonymous)
|
||||||
- `cost`: the cost of the action in msats as a `BigInt`
|
- `cost`: the cost of the action in msats as a `BigInt`
|
||||||
|
- `sybilFeePercent`: the sybil fee percent as a `BigInt` (eg. 30n for 30%)
|
||||||
- `tx`: the current transaction (for anything that needs to be done atomically with the payment)
|
- `tx`: the current transaction (for anything that needs to be done atomically with the payment)
|
||||||
- `models`: the current prisma client (for anything that doesn't need to be done atomically with the payment)
|
- `models`: the current prisma client (for anything that doesn't need to be done atomically with the payment)
|
||||||
- `lnd`: the current lnd client
|
- `lnd`: the current lnd client
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createHodlInvoice, createInvoice, parsePaymentRequest } from 'ln-service'
|
import { createHodlInvoice, createInvoice, parsePaymentRequest } from 'ln-service'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
import { PAID_ACTION_TERMINAL_STATES, USER_ID } from '@/lib/constants'
|
import { PAID_ACTION_TERMINAL_STATES, USER_ID } from '@/lib/constants'
|
||||||
import { createHmac, walletLogger } from '@/api/resolvers/wallet'
|
import { createHmac } from '@/api/resolvers/wallet'
|
||||||
import { Prisma } from '@prisma/client'
|
import { Prisma } from '@prisma/client'
|
||||||
import * as ITEM_CREATE from './itemCreate'
|
import * as ITEM_CREATE from './itemCreate'
|
||||||
import * as ITEM_UPDATE from './itemUpdate'
|
import * as ITEM_UPDATE from './itemUpdate'
|
||||||
@ -14,8 +14,7 @@ import * as TERRITORY_BILLING from './territoryBilling'
|
|||||||
import * as TERRITORY_UNARCHIVE from './territoryUnarchive'
|
import * as TERRITORY_UNARCHIVE from './territoryUnarchive'
|
||||||
import * as DONATE from './donate'
|
import * as DONATE from './donate'
|
||||||
import * as BOOST from './boost'
|
import * as BOOST from './boost'
|
||||||
import wrapInvoice from 'wallets/wrap'
|
import { createWrappedInvoice } from 'wallets/server'
|
||||||
import { createInvoice as createUserInvoice } from 'wallets/server'
|
|
||||||
|
|
||||||
export const paidActions = {
|
export const paidActions = {
|
||||||
ITEM_CREATE,
|
ITEM_CREATE,
|
||||||
@ -44,6 +43,7 @@ export default async function performPaidAction (actionType, args, context) {
|
|||||||
|
|
||||||
context.me = me ? await models.user.findUnique({ where: { id: me.id } }) : undefined
|
context.me = me ? await models.user.findUnique({ where: { id: me.id } }) : undefined
|
||||||
context.cost = await paidAction.getCost(args, context)
|
context.cost = await paidAction.getCost(args, context)
|
||||||
|
context.sybilFeePercent = await paidAction.getSybilFeePercent?.(args, context)
|
||||||
|
|
||||||
if (!me) {
|
if (!me) {
|
||||||
if (!paidAction.anonable) {
|
if (!paidAction.anonable) {
|
||||||
@ -229,8 +229,7 @@ const MAX_PENDING_PAID_ACTIONS_PER_USER = 100
|
|||||||
export async function createLightningInvoice (actionType, args, context) {
|
export async function createLightningInvoice (actionType, args, context) {
|
||||||
// if the action has an invoiceable peer, we'll create a peer invoice
|
// if the action has an invoiceable peer, we'll create a peer invoice
|
||||||
// wrap it, and return the wrapped invoice
|
// wrap it, and return the wrapped invoice
|
||||||
const { cost, models, lnd, me } = context
|
const { cost, models, lnd, sybilFeePercent, me } = context
|
||||||
const userId = await paidActions[actionType]?.invoiceablePeer?.(args, context)
|
|
||||||
|
|
||||||
// count pending invoices and bail if we're over the limit
|
// count pending invoices and bail if we're over the limit
|
||||||
const pendingInvoices = await models.invoice.count({
|
const pendingInvoices = await models.invoice.count({
|
||||||
@ -248,33 +247,29 @@ export async function createLightningInvoice (actionType, args, context) {
|
|||||||
throw new Error('You have too many pending paid actions, cancel some or wait for them to expire')
|
throw new Error('You have too many pending paid actions, cancel some or wait for them to expire')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userId = await paidActions[actionType]?.getInvoiceablePeer?.(args, context)
|
||||||
if (userId) {
|
if (userId) {
|
||||||
let logger, bolt11
|
|
||||||
try {
|
try {
|
||||||
|
if (!sybilFeePercent) {
|
||||||
|
throw new Error('sybil fee percent is not set for an invoiceable peer action')
|
||||||
|
}
|
||||||
|
|
||||||
const description = await paidActions[actionType].describe(args, context)
|
const description = await paidActions[actionType].describe(args, context)
|
||||||
const { invoice, wallet } = await createUserInvoice(userId, {
|
|
||||||
// this is the amount the stacker will receive, the other 3/10ths is the sybil fee
|
const { invoice, wrappedInvoice, wallet, maxFee } = await createWrappedInvoice(userId, {
|
||||||
msats: cost * BigInt(7) / BigInt(10),
|
msats: cost,
|
||||||
|
feePercent: sybilFeePercent,
|
||||||
description,
|
description,
|
||||||
expiry: INVOICE_EXPIRE_SECS
|
expiry: INVOICE_EXPIRE_SECS
|
||||||
}, { models })
|
}, { models, me, lnd })
|
||||||
|
|
||||||
logger = walletLogger({ wallet, models })
|
|
||||||
bolt11 = invoice
|
|
||||||
|
|
||||||
// the sender (me) decides if the wrapped invoice has a description
|
|
||||||
// whereas the recipient decides if their invoice has a description
|
|
||||||
const { invoice: wrappedInvoice, maxFee } = await wrapInvoice(
|
|
||||||
bolt11, { msats: cost, description }, { me, lnd })
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bolt11,
|
bolt11: invoice,
|
||||||
wrappedBolt11: wrappedInvoice.request,
|
wrappedBolt11: wrappedInvoice,
|
||||||
wallet,
|
wallet,
|
||||||
maxFee
|
maxFee
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger?.error('invalid invoice: ' + e.message, { bolt11 })
|
|
||||||
console.error('failed to create stacker invoice, falling back to SN invoice', e)
|
console.error('failed to create stacker invoice, falling back to SN invoice', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ export async function getCost ({ sats }) {
|
|||||||
return satsToMsats(sats)
|
return satsToMsats(sats)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function invoiceablePeer ({ id }, { models }) {
|
export async function getInvoiceablePeer ({ id }, { models }) {
|
||||||
const item = await models.item.findUnique({
|
const item = await models.item.findUnique({
|
||||||
where: { id: parseInt(id) },
|
where: { id: parseInt(id) },
|
||||||
include: {
|
include: {
|
||||||
@ -27,8 +27,12 @@ export async function invoiceablePeer ({ id }, { models }) {
|
|||||||
return item.user.wallets.length > 0 && item.itemForwards.length === 0 ? item.userId : null
|
return item.user.wallets.length > 0 && item.itemForwards.length === 0 ? item.userId : null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function perform ({ invoiceId, sats, id: itemId, ...args }, { me, cost, tx }) {
|
export async function getSybilFeePercent () {
|
||||||
const feeMsats = 3n * (cost / BigInt(10)) // 30% fee
|
return 30n
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function perform ({ invoiceId, sats, id: itemId, ...args }, { me, cost, sybilFeePercent, tx }) {
|
||||||
|
const feeMsats = cost * sybilFeePercent / 100n
|
||||||
const zapMsats = cost - feeMsats
|
const zapMsats = cost - feeMsats
|
||||||
itemId = parseInt(itemId)
|
itemId = parseInt(itemId)
|
||||||
|
|
||||||
|
@ -494,26 +494,6 @@ export const lud18PayerDataSchema = (k1) => object({
|
|||||||
identifier: string()
|
identifier: string()
|
||||||
})
|
})
|
||||||
|
|
||||||
// check if something is _really_ a number.
|
|
||||||
// returns true for every number in this range: [-Infinity, ..., 0, ..., Infinity]
|
|
||||||
export const isNumber = x => typeof x === 'number' && !Number.isNaN(x)
|
|
||||||
|
|
||||||
export const toNumber = (x, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) => {
|
|
||||||
if (typeof x === 'undefined') {
|
|
||||||
throw new Error('value is required')
|
|
||||||
}
|
|
||||||
const n = Number(x)
|
|
||||||
if (isNumber(n)) {
|
|
||||||
if (x < min || x > max) {
|
|
||||||
throw new Error(`value ${x} must be between ${min} and ${max}`)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
throw new Error(`value ${x} is not a number`)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const toPositiveNumber = (x) => toNumber(x, 0)
|
|
||||||
|
|
||||||
export const deviceSyncSchema = object().shape({
|
export const deviceSyncSchema = object().shape({
|
||||||
passphrase: string().required('required')
|
passphrase: string().required('required')
|
||||||
.test(async (value, context) => {
|
.test(async (value, context) => {
|
||||||
@ -533,3 +513,79 @@ export const deviceSyncSchema = object().shape({
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// check if something is _really_ a number.
|
||||||
|
// returns true for every number in this range: [-Infinity, ..., 0, ..., Infinity]
|
||||||
|
export const isNumber = x => typeof x === 'number' && !Number.isNaN(x)
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any | bigint} x
|
||||||
|
* @param {number} min
|
||||||
|
* @param {number} max
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export const toNumber = (x, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) => {
|
||||||
|
if (typeof x === 'undefined') {
|
||||||
|
throw new Error('value is required')
|
||||||
|
}
|
||||||
|
if (typeof x === 'bigint') {
|
||||||
|
if (x < BigInt(min) || x > BigInt(max)) {
|
||||||
|
throw new Error(`value ${x} must be between ${min} and ${max}`)
|
||||||
|
}
|
||||||
|
return Number(x)
|
||||||
|
} else {
|
||||||
|
const n = Number(x)
|
||||||
|
if (isNumber(n)) {
|
||||||
|
if (x < min || x > max) {
|
||||||
|
throw new Error(`value ${x} must be between ${min} and ${max}`)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`value ${x} is not a number`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any | bigint} x
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export const toPositiveNumber = (x) => toNumber(x, 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} x
|
||||||
|
* @param {bigint | number} [min]
|
||||||
|
* @param {bigint | number} [max]
|
||||||
|
* @returns {bigint}
|
||||||
|
*/
|
||||||
|
export const toBigInt = (x, min, max) => {
|
||||||
|
if (typeof x === 'undefined') throw new Error('value is required')
|
||||||
|
|
||||||
|
const n = BigInt(x)
|
||||||
|
if (min !== undefined && n < BigInt(min)) {
|
||||||
|
throw new Error(`value ${x} must be at least ${min}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max !== undefined && n > BigInt(max)) {
|
||||||
|
throw new Error(`value ${x} must be at most ${max}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number|bigint} x
|
||||||
|
* @returns {bigint}
|
||||||
|
*/
|
||||||
|
export const toPositiveBigInt = (x) => {
|
||||||
|
return toBigInt(x, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number|bigint} x
|
||||||
|
* @returns {number|bigint}
|
||||||
|
*/
|
||||||
|
export const toPositive = (x) => {
|
||||||
|
if (typeof x === 'bigint') return toPositiveBigInt(x)
|
||||||
|
return toPositiveNumber(x)
|
||||||
|
}
|
||||||
|
@ -14,11 +14,12 @@ import * as webln from 'wallets/webln'
|
|||||||
import { walletLogger } from '@/api/resolvers/wallet'
|
import { walletLogger } from '@/api/resolvers/wallet'
|
||||||
import walletDefs from 'wallets/server'
|
import walletDefs from 'wallets/server'
|
||||||
import { parsePaymentRequest } from 'ln-service'
|
import { parsePaymentRequest } from 'ln-service'
|
||||||
import { toPositiveNumber } from '@/lib/validate'
|
import { toPositiveBigInt, toPositiveNumber } from '@/lib/validate'
|
||||||
import { PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
|
import { PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
|
||||||
import { withTimeout } from '@/lib/time'
|
import { withTimeout } from '@/lib/time'
|
||||||
import { canReceive } from './common'
|
import { canReceive } from './common'
|
||||||
import { formatMsats, formatSats, msatsToSats } from '@/lib/format'
|
import { formatMsats, formatSats, msatsToSats } from '@/lib/format'
|
||||||
|
import wrapInvoice from './wrap'
|
||||||
|
|
||||||
export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd, blink, lnc, webln]
|
export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd, blink, lnc, webln]
|
||||||
|
|
||||||
@ -96,6 +97,37 @@ export async function createInvoice (userId, { msats, description, descriptionHa
|
|||||||
throw new Error('no wallet to receive available')
|
throw new Error('no wallet to receive available')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createWrappedInvoice (userId,
|
||||||
|
{ msats, feePercent, description, descriptionHash, expiry = 360 },
|
||||||
|
{ models, me, lnd }) {
|
||||||
|
let logger, bolt11
|
||||||
|
try {
|
||||||
|
const { invoice, wallet } = await createInvoice(userId, {
|
||||||
|
// this is the amount the stacker will receive, the other (feePercent)% is our fee
|
||||||
|
msats: toPositiveBigInt(msats) * (100n - feePercent) / 100n,
|
||||||
|
description,
|
||||||
|
descriptionHash,
|
||||||
|
expiry
|
||||||
|
}, { models })
|
||||||
|
|
||||||
|
logger = walletLogger({ wallet, models })
|
||||||
|
bolt11 = invoice
|
||||||
|
|
||||||
|
const { invoice: wrappedInvoice, maxFee } =
|
||||||
|
await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd })
|
||||||
|
|
||||||
|
return {
|
||||||
|
invoice,
|
||||||
|
wrappedInvoice: wrappedInvoice.request,
|
||||||
|
wallet,
|
||||||
|
maxFee
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger?.error('invalid invoice: ' + e.message, { bolt11 })
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function walletCreateInvoice (
|
async function walletCreateInvoice (
|
||||||
{
|
{
|
||||||
msats,
|
msats,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createHodlInvoice, parsePaymentRequest } from 'ln-service'
|
import { createHodlInvoice, parsePaymentRequest } from 'ln-service'
|
||||||
import { estimateRouteFee, getBlockHeight } from '../api/lnd'
|
import { estimateRouteFee, getBlockHeight } from '../api/lnd'
|
||||||
import { toPositiveNumber } from '@/lib/validate'
|
import { toBigInt, toPositiveBigInt, toPositiveNumber } from '@/lib/validate'
|
||||||
|
|
||||||
const MIN_OUTGOING_MSATS = BigInt(900) // the minimum msats we'll allow for the outgoing invoice
|
const MIN_OUTGOING_MSATS = BigInt(900) // the minimum msats we'll allow for the outgoing invoice
|
||||||
const MAX_OUTGOING_MSATS = BigInt(900_000_000) // the maximum msats we'll allow for the outgoing invoice
|
const MAX_OUTGOING_MSATS = BigInt(900_000_000) // the maximum msats we'll allow for the outgoing invoice
|
||||||
@ -9,20 +9,26 @@ const INCOMING_EXPIRATION_BUFFER_MSECS = 300_000 // the buffer enforce for the i
|
|||||||
const MAX_OUTGOING_CLTV_DELTA = 500 // the maximum cltv delta we'll allow for the outgoing invoice
|
const MAX_OUTGOING_CLTV_DELTA = 500 // the maximum cltv delta we'll allow for the outgoing invoice
|
||||||
export const MIN_SETTLEMENT_CLTV_DELTA = 80 // the minimum blocks we'll leave for settling the incoming invoice
|
export const MIN_SETTLEMENT_CLTV_DELTA = 80 // the minimum blocks we'll leave for settling the incoming invoice
|
||||||
const FEE_ESTIMATE_TIMEOUT_SECS = 5 // the timeout for the fee estimate request
|
const FEE_ESTIMATE_TIMEOUT_SECS = 5 // the timeout for the fee estimate request
|
||||||
const MAX_FEE_ESTIMATE_PERCENT = 0.025 // the maximum fee relative to outgoing we'll allow for the fee estimate
|
const MAX_FEE_ESTIMATE_PERCENT = 3n // the maximum fee relative to outgoing we'll allow for the fee estimate
|
||||||
const ZAP_SYBIL_FEE_MULT = 10 / 7 // the fee for the zap sybil service
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The wrapInvoice function is used to wrap an outgoing invoice with the necessary parameters for an incoming hold invoice.
|
The wrapInvoice function is used to wrap an outgoing invoice with the necessary parameters for an incoming hold invoice.
|
||||||
|
|
||||||
@param bolt11 {string} the bolt11 invoice to wrap
|
@param args {object} {
|
||||||
@param options {object}
|
bolt11: {string} the bolt11 invoice to wrap
|
||||||
|
feePercent: {bigint} the fee percent to use for the incoming invoice
|
||||||
|
}
|
||||||
|
@param options {object} {
|
||||||
|
msats: {bigint} the amount in msats to use for the incoming invoice
|
||||||
|
description: {string} the description to use for the incoming invoice
|
||||||
|
descriptionHash: {string} the description hash to use for the incoming invoice
|
||||||
|
}
|
||||||
@returns {
|
@returns {
|
||||||
invoice: the wrapped incoming invoice,
|
invoice: the wrapped incoming invoice,
|
||||||
maxFee: number
|
maxFee: number
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
export default async function wrapInvoice (bolt11, { msats, description, descriptionHash }, { me, lnd }) {
|
export default async function wrapInvoice ({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd }) {
|
||||||
try {
|
try {
|
||||||
console.group('wrapInvoice', description)
|
console.group('wrapInvoice', description)
|
||||||
|
|
||||||
@ -38,9 +44,17 @@ export default async function wrapInvoice (bolt11, { msats, description, descrip
|
|||||||
|
|
||||||
console.log('invoice', inv.id, inv.mtokens, inv.expires_at, inv.cltv_delta, inv.destination)
|
console.log('invoice', inv.id, inv.mtokens, inv.expires_at, inv.cltv_delta, inv.destination)
|
||||||
|
|
||||||
|
// validate fee percent
|
||||||
|
if (feePercent) {
|
||||||
|
// assert the fee percent is in the range 0-100
|
||||||
|
feePercent = toBigInt(feePercent, 0n, 100n)
|
||||||
|
} else {
|
||||||
|
throw new Error('Fee percent is missing')
|
||||||
|
}
|
||||||
|
|
||||||
// validate outgoing amount
|
// validate outgoing amount
|
||||||
if (inv.mtokens) {
|
if (inv.mtokens) {
|
||||||
outgoingMsat = toPositiveNumber(inv.mtokens)
|
outgoingMsat = toPositiveBigInt(inv.mtokens)
|
||||||
if (outgoingMsat < MIN_OUTGOING_MSATS) {
|
if (outgoingMsat < MIN_OUTGOING_MSATS) {
|
||||||
throw new Error(`Invoice amount is too low: ${outgoingMsat}`)
|
throw new Error(`Invoice amount is too low: ${outgoingMsat}`)
|
||||||
}
|
}
|
||||||
@ -53,8 +67,11 @@ export default async function wrapInvoice (bolt11, { msats, description, descrip
|
|||||||
|
|
||||||
// validate incoming amount
|
// validate incoming amount
|
||||||
if (msats) {
|
if (msats) {
|
||||||
msats = toPositiveNumber(msats)
|
msats = toPositiveBigInt(msats)
|
||||||
if (outgoingMsat * ZAP_SYBIL_FEE_MULT > msats) {
|
// outgoing amount should be smaller than the incoming amount
|
||||||
|
// by a factor of exactly 100n / (100n - feePercent)
|
||||||
|
const incomingMsats = outgoingMsat * 100n / (100n - feePercent)
|
||||||
|
if (incomingMsats > msats) {
|
||||||
throw new Error('Sybil fee is too low')
|
throw new Error('Sybil fee is too low')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -162,7 +179,7 @@ export default async function wrapInvoice (bolt11, { msats, description, descrip
|
|||||||
|
|
||||||
// validate the fee budget
|
// validate the fee budget
|
||||||
const minEstFees = toPositiveNumber(routingFeeMsat)
|
const minEstFees = toPositiveNumber(routingFeeMsat)
|
||||||
const outgoingMaxFeeMsat = Math.ceil(msats * MAX_FEE_ESTIMATE_PERCENT)
|
const outgoingMaxFeeMsat = Math.ceil(toPositiveNumber(msats * MAX_FEE_ESTIMATE_PERCENT) / 100)
|
||||||
if (minEstFees > outgoingMaxFeeMsat) {
|
if (minEstFees > outgoingMaxFeeMsat) {
|
||||||
throw new Error('Estimated fees are too high')
|
throw new Error('Estimated fees are too high')
|
||||||
}
|
}
|
||||||
|
@ -112,8 +112,10 @@ async function transitionInvoice (jobName, { invoiceId, fromState, toState, tran
|
|||||||
async function performPessimisticAction ({ lndInvoice, dbInvoice, tx, models, lnd, boss }) {
|
async function performPessimisticAction ({ lndInvoice, dbInvoice, tx, models, lnd, boss }) {
|
||||||
try {
|
try {
|
||||||
const args = { ...dbInvoice.actionArgs, invoiceId: dbInvoice.id }
|
const args = { ...dbInvoice.actionArgs, invoiceId: dbInvoice.id }
|
||||||
const result = await paidActions[dbInvoice.actionType].perform(args,
|
const context = { tx, cost: BigInt(lndInvoice.received_mtokens) }
|
||||||
{ models, tx, lnd, cost: BigInt(lndInvoice.received_mtokens), me: dbInvoice.user })
|
context.sybilFeePercent = await paidActions[dbInvoice.actionType].getSybilFeePercent?.(args, context)
|
||||||
|
|
||||||
|
const result = await paidActions[dbInvoice.actionType].perform(args, context)
|
||||||
await tx.invoice.update({
|
await tx.invoice.update({
|
||||||
where: { id: dbInvoice.id },
|
where: { id: dbInvoice.id },
|
||||||
data: {
|
data: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user