Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bolt12 support #1727

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e803efe
bolt12 attachment
riccardobl Dec 17, 2024
332d1e1
don't support direct payments to bolt12
riccardobl Dec 17, 2024
f927fc5
code cleanup, add bolt12info (bolt11 tags equivalent)
riccardobl Dec 17, 2024
494061c
add withdraw to bolt12, improve checks and naming
riccardobl Dec 17, 2024
8fbf5c2
Add create invoice test
riccardobl Dec 17, 2024
f68b69d
Add bolt12 logo
riccardobl Dec 17, 2024
7ff3e1b
improve labels
riccardobl Dec 17, 2024
95246bd
download from sn fork
riccardobl Dec 17, 2024
d326322
resolve bolt12 invoice inside attachment
riccardobl Dec 18, 2024
bae01b3
Merge branch 'master' into bolt12a
riccardobl Dec 18, 2024
20c3e58
rebase
riccardobl Dec 18, 2024
90f2c9c
add support for max_fee_mtokens in bolt12 interface
riccardobl Dec 18, 2024
b6cc65f
revert some unrelated changes
riccardobl Dec 18, 2024
6c863d8
Merge branch 'master' into bolt12a
riccardobl Dec 18, 2024
0e56bc8
deduplicate code
riccardobl Dec 20, 2024
cc993ff
Merge branch 'master' into bolt12a
riccardobl Dec 22, 2024
efcd9a2
Update lib/validate.js
riccardobl Dec 25, 2024
2411e99
Update wallets/bolt12/index.js
riccardobl Dec 25, 2024
28c24d5
fix trigger name
riccardobl Dec 25, 2024
f735d68
fix typo
riccardobl Dec 25, 2024
86a36ae
use String() to cast strings
riccardobl Dec 25, 2024
aae6de9
catch errors in async callback
riccardobl Dec 25, 2024
4191919
readd trim removed by mistake
riccardobl Dec 25, 2024
fa9ede4
removed unused default, rename lndSocket to lndkSocket
riccardobl Dec 25, 2024
63013c0
improve feature bit mapping
riccardobl Dec 25, 2024
406d3aa
add missing await
riccardobl Dec 25, 2024
501d272
permalink to repo
riccardobl Dec 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ LND_CERT=2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943516a434
LND_MACAROON=0201036c6e6402f801030a106cf4e146abffa5d766befbbf4c73b5a31201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e6572617465120472656164000006202c3bfd55c191e925cbffd73712c9d4b9b4a8440410bde5f8a0a6e33af8b3d876
LND_SOCKET=sn_lnd:10009

# xxd -p -c0 docker/lndk/tls-cert.pem
LNDK_CERT=2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942614443434151326741774942416749554f6d7333785a2b704256556e746e4644374a306d374c6c314d5a5977436759494b6f5a497a6a3045417749770a495445664d4230474131554541777757636d4e6e5a573467633256735a69427a615764755a5751675932567964444167467730334e5441784d4445774d4441770a4d444261474138304d446b324d4445774d5441774d4441774d466f77495445664d4230474131554541777757636d4e6e5a573467633256735a69427a615764750a5a575167593256796444425a4d424d4742797147534d34394167454743437147534d3439417745484130494142476475396358554753504979635343626d47620a362f34552b74787645306153767a734d632b704b4669586c422b502f33782f5778594d786c4842306c68396654515538746456694a3241592f516e485677556b0a4f34436a495441664d42304741315564455151574d42534343577876593246736147397a64494948633235666247356b617a414b42676771686b6a4f505151440a41674e4a41444247416945413738556450486764615856797474717432312b7557546c466e344236717565474c2f636d5970516269497343495143777859306e0a783276357a58457750552f624f6e61514e657139463841542b2f346c4b656c48664f4e2f47773d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a
LNDK_MACAROON=0201036c6e6402f801030a106cf4e146abffa5d766befbbf4c73b5a31201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e6572617465120472656164000006202c3bfd55c191e925cbffd73712c9d4b9b4a8440410bde5f8a0a6e33af8b3d876
LNDK_SOCKET=sn_lndk:7000



