Merge remote-tracking branch 'fiatjaf/bolt11'

This commit is contained in:
Jonathan Staab 2023-03-03 16:24:54 -06:00
commit eb50234785
7 changed files with 77 additions and 30 deletions

BIN
package-lock.json generated

Binary file not shown.

View File

@ -29,15 +29,13 @@
"@bugsnag/js": "^7.18.0",
"@fortawesome/fontawesome-free": "^6.2.1",
"@noble/secp256k1": "^1.7.0",
"@scure/base": "^1.1.1",
"@tsconfig/svelte": "^3.0.0",
"bech32": "^2.0.0",
"bolt11": "^1.4.0",
"classnames": "^2.3.2",
"compressorjs": "^1.1.1",
"fuse.js": "^6.6.2",
"hurdak": "github:ConsignCloud/hurdak",
"husky": "^8.0.3",
"js-lnurl": "^0.5.1",
"localforage": "^1.10.0",
"localforage-memoryStorageDriver": "^0.9.2",
"nostr-tools": "^1.7.4",

View File

@ -200,4 +200,3 @@ const applyContext = (notes, context) => {
export default {
listen, load, loadPeople, personKinds, loadParents, streamContext, applyContext,
}

View File

@ -1,9 +1,8 @@
import {uniq, pick, identity, isEmpty} from 'ramda'
import {nip05} from 'nostr-tools'
import {getParams} from 'js-lnurl'
import {noop, createMap, ensurePlural, chunk, switcherFn} from 'hurdak/lib/hurdak'
import {log} from 'src/util/logger'
import {hexToBech32, tryFetch, now, sleep, tryJson, timedelta, shuffle, hash} from 'src/util/misc'
import {lnurlEncode, lnurlDecode, tryFetch, now, sleep, tryJson, timedelta, shuffle, hash} from 'src/util/misc'
import {Tags, roomAttrs, personKinds, isRelay, isShareableRelay, normalizeRelayUrl} from 'src/util/nostr'
import database from 'src/agent/database'
@ -308,25 +307,31 @@ const verifyNip05 = (pubkey, as) =>
}, noop)
const verifyZapper = async (pubkey, address) => {
// Try to parse it as a lud06 LNURL
let zapper = await getParams(address) as any
let lnurl = address
// If that failed, try to parse it as a lud16 address
if (zapper.status === 'ERROR' && address.includes('@')) {
let url
if (address.startsWith('lnurl1')) {
// Try to parse it as a lud06 LNURL
url = lnurlDecode(address)
const lnurl = address
} else if (address.includes('@')) {
// Otherwise try to parse it as a lud16 address
const [name, domain] = address.split('@')
if (!domain || !name) {
return
}
const url = `https://${domain}/.well-known/lnurlp/${name}`
const res = await tryFetch(() => fetch(url))
url = `https://${domain}/.well-known/lnurlp/${name}`
} else {
// Otherwise this is not valid
return
}
if (res) {
zapper = await res.json()
lnurl = hexToBech32('lnurl', url)
}
const res = await tryFetch(() => fetch(url))
let zapper
if (res) {
zapper = await res.json()
lnurl = lnurlEncode('lnurl', url)
}
if (zapper?.allowsNostr && zapper?.nostrPubkey) {

46
src/util/lightning.ts Normal file
View File

@ -0,0 +1,46 @@
const DIVISORS = {
m: BigInt(1e3),
u: BigInt(1e6),
n: BigInt(1e9),
p: BigInt(1e12)
}
const MAX_MILLISATS = BigInt('2100000000000000000')
const MILLISATS_PER_BTC = BigInt(1e11)
function hrpToMillisat(hrpString: string) {
let divisor, value
if (hrpString.slice(-1).match(/^[munp]$/)) {
divisor = hrpString.slice(-1)
value = hrpString.slice(0, -1)
} else if (hrpString.slice(-1).match(/^[^munp0-9]$/)) {
throw new Error('Not a valid multiplier for the amount')
} else {
value = hrpString
}
if (!value.match(/^\d+$/))
throw new Error('Not a valid human readable amount')
const valueBN = BigInt(value)
const millisatoshisBN = divisor
? (valueBN * MILLISATS_PER_BTC) / DIVISORS[divisor]
: valueBN * MILLISATS_PER_BTC
if (
(divisor === 'p' && !(valueBN % BigInt(10) === BigInt(0))) ||
millisatoshisBN > MAX_MILLISATS
) {
throw new Error('Amount is outside of valid range')
}
return millisatoshisBN
}
export function invoiceAmount(bolt11: string): number {
const hrp = bolt11.match(/lnbc(\d+\w)/)
const bn = hrpToMillisat(hrp[1])
return Number(bn)
}

View File

@ -1,5 +1,4 @@
import {bech32} from 'bech32'
import {Buffer} from 'buffer'
import {bech32, utf8} from '@scure/base'
import {debounce, throttle} from 'throttle-debounce'
import {aperture, path as getPath, allPass, pipe, isNil, complement, equals, is, pluck, sum, identity, sortBy} from "ramda"
import Fuse from "fuse.js/dist/fuse.min.js"
@ -340,11 +339,11 @@ export const uploadFile = (url, fileObj) => {
return fetchJson(url, {method: 'POST', body})
}
export const hexToBech32 = (prefix, hex) =>
bech32.encode(prefix, bech32.toWords(Buffer.from(hex, 'hex')))
export const lnurlEncode = (prefix, url) =>
bech32.encode(prefix, bech32.toWords(utf8.decode(url)), false)
export const bech32ToHex = b32 =>
Buffer.from(bech32.fromWords(bech32.decode(b32).words)).toString('hex')
export const lnurlDecode = b32 =>
utf8.encode(bech32.fromWords(bech32.decode(b32, false).words))
export const formatSats = sats => {
const formatter = new Intl.NumberFormat()

View File

@ -1,6 +1,5 @@
<script lang="ts">
import cx from 'classnames'
import bolt11 from 'bolt11'
import QRCode from 'qrcode'
import {nip19} from 'nostr-tools'
import {find, sum, last, whereEq, without, uniq, pluck, reject, propEq} from 'ramda'
@ -9,9 +8,10 @@
import {slide} from 'svelte/transition'
import {navigate} from 'svelte-routing'
import {quantify} from 'hurdak/lib/hurdak'
import {Tags, findRootId, findReplyId, displayPerson, isLike} from "src/util/nostr"
import {Tags, findRootId, findReplyId, displayPerson, isLike} from 'src/util/nostr'
import {formatTimestamp, now, tryJson, stringToColor, formatSats, fetchJson} from 'src/util/misc'
import {extractUrls, copyToClipboard} from "src/util/html"
import {invoiceAmount} from 'src/util/lightning'
import ImageCircle from 'src/partials/ImageCircle.svelte'
import ImageInput from 'src/partials/ImageInput.svelte'
import Input from 'src/partials/Input.svelte'
@ -77,7 +77,7 @@
return tryJson(() => ({
...zap,
invoice: bolt11.decode(zapMeta.bolt11),
invoiceAmount: invoiceAmount(zapMeta.bolt11),
request: JSON.parse(zapMeta.description),
}))
})
@ -91,11 +91,11 @@
return false
}
const {invoice, request} = zap
const {invoiceAmount, request} = zap
const reqMeta = Tags.from(request).asMeta()
// Verify that the zapper actually sent the requested amount (if it was supplied)
if (reqMeta.amount && reqMeta.amount !== parseInt(invoice.millisatoshis)) {
if (reqMeta.amount && parseInt(reqMeta.amount) !== invoiceAmount) {
return false
}
@ -117,7 +117,7 @@
$: zapped = find(z => z.request.pubkey === $profile?.pubkey, zaps)
$: $likesCount = likes.length
$: $flagsCount = flags.length
$: $zapsTotal = sum(zaps.map(zap => zap.invoice.satoshis))
$: $zapsTotal = sum(zaps.map(zap => zap.invoiceAmount)) / 1000
$: $repliesCount = note.replies.length
$: visibleNotes = note.replies.filter(r => showContext ? true : !r.isContext)
$: canZap = $person?.zapper && user.canZap()