Skip to content

Commit

Permalink
code cleanup, add bolt12info (bolt11 tags equivalent)
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardobl committed Dec 17, 2024
1 parent 332d1e1 commit f927fc5
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 85 deletions.
2 changes: 1 addition & 1 deletion api/paidAction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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/invoices'
import { parseBolt11 } from '@/lib/bolt11'

import * as ITEM_CREATE from './itemCreate'
import * as ITEM_UPDATE from './itemUpdate'
Expand Down
5 changes: 3 additions & 2 deletions api/resolvers/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
import { amountSchema, validateSchema, withdrawlSchema, lnAddrSchema } from '@/lib/validate'
import assertGofacYourself from './ofac'
import assertApiKeyNotPermitted from './apiKey'
import { bolt11Tags } from '@/lib/bolt11'
import { bolt11Info, isBolt11 } from '@/lib/bolt11-info'
import { bolt12Info } from '@/lib/bolt12-info'
import { finalizeHodlInvoice } from '@/worker/wallet'
import walletDefs from '@/wallets/server'
import { generateResolverName, generateTypeDefName } from '@/wallets/graphql'
Expand Down Expand Up @@ -368,7 +369,7 @@ const resolvers = {
f = { ...f, ...f.other }

if (f.bolt11) {
f.description = bolt11Tags(f.bolt11).description
f.description = isBolt11(f.bolt11) ? bolt11Info(f.bolt11).description : bolt12Info(f.bolt11).description
}

switch (f.type) {
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 { bolt11Info, isBolt11 } from '@/lib/bolt11-info'
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) ? bolt11Info(bolt11) : bolt12Info(bolt11))
}

return (
Expand Down
10 changes: 10 additions & 0 deletions lib/bolt11-info.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 bolt11Info (bolt11) {
if (!isBolt11(bolt11)) throw new Error('not a bolt11 invoice')
return decode(bolt11).tagsObject
}
22 changes: 19 additions & 3 deletions lib/bolt11.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { decode } from 'bolt11'
/* eslint-disable camelcase */
import { payViaPaymentRequest, parsePaymentRequest } from 'ln-service'

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

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, ...args }) {
if (!isBolt11(request)) throw new Error('not a bolt11 invoice')
return payViaPaymentRequest({
lnd,
request,
max_fee,
...args
})
}
28 changes: 28 additions & 0 deletions lib/bolt12-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { deserializeTLVStream } from './tlv'
import * as bech32b12 from '@/lib/bech32b12'

export function isBolt12 (invoice) {
return invoice.startsWith('lni1') || invoice.startsWith('lno1')
}

export function bolt12Info (bolt12) {
if (!isBolt12(bolt12)) throw new Error('not a bolt12 invoice or offer')
const buf = bech32b12.decode(bolt12.substring(4)/* remove lni1 or lno1 prefix */)
const tlv = deserializeTLVStream(buf)
const INFO_TYPES = {
description: 10n,
payment_hash: 168n
}
const info = {
description: '',
payment_hash: ''
}
for (const { type, value } of tlv) {
if (type === INFO_TYPES.description) {
info.description = value.toString()
} else if (type === INFO_TYPES.payment_hash) {
info.payment_hash = value.toString('hex')
}
}
return info
}
25 changes: 25 additions & 0 deletions lib/bolt12.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable camelcase */

import { payViaBolt12PaymentRequest, parseBolt12Request } from '@/lib/lndk'

export function isBolt12Offer (invoice) {
return invoice.startsWith('lno1')
}

export function isBolt12Invoice (invoice) {
return invoice.startsWith('lni1')
}

export function isBolt12 (invoice) {
return isBolt12Offer(invoice) || isBolt12Invoice(invoice)
}

export async function payBolt12 ({ lnd, request: invoice, max_fee }) {
if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 invoice')
return await payViaBolt12PaymentRequest({ lnd, request: invoice, max_fee })
}

export function parseBolt12 ({ lnd, request: invoice }) {
if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 request')
return parseBolt12Request({ lnd, request: invoice })
}
49 changes: 3 additions & 46 deletions lib/invoices.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,9 @@
/* eslint-disable camelcase */

import { payViaPaymentRequest, parsePaymentRequest } from 'ln-service'
import { payBolt12, parseBolt12, isBolt12Invoice } from './bolt12'
import { payBolt11, parseBolt11 } from './bolt11'
import { estimateBolt12RouteFee } from '@/lib/lndk'
import { estimateRouteFee } from '@/api/lnd'