# nostr (NIP-57 zap receipts)
# openssl rand -hex 32
NOSTR_PRIVATE_KEY=5f30b7e7714360f51f2be2e30c1d93b7fdf67366e730658e85777dfcc4e4245f
Expand Down
6 changes: 6 additions & 0 deletions api/lnd/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cachedFetcher } from '@/lib/fetch'
import { toPositiveNumber } from '@/lib/format'
import { authenticatedLndGrpc } from '@/lib/lnd'
import { installLNDK } from '@/lib/lndk'
import { getIdentity, getHeight, getWalletInfo, getNode, getPayment } from 'ln-service'
import { datePivot } from '@/lib/time'
import { LND_PATHFINDING_TIMEOUT_MS } from '@/lib/constants'
Expand All @@ -10,6 +11,11 @@ const lnd = global.lnd || authenticatedLndGrpc({
macaroon: process.env.LND_MACAROON,
socket: process.env.LND_SOCKET
}).lnd
installLNDK(lnd, {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since lndk is just adding few apis to lnd, we are going to need them both to do most of the api work, by appending the lndk client to the lnd object we reflect its nature as an extension and we avoid passing another field to every context

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't mutate objects from an external library in any way for any reason.

I know it's highly unlikely that this is an issue now (and probably also not in the future), but if this ever becomes an issue, it's going to be extremely hard to debug because it will break in ways we cannot anticipate.

I also don't want to even have to ask myself if this might break. I see this like the Sword of Damocles. Just don't do it, the risk/reward ratio just isn't worth it. I suggest to just pass lndk in the context like we pass lnd around.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can then also remove all these checks:

const lndk = lnd?.lndk
if (!lndk) throw new Error('lndk not installed, please use installLNDK')

cert: process.env.LNDK_CERT,
macaroon: process.env.LNDK_MACAROON,
socket: process.env.LNDK_SOCKET
})

if (process.env.NODE_ENV === 'development') global.lnd = lnd

Expand Down
11 changes: 7 additions & 4 deletions api/paidAction/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createHodlInvoice, createInvoice, parsePaymentRequest } from 'ln-service'
import { createHodlInvoice, createInvoice } from 'ln-service'
import { datePivot } from '@/lib/time'
import { PAID_ACTION_PAYMENT_METHODS, USER_ID } from '@/lib/constants'
import { createHmac } from '@/api/resolvers/wallet'
import { Prisma } from '@prisma/client'
import { createWrappedInvoice, createInvoice as createUserInvoice } from '@/wallets/server'
import { assertBelowMaxPendingInvoices, assertBelowMaxPendingDirectPayments } from './lib/assert'
import { parseBolt11 } from '@/lib/bolt11'

import * as ITEM_CREATE from './itemCreate'
import * as ITEM_UPDATE from './itemUpdate'
Expand Down Expand Up @@ -263,15 +264,16 @@ async function performDirectAction (actionType, args, incomingContext) {
invoiceObject = await createUserInvoice(userId, {
msats: cost,
description,
expiry: INVOICE_EXPIRE_SECS
expiry: INVOICE_EXPIRE_SECS,
supportBolt12: false // direct payment is not supported to bolt12 for compatibility reasons
}, { models, lnd })
} catch (e) {
console.error('failed to create outside invoice', e)
throw new NonInvoiceablePeerError()
}

const { invoice, wallet } = invoiceObject
const hash = parsePaymentRequest({ request: invoice }).id
const hash = await parseBolt11({ request: invoice }).id

const payment = await models.directPayment.create({
data: {
Expand Down Expand Up @@ -419,8 +421,9 @@ async function createDbInvoice (actionType, args, context) {
throw new Error('The cost of the action must be at least 1 sat')
}

// note: served invoice is always bolt11
const servedBolt11 = wrappedBolt11 ?? bolt11
const servedInvoice = parsePaymentRequest({ request: servedBolt11 })
const servedInvoice = await parseBolt11({ request: servedBolt11 })
const expiresAt = new Date(servedInvoice.expires_at)

const invoiceData = {
Expand Down
6 changes: 3 additions & 3 deletions api/payingAction/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LND_PATHFINDING_TIME_PREF_PPM, LND_PATHFINDING_TIMEOUT_MS } from '@/lib/constants'
import { msatsToSats, satsToMsats, toPositiveBigInt } from '@/lib/format'
import { Prisma } from '@prisma/client'
import { parsePaymentRequest, payViaPaymentRequest } from 'ln-service'
import { payInvoice, parseInvoice } from '@/lib/boltInvoices'

// paying actions are completely distinct from paid actions
// and there's only one paying action: send
Expand All @@ -14,7 +14,7 @@ export default async function performPayingAction ({ bolt11, maxFee, walletId },
throw new Error('You must be logged in to perform this action')
}

const decoded = await parsePaymentRequest({ request: bolt11 })
const decoded = await parseInvoice({ request: bolt11, lnd })
const cost = toPositiveBigInt(toPositiveBigInt(decoded.mtokens) + satsToMsats(maxFee))

console.log('cost', cost)
Expand All @@ -40,7 +40,7 @@ export default async function performPayingAction ({ bolt11, maxFee, walletId },
})
}, { isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted })

