diff --git a/package-lock.json b/package-lock.json index d9650d55..2a7f500a 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 8b069f91..51645da1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/agent/network.ts b/src/agent/network.ts index d1cc43e6..6f953812 100644 --- a/src/agent/network.ts +++ b/src/agent/network.ts @@ -200,4 +200,3 @@ const applyContext = (notes, context) => { export default { listen, load, loadPeople, personKinds, loadParents, streamContext, applyContext, } - diff --git a/src/agent/sync.ts b/src/agent/sync.ts index 83de1558..bdfdbfc1 100644 --- a/src/agent/sync.ts +++ b/src/agent/sync.ts @@ -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) { diff --git a/src/util/lightning.ts b/src/util/lightning.ts new file mode 100644 index 00000000..e1800319 --- /dev/null +++ b/src/util/lightning.ts @@ -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) +} diff --git a/src/util/misc.ts b/src/util/misc.ts index e2e4c29b..782b9356 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -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() diff --git a/src/views/notes/Note.svelte b/src/views/notes/Note.svelte index e752d40c..a8bd034f 100644 --- a/src/views/notes/Note.svelte +++ b/src/views/notes/Note.svelte @@ -1,6 +1,5 @@