Skip to content

Commit 494061c

Browse files
committed
add withdraw to bolt12, improve checks and naming
1 parent f927fc5 commit 494061c

File tree

18 files changed

+248
-109
lines changed

18 files changed

+248
-109
lines changed

Diff for: api/payingAction/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { LND_PATHFINDING_TIME_PREF_PPM, LND_PATHFINDING_TIMEOUT_MS } from '@/lib/constants'
22
import { msatsToSats, satsToMsats, toPositiveBigInt } from '@/lib/format'
33
import { Prisma } from '@prisma/client'
4-
import { payInvoice, parseInvoice } from '@/lib/invoices'
4+
import { payInvoice, parseInvoice } from '@/lib/boltInvoices'
55

66
// paying actions are completely distinct from paid actions
77
// and there's only one paying action: send

Diff for: api/resolvers/wallet.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
import { amountSchema, validateSchema, withdrawlSchema, lnAddrSchema } from '@/lib/validate'
1313
import assertGofacYourself from './ofac'
1414
import assertApiKeyNotPermitted from './apiKey'
15-
import { bolt11Info, isBolt11 } from '@/lib/bolt11-info'
15+
import { bolt11Tags, isBolt11 } from '@/lib/bolt11-tags'
1616
import { bolt12Info } from '@/lib/bolt12-info'
1717
import { finalizeHodlInvoice } from '@/worker/wallet'
1818
import walletDefs from '@/wallets/server'
@@ -24,8 +24,10 @@ import validateWallet from '@/wallets/validate'
2424
import { canReceive } from '@/wallets/common'
2525
import performPaidAction from '../paidAction'
2626
import performPayingAction from '../payingAction'
27-
import { parseInvoice } from '@/lib/invoices'
27+
import { parseInvoice } from '@/lib/boltInvoices'
2828
import lnd from '@/api/lnd'
29+
import { isBolt12Offer } from '@/lib/bolt12'
30+
import { fetchBolt12InvoiceFromOffer } from '@/lib/lndk'
2931