payViaPaymentRequest({
payInvoice({
lnd,
request: withdrawal.bolt11,
max_fee: msatsToSats(withdrawal.msatsFeePaying),
Expand Down
36 changes: 27 additions & 9 deletions api/resolvers/wallet.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
getInvoice as getInvoiceFromLnd, deletePayment, getPayment,
parsePaymentRequest
getInvoice as getInvoiceFromLnd, deletePayment, getPayment
} from 'ln-service'
import crypto, { timingSafeEqual } from 'crypto'
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
Expand All @@ -14,7 +13,8 @@ import {
import { amountSchema, validateSchema, withdrawlSchema, lnAddrSchema } from '@/lib/validate'
import assertGofacYourself from './ofac'
import assertApiKeyNotPermitted from './apiKey'
import { bolt11Tags } from '@/lib/bolt11'
import { bolt11Tags, isBolt11 } from '@/lib/bolt11-tags'
import { bolt12Info } from '@/lib/bolt12-info'
import { finalizeHodlInvoice } from '@/worker/wallet'
import walletDefs from '@/wallets/server'
import { generateResolverName, generateTypeDefName } from '@/wallets/graphql'
Expand All @@ -25,14 +25,18 @@ import validateWallet from '@/wallets/validate'
import { canReceive, getWalletByType } from '@/wallets/common'
import performPaidAction from '../paidAction'
import performPayingAction from '../payingAction'
import { parseInvoice } from '@/lib/boltInvoices'
import lnd from '@/api/lnd'
import { isBolt12Offer } from '@/lib/bolt12'
import { fetchBolt12InvoiceFromOffer } from '@/lib/lndk'
import { timeoutSignal, withTimeout } from '@/lib/time'

function injectResolvers (resolvers) {
console.group('injected GraphQL resolvers:')
for (const walletDef of walletDefs) {
const resolverName = generateResolverName(walletDef.walletField)
console.log(resolverName)
resolvers.Mutation[resolverName] = async (parent, { settings, validateLightning, vaultEntries, ...data }, { me, models }) => {
resolvers.Mutation[resolverName] = async (parent, { settings, validateLightning, vaultEntries, ...data }, { me, models, lnd }) => {
console.log('resolving', resolverName, { settings, validateLightning, vaultEntries, ...data })

let existingVaultEntries
Expand Down Expand Up @@ -71,6 +75,7 @@ function injectResolvers (resolvers) {
? (data) => withTimeout(
walletDef.testCreateInvoice(data, {
logger,
lnd,
signal: timeoutSignal(WALLET_CREATE_INVOICE_TIMEOUT_MS)
}),
WALLET_CREATE_INVOICE_TIMEOUT_MS)
Expand Down Expand Up @@ -375,7 +380,7 @@ const resolvers = {
f = { ...f, ...f.other }

if (f.bolt11) {
f.description = bolt11Tags(f.bolt11).description
f.description = isBolt11(f.bolt11) ? bolt11Tags(f.bolt11).description : bolt12Info(f.bolt11).description
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhh, there is payInvoice, parseInvoice and estimateFees which supports bolt11 and bolt12. Maybe there should also be boltTags that does the same for the tags?

}

switch (f.type) {
Expand Down Expand Up @@ -487,6 +492,7 @@ const resolvers = {
},
createWithdrawl: createWithdrawal,
sendToLnAddr,
sendToBolt12Offer,
cancelInvoice: async (parent, { hash, hmac }, { models, lnd, boss }) => {
verifyHmac(hash, hmac)
await finalizeHodlInvoice({ data: { hash }, lnd, models, boss })
Expand Down Expand Up @@ -732,8 +738,8 @@ export const walletLogger = ({ wallet, models }) => {
const log = (level) => async (message, context = {}) => {
try {
if (context?.bolt11) {
// automatically populate context from bolt11 to avoid duplicating this code
const decoded = await parsePaymentRequest({ request: context.bolt11 })
// automatically populate context from invoice to avoid duplicating this code
const decoded = await parseInvoice({ request: context.bolt11, lnd })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhh, I think ideally this would store the bolt12 offer as bolt12 in the context instead of the bolt12 invoice as bolt11.

But I am not sure how easy it is to get the offer back from the invoice? But I think we can at least save it as bolt12 here.

I really have to do #1598 soon 👀

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think the offer is available as part of the invoice tlv.
Can we do all the cosmetic changes from bolt11->bolt12 on a separated pr? This one is already big, propagating all the naming would add even more changes. Unless you mean something else

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do all the cosmetic changes from bolt11->bolt12 on a separated pr? This one is already big, propagating all the naming would add even more changes. Unless you mean something else

I just meant the user-facing stuff (so not just cosmetic imo), not renaming variables but yeah, we can do it in a separate PR. Just wanted to flag this.

context = {
...context,
amount: formatMsats(decoded.mtokens),
Expand Down Expand Up @@ -912,7 +918,7 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model
// decode invoice to get amount
let decoded, sockets
try {
decoded = await parsePaymentRequest({ request: invoice })
decoded = await parseInvoice({ request: invoice, lnd })
} catch (error) {
console.log(error)
throw new GqlInputError('could not decode invoice')
Expand Down Expand Up @@ -972,6 +978,18 @@ export async function sendToLnAddr (parent, { addr, amount, maxFee, comment, ...
return await createWithdrawal(parent, { invoice: res.pr, maxFee }, { me, models, lnd, headers })
}

export async function sendToBolt12Offer (parent, { offer, amountSats, maxFee, comment }, { me, models, lnd, headers }) {
if (!me) {
throw new GqlAuthenticationError()
}
assertApiKeyNotPermitted({ me })
if (!isBolt12Offer(offer)) {
throw new GqlInputError('not a bolt12 offer')
}
const invoice = await fetchBolt12InvoiceFromOffer({ lnd, offer, msats: satsToMsats(amountSats), description: comment })
return await createWithdrawal(parent, { invoice, maxFee }, { me, models, lnd, headers })
}

export async function fetchLnAddrInvoice (
{ addr, amount, maxFee, comment, ...payer },
{
Expand Down Expand Up @@ -1012,7 +1030,7 @@ export async function fetchLnAddrInvoice (

// decode invoice
try {
const decoded = await parsePaymentRequest({ request: res.pr })
const decoded = await parseInvoice({ request: res.pr, lnd })
const ourPubkey = await getOurPubkey({ lnd })
if (autoWithdraw && decoded.destination === ourPubkey && process.env.NODE_ENV === 'production') {
// unset lnaddr so we don't trigger another withdrawal with same destination
Expand Down
1 change: 1 addition & 0 deletions api/typeDefs/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const typeDefs = `
createInvoice(amount: Int!): InvoiceOrDirect!
createWithdrawl(invoice: String!, maxFee: Int!): Withdrawl!
sendToLnAddr(addr: String!, amount: Int!, maxFee: Int!, comment: String, identifier: Boolean, name: String, email: String): Withdrawl!
sendToBolt12Offer(offer: String!, amountSats: Int!, maxFee: Int!, comment: String): Withdrawl!
cancelInvoice(hash: String!, hmac: String!): Invoice!
dropBolt11(hash: String!): Boolean
removeWallet(id: ID!): Boolean
Expand Down
5 changes: 3 additions & 2 deletions components/bolt11-info.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import AccordianItem from './accordian-item'
import { CopyInput } from './form'
import { bolt11Tags } from '@/lib/bolt11'
import { bolt11Tags, isBolt11 } from '@/lib/bolt11-tags'
import { bolt12Info } from '@/lib/bolt12-info'

export default ({ bolt11, preimage, children }) => {
let description, paymentHash
if (bolt11) {
({ description, payment_hash: paymentHash } = bolt11Tags(bolt11))
({ description, payment_hash: paymentHash } = isBolt11(bolt11) ? bolt11Tags(bolt11) : bolt12Info(bolt11))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see other comment

}

return (
Expand Down
6 changes: 4 additions & 2 deletions docker/lndk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
# This image uses fedora 40 because the official pre-built lndk binaries require
# glibc 2.39 which is not available on debian or ubuntu images.
FROM fedora:40
RUN useradd -u 1000 -m lndk

ENV INSTALLER_DOWNLOAD_URL="https://github.com/stackernews/lndk/releases/download/v0.2.0-maxfee"

RUN useradd -u 1000 -m lndk
RUN mkdir -p /home/lndk/.lndk
COPY ["./tls-*", "/home/lndk/.lndk"]
RUN chown 1000:1000 -Rvf /home/lndk/.lndk && \
chmod 644 /home/lndk/.lndk/tls-cert.pem && \
chmod 600 /home/lndk/.lndk/tls-key.pem

USER lndk
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/lndk-org/lndk/releases/download/v0.2.0/lndk-installer.sh | sh
RUN curl --proto '=https' --tlsv1.2 -LsSf $INSTALLER_DOWNLOAD_URL/lndk-installer.sh | sh
ekzyis marked this conversation as resolved.
Show resolved Hide resolved
RUN echo 'source /home/lndk/.cargo/env' >> $HOME/.bashrc
WORKDIR /home/lndk
EXPOSE 7000
Expand Down
10 changes: 10 additions & 0 deletions fragments/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ export const SEND_TO_LNADDR = gql`
}
}`

export const SEND_TO_BOLT12_OFFER = gql`
mutation sendToBolt12Offer($offer: String!, $amountSats: Int!, $maxFee: Int!, $comment: String) {
sendToBolt12Offer(offer: $offer, amountSats: $amountSats, maxFee: $maxFee, comment: $comment) {
id
}
}`

export const REMOVE_WALLET =
gql`
mutation removeWallet($id: ID!) {
Expand Down Expand Up @@ -169,6 +176,9 @@ export const WALLET_FIELDS = gql`
apiKeyRecv
currencyRecv
}
... on WalletBolt12 {
offer
}
}
}
`
Expand Down
49 changes: 49 additions & 0 deletions lib/bech32b12.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to take a closer look at this custom parser.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// bech32 without the checksum
// used for bolt12

const ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'

export function decode (str) {
if (str.length > 2048) throw new Error('input is too long')
const b5s = []
for (const char of str) {
const i = ALPHABET.indexOf(char)
if (i === -1) throw new Error('invalid bech32 character')
b5s.push(i)
}
const b8s = Buffer.from(converBits(b5s, 5, 8, false))
return b8s
}

export function encode (b8s) {
if (b8s.length > 2048) throw new Error('input is too long')
const b5s = converBits(b8s, 8, 5, true)
return b5s.map(b5 => ALPHABET[b5]).join('')
}

function converBits (data, frombits, tobits, pad) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in function name

let acc = 0
let bits = 0
const ret = []
const maxv = (1 << tobits) - 1
for (let p = 0; p < data.length; ++p) {
const value = data[p]
if (value < 0 || (value >> frombits) !== 0) {
throw new RangeError('input value is outside of range')
}
acc = (acc << frombits) | value
bits += frombits
while (bits >= tobits) {
bits -= tobits
ret.push((acc >> bits) & maxv)
}
}
if (pad) {
if (bits > 0) {
ret.push((acc << (tobits - bits)) & maxv)
}
} else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) {
throw new RangeError('could not convert bits')
}
return ret
}
10 changes: 10 additions & 0 deletions lib/bolt11-tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { decode } from 'bolt11'

export function isBolt11 (request) {
return request.startsWith('lnbc') || request.startsWith('lntb') || request.startsWith('lntbs') || request.startsWith('lnbcrt')
}

export function bolt11Tags (bolt11) {
if (!isBolt11(bolt11)) throw new Error('not a bolt11 invoice')
return decode(bolt11).tagsObject
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've renamed @/lib/bolt11 to @/lib/bolt11-tags

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not put everything bolt11 related into lib/bolt11.js? Same for bolt12 stuff?

Copy link
Member Author

@riccardobl riccardobl Dec 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because ln-service uses 'fs' to load protobuf files and it breaks webpack builds for the browser, iirc there are also a bunch of other node specific imports that would require a polyfill

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhhh, the files in lib/ should be importable by the client and server afaik.

Is there a way to fix this by dynamically importing server stuff if a server function is called without significant downsides?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it is possible, but last time dynamic imports caused a lot of issue in production.
Are they supposed to work reliably after your patch @huumn ?

26 changes: 23 additions & 3 deletions lib/bolt11.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
import { decode } from 'bolt11'
/* eslint-disable camelcase */
import { payViaPaymentRequest, parsePaymentRequest } from 'ln-service'
import { bolt11InvoiceSchema } from './validate'

export function bolt11Tags (bolt11) {
return decode(bolt11).tagsObject
export function isBolt11 (request) {
if (!request.startsWith('lnbc') && !request.startsWith('lntb') && !request.startsWith('lntbs') && !request.startsWith('lnbcrt')) return false
bolt11InvoiceSchema.validateSync(request)
return true
}

export async function parseBolt11 ({ request }) {
if (!isBolt11(request)) throw new Error('not a bolt11 invoice')
return parsePaymentRequest({ request })
}

export async function payBolt11 ({ lnd, request, max_fee, max_fee_mtokens, ...args }) {
if (!isBolt11(request)) throw new Error('not a bolt11 invoice')
return payViaPaymentRequest({
lnd,
request,
max_fee,
max_fee_mtokens,
...args
})
}
Loading
Loading