Skip to content

Commit

Permalink
feat: add lnurlw protocol support (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
dolcalmi authored Jan 8, 2023
1 parent 4e5f7c4 commit 9bb427c
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 24 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ yarn add lnurl-withdraw
```js
import { requestPayment } from 'lnurl-withdraw'

const { requested, params, invoice, hasValidAmount, hasValidDescription } =
const { sent, params, invoice, hasValidAmount, hasValidDescription } =
await requestPayment({
lnUrl:
'lnurl1dp68gurn8ghj7amfw35xgunpwuh8xarpva5kueewvaskcmme9e5k7tewwajkcmpdddhx7amw9akxuatjd3mj7ar9wd6xjmn8jx0750',
Expand Down Expand Up @@ -51,7 +51,7 @@ Request a payment to a lnurl service
@returns
{
requested: <True if returned status is OK Bool>
sent: <True if returned status is OK Bool>
invoice: <Invoice param String>
hasValidAmount: <True if the invoice amount is a valid amount according to returned min and max values Bool>
hasValidDescription: <True if the invoice description is equal to service returned description Bool>
Expand All @@ -71,7 +71,7 @@ Request a payment to a lnurl service
Example:

```node
const { requested, params, invoice, hasValidAmount, hasValidDescription } =
const { sent, params, invoice, hasValidAmount, hasValidDescription } =
await requestPayment({
lnUrl:
'lnurl1dp68gurn8ghj7amfw35xgunpwuh8xarpva5kueewvaskcmme9e5k7tewwajkcmpdddhx7amw9akxuatjd3mj7ar9wd6xjmn8jx0750',
Expand Down Expand Up @@ -141,7 +141,7 @@ Request a payment to a lnurl with the given invoice and service params (2nd step
@returns
{
requested: <True if returned status is OK Bool>
sent: <True if returned status is OK Bool>
invoice: <Invoice param String>
hasValidAmount: <True if the invoice amount is a valid amount according to returned min and max values Bool>
hasValidDescription: <True if the invoice description is equal to service returned description Bool>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@
"url": "https://github.com/dolcalmi/lnurl-withdraw/issues"
},
"dependencies": {
"@types/node": "^18.11.18",
"assert": "^2.0.0",
"axios": "^1.2.2",
"bech32": "^2.0.0",
"bolt11": "^1.4.0",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"https-browserify": "^1.0.0",
"is-url": "^1.2.4",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"tslib": "^2.4.1",
Expand Down
2 changes: 1 addition & 1 deletion src/request-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const requestPaymentWithServiceParams = async ({
const status = data && data.status && data.status.toUpperCase()

return {
requested: status === 'OK',
sent: status === 'OK',
params,
invoice,
hasValidAmount,
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export type LnUrlRequestPaymentArgs = LnUrlRequestPaymentBaseArgs & {
}

export type LnUrlRequestPaymentResponse = {
requested: boolean
sent: boolean
invoice: string
hasValidAmount: boolean
hasValidDescription: boolean
Expand Down
34 changes: 24 additions & 10 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import axios, { AxiosError } from 'axios'
import isURL from 'is-url'
import { bech32 } from 'bech32'
import * as bolt11 from 'bolt11'

import type { Satoshis } from './types'

const LNURL_REGEX =
/^(?:http.*[&?]lightning=|lightning:)?(lnurl[0-9]{1,}[02-9ac-hj-np-z]+)/
/^(?:http.*[&?]lightning=|http.*[&?]lnurlw=|lightning:|lnurlw:)?(lnurl[0-9]{1,}[02-9ac-hj-np-z]+)/

const ONION_REGEX = /^(http:\/\/[^/:@]+\.onion(?::\d{1,5})?)(\/.*)?$/

Expand All @@ -17,10 +16,20 @@ const ONION_REGEX = /^(http:\/\/[^/:@]+\.onion(?::\d{1,5})?)(\/.*)?$/
* @return plain url or null if is an invalid url or lightning address
*/
export const decodeUrl = (lnUrl: string): string | null => {
const bech32Url = parseLnUrl(lnUrl)
if (!lnUrl) return null

let url = lnUrl

const bech32Url = parseLnUrl(url)
if (bech32Url) {
const decoded = bech32.decode(bech32Url, 20000)
return Buffer.from(bech32.fromWords(decoded.words)).toString()
url = Buffer.from(bech32.fromWords(decoded.words)).toString()
}

const parsedUrl = parseUrl(url)
if (parsedUrl) {
const newProtocol = isOnionUrl(url) ? 'http:' : 'https:'
return url.replace(parsedUrl.protocol, newProtocol)
}

return null
Expand Down Expand Up @@ -49,19 +58,24 @@ export const isLnurl = (url: string): boolean => {
return LNURL_REGEX.test(url.toLowerCase())
}

export const parseUrl = (url: string | null): URL | null => {
if (!url) return null
try {
return new URL(url)
} catch {
return null
}
}

/**
* Verify if a string is an url
* @method isUrl
* @param url string to validate
* @return true if is an url
*/
export const isUrl = (url: string | null): url is string => {
if (!url) return false
try {
return isURL(url)
} catch {
return false
}
const parsedUrl = parseUrl(url)
return !!parsedUrl
}

/**
Expand Down
2 changes: 1 addition & 1 deletion test/request-payment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('requestPayment', () => {
validateInvoice: true,
})
expect(result).toMatchObject({
requested: true,
sent: true,
params: { ...serviceParamsExpected },
invoice,
hasValidAmount: true,
Expand Down
24 changes: 24 additions & 0 deletions test/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export const validLnUrls = [
`${defaultBech32Url.toUpperCase()}`,
`lightning:${defaultBech32Url}`,
`lightning:${defaultBech32Url.toUpperCase()}`,
`lnurlw:${defaultBech32Url}`,
`lnurlw:${defaultBech32Url.toUpperCase()}`,
`LIGHTNING:${defaultBech32Url}`,
`LIGHTNING:${defaultBech32Url.toUpperCase()}`,
`LNURLW:${defaultBech32Url}`,
`LNURLW:${defaultBech32Url.toUpperCase()}`,
`http://domain.com/?lightning=${defaultBech32Url}`,
`http://domain.com/?lightning=${defaultBech32Url.toUpperCase()}`,
`http://www.domain.com/?lightning=${defaultBech32Url}`,
Expand All @@ -14,12 +20,22 @@ export const validLnUrls = [
`http://domain.com/pagina.html?lightning=${defaultBech32Url.toUpperCase()}`,
`http://www.domain.com/pagina.html?lightning=${defaultBech32Url}`,
`http://www.domain.com/pagina.html?lightning=${defaultBech32Url.toUpperCase()}`,
`http://domain.com/?lnurlw=${defaultBech32Url}`,
`http://domain.com/?lnurlw=${defaultBech32Url.toUpperCase()}`,
`http://www.domain.com/?lnurlw=${defaultBech32Url}`,
`http://www.domain.com/?lnurlw=${defaultBech32Url.toUpperCase()}`,
`http://domain.com/pagina.html?lnurlw=${defaultBech32Url}`,
`http://domain.com/pagina.html?lnurlw=${defaultBech32Url.toUpperCase()}`,
`http://www.domain.com/pagina.html?lnurlw=${defaultBech32Url}`,
`http://www.domain.com/pagina.html?lnurlw=${defaultBech32Url.toUpperCase()}`,
]

export const invalidLnUrls = [
`lnurl`,
`lightning:abcd`,
`lightning:lnurl`,
`lnurlw:abcd`,
`lnurlw:lnurl`,
`http://domain.com/?lightning=abcd`,
`http://domain.com/?lightning=lnurl`,
`http://www.domain.com/?lightning=abcd`,
Expand All @@ -28,4 +44,12 @@ export const invalidLnUrls = [
`http://domain.com/pagina.html?lightning=lnurl`,
`http://www.domain.com/pagina.html?lightning=abcd`,
`http://www.domain.com/pagina.html?lightning=lnurl`,
`http://domain.com/?lnurlw=abcd`,
`http://domain.com/?lnurlw=lnurl`,
`http://www.domain.com/?lnurlw=abcd`,
`http://www.domain.com/?lnurlw=lnurl`,
`http://domain.com/pagina.html?lnurlw=abcd`,
`http://domain.com/pagina.html?lnurlw=lnurl`,
`http://www.domain.com/pagina.html?lnurlw=abcd`,
`http://www.domain.com/pagina.html?lnurlw=lnurl`,
]
7 changes: 1 addition & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==

"@types/node@*":
"@types/node@*", "@types/node@^18.11.18":
version "18.11.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f"
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
Expand Down Expand Up @@ -2688,11 +2688,6 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.3:
gopd "^1.0.1"
has-tostringtag "^1.0.0"

is-url@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==

isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
Expand Down

0 comments on commit 9bb427c

Please sign in to comment.