Skip to content

Commit

Permalink
bolt12 attachment
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardobl committed Dec 14, 2024
1 parent e6e82b0 commit 15d17e0
Show file tree
Hide file tree
Showing 23 changed files with 727 additions and 54 deletions.
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, {
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
8 changes: 5 additions & 3 deletions api/paidAction/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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 { parseInvoice } from '@/lib/invoices'
import lnd from '@/api/lnd'

import * as ITEM_CREATE from './itemCreate'
import * as ITEM_UPDATE from './itemUpdate'
Expand Down Expand Up @@ -271,7 +273,7 @@ async function performDirectAction (actionType, args, incomingContext) {
}

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

const payment = await models.directPayment.create({
data: {
Expand Down Expand Up @@ -415,7 +417,7 @@ async function createDbInvoice (actionType, args, context) {
}

const servedBolt11 = wrappedBolt11 ?? bolt11
const servedInvoice = parsePaymentRequest({ request: servedBolt11 })
const servedInvoice = await parseInvoice({ request: servedBolt11, lnd })
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_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/invoices'

// 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
11 changes: 6 additions & 5 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 @@ -24,6 +23,8 @@ import validateWallet from '@/wallets/validate'
import { canReceive } from '@/wallets/common'
import performPaidAction from '../paidAction'
import performPayingAction from '../payingAction'
import { parseInvoice } from '@/lib/invoices'
import lnd from '@/api/lnd'

function injectResolvers (resolvers) {
console.group('injected GraphQL resolvers:')
Expand Down Expand Up @@ -713,7 +714,7 @@ export const walletLogger = ({ wallet, models }) => {
try {
if (context?.bolt11) {
// automatically populate context from bolt11 to avoid duplicating this code
const decoded = await parsePaymentRequest({ request: context.bolt11 })
const decoded = await parseInvoice({ request: context.bolt11, lnd })
context = {
...context,
amount: formatMsats(decoded.mtokens),
Expand Down Expand Up @@ -890,7 +891,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 @@ -990,7 +991,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
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/riccardobl/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
RUN echo 'source /home/lndk/.cargo/env' >> $HOME/.bashrc
WORKDIR /home/lndk
EXPOSE 7000
Expand Down
3 changes: 3 additions & 0 deletions fragments/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ export const WALLET_FIELDS = gql`
apiKeyRecv
currencyRecv
}
... on WalletBolt12 {
offer
}
}
}
`
Expand Down
46 changes: 46 additions & 0 deletions lib/bech32b12.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'

export function decode (str) {
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) {
const b5s = converBits(b8s, 8, 5, true)
const str = []
for (const b5 of b5s) str.push(ALPHABET[b5])
return str.join('')
}

function converBits (data, frombits, tobits, pad) {
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
}
78 changes: 78 additions & 0 deletions lib/invoices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint-disable camelcase */

import { payViaPaymentRequest, parsePaymentRequest } from 'ln-service'
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 })
} else {
return await payBolt11({ lnd, request: invoice, max_fee, ...args })
}
}

export async function parseInvoice ({ lnd, request }) {
if (isBolt12Invoice(request)) {
return await parseBolt12({ lnd, request })
} else {
return await parseBolt11({ request })
}
}

export async function estimateFees ({ lnd, destination, tokens, mtokens, request, timeout }) {
if (isBolt12Invoice(request)) {
return await estimateBolt12RouteFee({ lnd, destination, tokens, mtokens, request, timeout })
} else {
return await estimateRouteFee({ lnd, destination, tokens, request, mtokens, timeout })
}
}
Loading

0 comments on commit 15d17e0

Please sign in to comment.