import { payViaBolt12PaymentRequest, parseBolt12Request, estimateBolt12RouteFee } from '@/lib/lndk'

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

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

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

return payViaPaymentRequest({
lnd,
request,
max_fee,
...args
})
}

export function isBolt12Offer (invoice) {
return invoice.startsWith('lno1')
}

export function isBolt12Invoice (invoice) {
console.log('isBolt12Invoice', invoice)
console.trace()
return invoice.startsWith('lni1')
}

export async function payBolt12 ({ lnd, request: invoice, max_fee }) {
if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 invoice')

if (!invoice) throw new Error('No invoice in bolt12, please use prefetchBolt12Invoice')
return await payViaBolt12PaymentRequest({ lnd, request: invoice, max_fee })
}

export function parseBolt12 ({ lnd, request: invoice }) {
if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 request')
return parseBolt12Request({ lnd, request: invoice })
}

export async function payInvoice ({ lnd, request: invoice, max_fee, ...args }) {
if (isBolt12Invoice(invoice)) {
return await payBolt12({ lnd, request: invoice, max_fee, ...args })
Expand Down
31 changes: 1 addition & 30 deletions lib/lndk.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,36 +121,7 @@ const chainsMap = {
'43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000': 'testnet',
'6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000': 'mainnet'
}
// @returns
// {
// [chain_addresses]: [<Chain Address String>]
// cltv_delta: <CLTV Delta Number>
// created_at: <Invoice Creation Date ISO 8601 String>
// [description]: <Description String>
// [description_hash]: <Description Hash Hex String>
// destination: <Public Key String>
// expires_at: <ISO 8601 Date String>
// features: [{
// bit: <BOLT 09 Feature Bit Number>
// is_required: <Feature Support is Required To Pay Bool>
// type: <Feature Type String>
// }]
// id: <Payment Request Hash String>
// is_expired: <Invoice is Expired Bool>
// [metadata]: <Payment Metadata Hex String>
// [mtokens]: <Requested Milli-Tokens Value String> (can exceed Number limit)
// network: <Network Name String>
// [payment]: <Payment Identifier Hex Encoded String>
// [routes]: [[{
// [base_fee_mtokens]: <Base Fee Millitokens String>
// [channel]: <Standard Format Channel Id String>
// [cltv_delta]: <Final CLTV Expiration Blocks Delta Number>
// [fee_rate]: <Fee Rate Millitokens Per Million Number>
// public_key: <Forward Edge Public Key Hex String>
// }]]
// [safe_tokens]: <Requested Tokens Rounded Up Number>
// [tokens]: <Requested Chain Tokens Number> (note: can differ from mtokens)
// }

export async function parseBolt12Request ({
lnd,
request
Expand Down
36 changes: 36 additions & 0 deletions lib/tlv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export function deserializeTLVStream (buff) {
const tlvs = []
let bytePos = 0
while (bytePos < buff.length) {
const [type, typeLength] = readBigSize(buff, bytePos)
bytePos += typeLength

let [length, lengthLength] = readBigSize(buff, bytePos)
length = Number(length)
bytePos += lengthLength

if (bytePos + length > buff.length) {
throw new Error('invalid tlv stream')
}

const value = buff.subarray(bytePos, bytePos + length)
bytePos += length

tlvs.push({ type, length, value })
}
return tlvs
}

function readBigSize (buf, offset) {
if (buf[offset] <= 252) {
return [BigInt(buf[offset]), 1]
} else if (buf[offset] === 253) {
return [BigInt(buf.readUInt16BE(offset + 1)), 3]
} else if (buf[offset] === 254) {
return [BigInt(buf.readUInt32BE(offset + 1)), 5]
} else if (buf[offset] === 255) {
return [buf.readBigUInt64BE(offset + 1), 9]
} else {
throw new Error('Invalid bigsize')
}
}
3 changes: 2 additions & 1 deletion wallets/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import * as webln from '@/wallets/webln'

import { walletLogger } from '@/api/resolvers/wallet'
import walletDefs from '@/wallets/server'
import { isBolt12Offer, parseInvoice } from '@/lib/invoices'
import { parseInvoice } from '@/lib/invoices'
import { isBolt12Offer } from '@/lib/bolt12'
import { toPositiveBigInt, toPositiveNumber, formatMsats, formatSats, msatsToSats } from '@/lib/format'
import { PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
import { withTimeout } from '@/lib/time'
Expand Down

0 comments on commit f927fc5

Please sign in to comment.