3032
function injectResolvers (resolvers) {
3133
console.group('injected GraphQL resolvers:')
@@ -369,7 +371,7 @@ const resolvers = {
369371
f = { ...f, ...f.other }
370372

371373
if (f.bolt11) {
372-
f.description = isBolt11(f.bolt11) ? bolt11Info(f.bolt11).description : bolt12Info(f.bolt11).description
374+
f.description = isBolt11(f.bolt11) ? bolt11Tags(f.bolt11).description : bolt12Info(f.bolt11).description
373375
}
374376

375377
switch (f.type) {
@@ -481,6 +483,7 @@ const resolvers = {
481483
},
482484
createWithdrawl: createWithdrawal,
483485
sendToLnAddr,
486+
sendToBolt12Offer,
484487
cancelInvoice: async (parent, { hash, hmac }, { models, lnd, boss }) => {
485488
verifyHmac(hash, hmac)
486489
await finalizeHodlInvoice({ data: { hash }, lnd, models, boss })
@@ -940,7 +943,7 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model
940943
throw new GqlInputError('SN cannot pay an invoice that SN is proxying')
941944
}
942945

943-
return await performPayingAction({ invoice, maxFee, walletId: wallet?.id }, { me, models, lnd })
946+
return await performPayingAction({ bolt11: invoice, maxFee, walletId: wallet?.id }, { me, models, lnd })
944947
}
945948

946949
export async function sendToLnAddr (parent, { addr, amount, maxFee, comment, ...payer },
@@ -961,6 +964,18 @@ export async function sendToLnAddr (parent, { addr, amount, maxFee, comment, ...
961964
return await createWithdrawal(parent, { invoice: res.pr, maxFee }, { me, models, lnd, headers })
962965
}
963966

967+
export async function sendToBolt12Offer (parent, { offer, amountSats, maxFee, comment }, { me, models, lnd, headers }) {
968+
if (!me) {
969+
throw new GqlAuthenticationError()
970+
}
971+
assertApiKeyNotPermitted({ me })
972+
if (!isBolt12Offer(offer)) {
973+
throw new GqlInputError('not a bolt12 offer')
974+
}
975+
const invoice = await fetchBolt12InvoiceFromOffer({ lnd, offer, msats: satsToMsats(amountSats), description: comment })
976+
return await createWithdrawal(parent, { invoice, maxFee }, { me, models, lnd, headers })
977+
}
978+
964979
export async function fetchLnAddrInvoice (
965980
{ addr, amount, maxFee, comment, ...payer },
966981
{

Diff for: api/typeDefs/wallet.js

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const typeDefs = `
7878
createInvoice(amount: Int!): InvoiceOrDirect!
7979
createWithdrawl(invoice: String!, maxFee: Int!): Withdrawl!
8080
sendToLnAddr(addr: String!, amount: Int!, maxFee: Int!, comment: String, identifier: Boolean, name: String, email: String): Withdrawl!
81+
sendToBolt12Offer(offer: String!, amountSats: Int!, maxFee: Int!, comment: String): Withdrawl!
8182
cancelInvoice(hash: String!, hmac: String!): Invoice!
8283
dropBolt11(hash: String!): Boolean
8384
removeWallet(id: ID!): Boolean

Diff for: components/bolt11-info.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import AccordianItem from './accordian-item'
22
import { CopyInput } from './form'
3-
import { bolt11Info, isBolt11 } from '@/lib/bolt11-info'
3+
import { bolt11Tags, isBolt11 } from '@/lib/bolt11-tags'
44
import { bolt12Info } from '@/lib/bolt12-info'
55

66
export default ({ bolt11, preimage, children }) => {
77
let description, paymentHash
88
if (bolt11) {
9-
({ description, payment_hash: paymentHash } = isBolt11(bolt11) ? bolt11Info(bolt11) : bolt12Info(bolt11))
9+
({ description, payment_hash: paymentHash } = isBolt11(bolt11) ? bolt11Tags(bolt11) : bolt12Info(bolt11))
1010
}
1111

1212
return (

Diff for: fragments/wallet.js

+7
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ export const SEND_TO_LNADDR = gql`
121121
}
122122
}`
123123

124+
export const SEND_TO_BOLT12_OFFER = gql`
125+
mutation sendToBolt12Offer($offer: String!, $amountSats: Int!, $maxFee: Int!, $comment: String) {
126+
sendToBolt12Offer(offer: $offer, amountSats: $amountSats, maxFee: $maxFee, comment: $comment) {
127+
id
128+
}
129+
}`
130+
124131
export const REMOVE_WALLET =
125132
gql`
126133
mutation removeWallet($id: ID!) {

Diff for: lib/bech32b12.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1+
// bech32 without the checksum
2+
// used for bolt12
3+
14
const ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
25

36
export function decode (str) {
7+
if (str.length > 2048) throw new Error('input is too long')
48
const b5s = []
59
for (const char of str) {
610
const i = ALPHABET.indexOf(char)
7-
if (i === -1) throw new Error('Invalid bech32 character')
11+
if (i === -1) throw new Error('invalid bech32 character')
812
b5s.push(i)
913
}
1014
const b8s = Buffer.from(converBits(b5s, 5, 8, false))
1115
return b8s
1216
}
1317

1418
export function encode (b8s) {
19+
if (b8s.length > 2048) throw new Error('input is too long')
1520
const b5s = converBits(b8s, 8, 5, true)
16-
const str = []
17-
for (const b5 of b5s) str.push(ALPHABET[b5])
18-
return str.join('')
21+
return b5s.map(b5 => ALPHABET[b5]).join('')
1922
}
2023

2124
function converBits (data, frombits, tobits, pad) {

Diff for: lib/bolt11-info.js renamed to lib/bolt11-tags.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export function isBolt11 (request) {
44
return request.startsWith('lnbc') || request.startsWith('lntb') || request.startsWith('lntbs') || request.startsWith('lnbcrt')
55
}
66

7-
export function bolt11Info (bolt11) {
7+
export function bolt11Tags (bolt11) {
88
if (!isBolt11(bolt11)) throw new Error('not a bolt11 invoice')
99
return decode(bolt11).tagsObject
1010
}

Diff for: lib/bolt11.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
/* eslint-disable camelcase */
22
import { payViaPaymentRequest, parsePaymentRequest } from 'ln-service'
3+
import { bolt11InvoiceSchema } from './validate'
34

45
export function isBolt11 (request) {
5-
return request.startsWith('lnbc') || request.startsWith('lntb') || request.startsWith('lntbs') || request.startsWith('lnbcrt')
6+
if (!request.startsWith('lnbc') && !request.startsWith('lntb') && !request.startsWith('lntbs') && !request.startsWith('lnbcrt')) return false
7+
bolt11InvoiceSchema.validateSync(request)
8+
return true
69
}
710

811
export async function parseBolt11 ({ request }) {

Diff for: lib/bolt12-info.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { deserializeTLVStream } from './tlv'
22
import * as bech32b12 from '@/lib/bech32b12'
33

4+
const TYPE_DESCRIPTION = 10n
5+
const TYPE_PAYER_NOTE = 89n
6+
const TYPE_PAYMENT_HASH = 168n
7+
48
export function isBolt12 (invoice) {
59
return invoice.startsWith('lni1') || invoice.startsWith('lno1')
610
}
@@ -9,20 +13,21 @@ export function bolt12Info (bolt12) {
913
if (!isBolt12(bolt12)) throw new Error('not a bolt12 invoice or offer')
1014
const buf = bech32b12.decode(bolt12.substring(4)/* remove lni1 or lno1 prefix */)
1115
const tlv = deserializeTLVStream(buf)
12-
const INFO_TYPES = {
13-
description: 10n,
14-
payment_hash: 168n
15-
}
16+
1617
const info = {
1718
description: '',
1819
payment_hash: ''
1920
}
21+
2022
for (const { type, value } of tlv) {
21-
if (type === INFO_TYPES.description) {
22-
info.description = value.toString()
23-
} else if (type === INFO_TYPES.payment_hash) {
23+
if (type === TYPE_DESCRIPTION) {
24+
info.description = value.toString() || info.description
25+
} else if (type === TYPE_PAYER_NOTE) {
26+
info.description = value.toString() || info.description
27+
} else if (type === TYPE_PAYMENT_HASH) {
2428
info.payment_hash = value.toString('hex')
2529
}
2630
}
31+
2732
return info
2833
}

Diff for: lib/bolt12.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
/* eslint-disable camelcase */
22

33
import { payViaBolt12PaymentRequest, parseBolt12Request } from '@/lib/lndk'
4+
import { bolt12OfferSchema, bolt12InvoiceSchema } from './validate'
45

56
export function isBolt12Offer (invoice) {
6-
return invoice.startsWith('lno1')
7+
if (!invoice.startsWith('lno1')) return false
8+
bolt12OfferSchema.validateSync(invoice)
9+
return true
710
}
811

912
export function isBolt12Invoice (invoice) {
10-
return invoice.startsWith('lni1')
13+
if (!invoice.startsWith('lni1')) return false
14+
bolt12InvoiceSchema.validateSync(invoice)
15+
return true
1116
}
1217

1318
export function isBolt12 (invoice) {
@@ -19,7 +24,7 @@ export async function payBolt12 ({ lnd, request: invoice, max_fee }) {
1924
return await payViaBolt12PaymentRequest({ lnd, request: invoice, max_fee })
2025
}
2126

22-
export function parseBolt12 ({ lnd, request: invoice }) {
27+
export async function parseBolt12 ({ lnd, request: invoice }) {
2328
if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 request')
24-
return parseBolt12Request({ lnd, request: invoice })
29+
return await parseBolt12Request({ lnd, request: invoice })
2530
}

Diff for: lib/invoices.js renamed to lib/boltInvoices.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
11
/* eslint-disable camelcase */
2-
import { payBolt12, parseBolt12, isBolt12Invoice } from './bolt12'
3-
import { payBolt11, parseBolt11 } from './bolt11'
2+
import { payBolt12, parseBolt12, isBolt12Invoice, isBolt12Offer } from './bolt12'
3+
import { payBolt11, parseBolt11, isBolt11 } from './bolt11'
44
import { estimateBolt12RouteFee } from '@/lib/lndk'
55
import { estimateRouteFee } from '@/api/lnd'
66

77
export async function payInvoice ({ lnd, request: invoice, max_fee, ...args }) {
88
if (isBolt12Invoice(invoice)) {
99
return await payBolt12({ lnd, request: invoice, max_fee, ...args })
10-
} else {
10+
} else if (isBolt11(invoice)) {
1111
return await payBolt11({ lnd, request: invoice, max_fee, ...args })
12+
} else if (isBolt12Offer(invoice)) {
13+
throw new Error('cannot pay bolt12 offer directly, please fetch a bolt12 invoice from the offer first')
14+
} else {
15+
throw new Error('unknown invoice type')
1216
}
1317
}
1418

1519
export async function parseInvoice ({ lnd, request }) {
1620
if (isBolt12Invoice(request)) {
1721
return await parseBolt12({ lnd, request })
18-
} else {
22+
} else if (isBolt11(request)) {
1923
return await parseBolt11({ request })
24+
} else if (isBolt12Offer(request)) {
25+
throw new Error('bolt12 offer instead of invoice, please fetch a bolt12 invoice from the offer first')
26+
} else {
27+
throw new Error('unknown invoice type')
2028
}
2129
}
2230

2331
export async function estimateFees ({ lnd, destination, tokens, mtokens, request, timeout }) {
2432
if (isBolt12Invoice(request)) {
2533
return await estimateBolt12RouteFee({ lnd, destination, tokens, mtokens, request, timeout })
26-
} else {
34+
} else if (isBolt11(request)) {
2735
return await estimateRouteFee({ lnd, destination, tokens, request, mtokens, timeout })
36+
} else if (isBolt12Offer(request)) {
37+
throw new Error('bolt12 offer instead of invoice, please fetch a bolt12 invoice from the offer first')
38+
} else {
39+
throw new Error('unknown invoice type')
2840
}
2941
}

0 commit comments

Comments
 (0)