Msats to sats floor (#1307)
* make wallet invoice creation tests make full sat invoice * handle rounded/floored msats for receiving wallets * msats flooring to sats function
This commit is contained in:
parent
ab80873a57
commit
ffc156df2b
|
@ -231,7 +231,7 @@ export async function createLightningInvoice (actionType, args, context) {
|
|||
}, { models })
|
||||
|
||||
const { invoice: wrappedInvoice, maxFee } = await wrapInvoice(
|
||||
bolt11, { description }, { lnd })
|
||||
bolt11, { msats: cost, description }, { lnd })
|
||||
|
||||
return {
|
||||
bolt11,
|
||||
|
|
|
@ -52,6 +52,7 @@ export const msatsToSats = msats => {
|
|||
if (msats === null || msats === undefined) {
|
||||
return null
|
||||
}
|
||||
// implicitly floors the result
|
||||
return Number(BigInt(msats) / 1000n)
|
||||
}
|
||||
|
||||
|
@ -62,6 +63,8 @@ export const satsToMsats = sats => {
|
|||
return BigInt(sats) * 1000n
|
||||
}
|
||||
|
||||
export const msatsSatsFloor = msats => satsToMsats(msatsToSats(msats))
|
||||
|
||||
export const msatsToSatsDecimal = msats => {
|
||||
if (msats === null || msats === undefined) {
|
||||
return null
|
||||
|
|
|
@ -3,7 +3,7 @@ import { createInvoice as clnCreateInvoice } from '@/lib/cln'
|
|||
export * from 'wallets/cln'
|
||||
|
||||
export const testConnectServer = async ({ socket, rune, cert }) => {
|
||||
return await createInvoice({ msats: 1, expiry: 1, description: '' }, { socket, rune, cert })
|
||||
return await createInvoice({ msats: 1000, expiry: 1, description: '' }, { socket, rune, cert })
|
||||
}
|
||||
|
||||
export const createInvoice = async (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { msatsSatsFloor } from '@/lib/format'
|
||||
import { lnAddrOptions } from '@/lib/lnurl'
|
||||
|
||||
export * from 'wallets/lightning-address'
|
||||
|
@ -12,6 +13,10 @@ export const createInvoice = async (
|
|||
) => {
|
||||
const { callback, commentAllowed } = await lnAddrOptions(address)
|
||||
const callbackUrl = new URL(callback)
|
||||
|
||||
// most lnurl providers suck nards so we have to floor to nearest sat
|
||||
msats = msatsSatsFloor(msats)
|
||||
|
||||
callbackUrl.searchParams.append('amount', msats)
|
||||
|
||||
if (commentAllowed >= description?.length) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export * from 'wallets/lnbits'
|
||||
|
||||
export async function testConnectServer ({ url, invoiceKey }) {
|
||||
return await createInvoice({ msats: 1, expiry: 1 }, { url, invoiceKey })
|
||||
return await createInvoice({ msats: 1000, expiry: 1 }, { url, invoiceKey })
|
||||
}
|
||||
|
||||
export async function createInvoice (
|
||||
|
|
|
@ -4,7 +4,7 @@ import { authenticatedLndGrpc, createInvoice as lndCreateInvoice } from 'ln-serv
|
|||
export * from 'wallets/lnd'
|
||||
|
||||
export const testConnectServer = async ({ cert, macaroon, socket }) => {
|
||||
return await createInvoice({ msats: 1, expiry: 1 }, { cert, macaroon, socket })
|
||||
return await createInvoice({ msats: 1000, expiry: 1 }, { cert, macaroon, socket })
|
||||
}
|
||||
|
||||
export const createInvoice = async (
|
||||
|
|
|
@ -53,7 +53,21 @@ export async function createInvoice (userId, { msats, description, descriptionHa
|
|||
|
||||
const bolt11 = await parsePaymentRequest({ request: invoice })
|
||||
if (BigInt(bolt11.mtokens) !== BigInt(msats)) {
|
||||
throw new Error('invoice has incorrect amount')
|
||||
if (BigInt(bolt11.mtokens) > BigInt(msats)) {
|
||||
throw new Error(`invoice is for an amount greater than requested ${bolt11.mtokens} > ${msats}`)
|
||||
}
|
||||
if (BigInt(bolt11.mtokens) === 0n) {
|
||||
throw new Error('invoice is for 0 msats')
|
||||
}
|
||||
if (BigInt(msats) - BigInt(bolt11.mtokens) >= 1000n) {
|
||||
throw new Error(`invoice has a different satoshi amount ${bolt11.mtokens} !== ${msats}`)
|
||||
}
|
||||
|
||||
await addWalletLog({
|
||||
wallet,
|
||||
level: 'INFO',
|
||||
message: `wallet does not support msats so we floored ${msats} msats to nearest sat ${BigInt(bolt11.mtokens)} msats`
|
||||
}, { models })
|
||||
}
|
||||
|
||||
return { invoice, wallet }
|
||||
|
|
|
@ -19,10 +19,10 @@ const ZAP_SYBIL_FEE_MULT = 10 / 9 // the fee for the zap sybil service
|
|||
@param options {object}
|
||||
@returns {
|
||||
invoice: the wrapped incoming invoice,
|
||||
outgoingMaxFeeMsat: number
|
||||
maxFee: number
|
||||
}
|
||||
*/
|
||||
export default async function wrapInvoice (bolt11, { description, descriptionHash }, { lnd }) {
|
||||
export default async function wrapInvoice (bolt11, { msats, description, descriptionHash }, { lnd }) {
|
||||
try {
|
||||
console.group('wrapInvoice', description)
|
||||
|
||||
|
@ -38,7 +38,7 @@ export default async function wrapInvoice (bolt11, { description, descriptionHas
|
|||
|
||||
console.log('invoice', inv.mtokens, inv.expires_at, inv.cltv_delta)
|
||||
|
||||
// validate amount
|
||||
// validate outgoing amount
|
||||
if (inv.mtokens) {
|
||||
outgoingMsat = toPositiveNumber(inv.mtokens)
|
||||
if (outgoingMsat < MIN_OUTGOING_MSATS) {
|
||||
|
@ -48,7 +48,17 @@ export default async function wrapInvoice (bolt11, { description, descriptionHas
|
|||
throw new Error(`Invoice amount is too high: ${outgoingMsat}`)
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invoice amount is missing')
|
||||
throw new Error('Outgoing invoice is missing amount')
|
||||
}
|
||||
|
||||
// validate incoming amount
|
||||
if (msats) {
|
||||
msats = toPositiveNumber(msats)
|
||||
if (outgoingMsat * ZAP_SYBIL_FEE_MULT > msats) {
|
||||
throw new Error('Sybil fee is too low')
|
||||
}
|
||||
} else {
|
||||
throw new Error('Incoming invoice amount is missing')
|
||||
}
|
||||
|
||||
// validate features
|
||||
|
@ -145,13 +155,13 @@ export default async function wrapInvoice (bolt11, { description, descriptionHas
|
|||
|
||||
// validate the fee budget
|
||||
const minEstFees = toPositiveNumber(routingFeeMsat)
|
||||
const outgoingMaxFeeMsat = Math.ceil(outgoingMsat * MAX_FEE_ESTIMATE_PERCENT)
|
||||
const outgoingMaxFeeMsat = Math.ceil(msats * MAX_FEE_ESTIMATE_PERCENT)
|
||||
if (minEstFees > outgoingMaxFeeMsat) {
|
||||
throw new Error('Estimated fees are too high')
|
||||
}
|
||||
|
||||
// calculate the incoming invoice amount, without fees
|
||||
wrapped.mtokens = String(Math.ceil(outgoingMsat * ZAP_SYBIL_FEE_MULT))
|
||||
wrapped.mtokens = String(msats)
|
||||
console.log('outgoingMaxFeeMsat', outgoingMaxFeeMsat, 'wrapped', wrapped)
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { msatsToSats, satsToMsats } from '@/lib/format'
|
||||
import { msatsSatsFloor, msatsToSats, satsToMsats } from '@/lib/format'
|
||||
import { createWithdrawal } from '@/api/resolvers/wallet'
|
||||
import { createInvoice } from 'wallets/server'
|
||||
|
||||
|
@ -12,14 +12,13 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
|||
// excess must be greater than 10% of threshold
|
||||
if (excess < Number(threshold) * 0.1) return
|
||||
|
||||
const maxFeeMsats = Math.ceil(excess * (user.autoWithdrawMaxFeePercent / 100.0))
|
||||
const msats = excess - maxFeeMsats
|
||||
// floor fee to nearest sat but still denominated in msats
|
||||
const maxFeeMsats = msatsSatsFloor(Math.ceil(excess * (user.autoWithdrawMaxFeePercent / 100.0)))
|
||||
// msats will be floored by createInvoice if it needs to be
|
||||
const msats = BigInt(excess) - maxFeeMsats
|
||||
|
||||
// must be >= 1 sat
|
||||
if (msats < 1000) return
|
||||
|
||||
// maxFee is expected to be in sats, ie "msatsFeePaying" is always divisible by 1000
|
||||
const maxFee = msatsToSats(maxFeeMsats)
|
||||
if (msats < 1000n) return
|
||||
|
||||
// check that
|
||||
// 1. the user doesn't have an autowithdraw pending
|
||||
|
@ -33,7 +32,7 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
|||
OR (
|
||||
status <> 'CONFIRMED' AND
|
||||
now() < created_at + interval '1 hour' AND
|
||||
"msatsFeePaying" >= ${satsToMsats(maxFee)}
|
||||
"msatsFeePaying" >= ${maxFeeMsats}
|
||||
))
|
||||
)`
|
||||
|
||||
|
@ -41,6 +40,6 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
|||
|
||||
const { invoice, wallet } = await createInvoice(id, { msats, description: 'SN: autowithdrawal', expiry: 360 }, { models })
|
||||
return await createWithdrawal(null,
|
||||
{ invoice, maxFee },
|
||||
{ invoice, maxFee: msatsToSats(maxFeeMsats) },
|
||||
{ me: { id }, models, lnd, walletId: wallet.id })
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue