diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index cef9a77..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "root": true, - "parserOptions": { - "ecmaVersion": 9, - "ecmaFeatures": { - "jsx": true - }, - "sourceType": "module", - "allowImportExportEverywhere": false - }, - - "env": { - "es6": true, - "node": true - }, - - "globals": { - "document": false, - "navigator": false, - "window": false, - "location": false, - "URL": false, - "URLSearchParams": false, - "fetch": false, - "EventSource": false, - "localStorage": false, - "sessionStorage": false, - "BigInt": false - }, - - "rules": { - "accessor-pairs": 2, - "arrow-spacing": [2, {"before": true, "after": true}], - "block-spacing": [2, "always"], - "brace-style": [2, "1tbs", {"allowSingleLine": true}], - "comma-dangle": 0, - "comma-spacing": [2, {"before": false, "after": true}], - "comma-style": [2, "last"], - "constructor-super": 2, - "curly": [0, "multi-line"], - "dot-location": [2, "property"], - "eol-last": 2, - "eqeqeq": [2, "allow-null"], - "generator-star-spacing": [2, {"before": true, "after": true}], - "handle-callback-err": [2, "^(err|error)$"], - "indent": 0, - "jsx-quotes": [2, "prefer-double"], - "key-spacing": [2, {"beforeColon": false, "afterColon": true}], - "keyword-spacing": [2, {"before": true, "after": true}], - "new-cap": 0, - "new-parens": 0, - "no-array-constructor": 2, - "no-caller": 2, - "no-class-assign": 2, - "no-cond-assign": 2, - "no-const-assign": 2, - "no-control-regex": 0, - "no-debugger": 0, - "no-delete-var": 2, - "no-dupe-args": 2, - "no-dupe-class-members": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty-character-class": 2, - "no-empty-pattern": 2, - "no-eval": 0, - "no-ex-assign": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-extra-boolean-cast": 2, - "no-extra-parens": [2, "functions"], - "no-fallthrough": 2, - "no-floating-decimal": 2, - "no-func-assign": 2, - "no-implied-eval": 2, - "no-inner-declarations": [0, "functions"], - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-iterator": 2, - "no-label-var": 2, - "no-labels": [2, {"allowLoop": false, "allowSwitch": false}], - "no-lone-blocks": 2, - "no-mixed-spaces-and-tabs": 2, - "no-multi-spaces": 2, - "no-multi-str": 2, - "no-multiple-empty-lines": [2, {"max": 2}], - "no-native-reassign": 2, - "no-negated-in-lhs": 2, - "no-new": 0, - "no-new-func": 2, - "no-new-object": 2, - "no-new-require": 2, - "no-new-symbol": 2, - "no-new-wrappers": 2, - "no-obj-calls": 2, - "no-octal": 2, - "no-octal-escape": 2, - "no-path-concat": 0, - "no-proto": 2, - "no-redeclare": 2, - "no-regex-spaces": 2, - "no-return-assign": 0, - "no-self-assign": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-shadow-restricted-names": 2, - "no-spaced-func": 2, - "no-sparse-arrays": 2, - "no-this-before-super": 2, - "no-throw-literal": 2, - "no-trailing-spaces": 2, - "no-undef": 2, - "no-undef-init": 2, - "no-unexpected-multiline": 2, - "no-unneeded-ternary": [2, {"defaultAssignment": false}], - "no-unreachable": 2, - "no-unused-vars": [ - 2, - {"vars": "local", "args": "none", "varsIgnorePattern": "^_"} - ], - "no-useless-call": 2, - "no-useless-constructor": 2, - "no-with": 2, - "one-var": [0, {"initialized": "never"}], - "operator-linebreak": [ - 2, - "after", - {"overrides": {"?": "before", ":": "before"}} - ], - "padded-blocks": [2, "never"], - "quotes": [ - 2, - "single", - {"avoidEscape": true, "allowTemplateLiterals": true} - ], - "semi": [2, "never"], - "semi-spacing": [2, {"before": false, "after": true}], - "space-before-blocks": [2, "always"], - "space-before-function-paren": 0, - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-unary-ops": [2, {"words": true, "nonwords": false}], - "spaced-comment": 0, - "template-curly-spacing": [2, "never"], - "use-isnan": 2, - "valid-typeof": 2, - "wrap-iife": [2, "any"], - "yield-star-spacing": [2, "both"], - "yoda": [0] - } -} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..312a86c --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,96 @@ +{ + "plugins": [ + "import", + "jsdoc", + "node", + "oxc", + "promise", + "unicorn" + ], + "categories": { + "correctness": "warn", + "suspicious": "warn", + "perf": "warn", + "pedantic": "warn" + }, + "files": ["**/*.js"], + "rules": { + "eslint/array-callback-return": ["warn", { "checkForEach": true }], + "eslint/eqeqeq": ["warn", "always", {"null": "ignore"}], + "eslint/func-style": ["off", "expression"], + "eslint/guard-for-in": "warn", + "eslint/no-duplicate-imports": ["warn", { "includeExports": true }], + "eslint/no-empty": "warn", + "eslint/no-empty-function": "warn", + "eslint/no-iterator": "warn", + "eslint/no-lone-blocks": "warn", + "eslint/no-multi-assign": "warn", + "eslint/no-multi-str": "warn", + "eslint/no-nested-ternary": "warn", + "eslint/no-new-func": "warn", + "eslint/no-proto": "warn", + "eslint/no-return-assign": ["warn", "always"], + "eslint/no-script-url": "warn", + "eslint/no-unused-expressions": "warn", + "eslint/no-var": "warn", + "eslint/no-void": "warn", + "eslint/prefer-exponentiation-operator": "warn", + "eslint/prefer-numeric-literals": "warn", + "eslint/prefer-object-has-own": "warn", + "eslint/prefer-object-spread": "warn", + "eslint/prefer-spread": "warn", + "eslint/yoda": "warn", + "eslint/max-depth": "off", + "eslint/max-classes-per-file": "off", + "eslint/max-lines": "off", + "eslint/max-lines-per-function": "off", + "eslint/max-nested-callbacks": "off", + "eslint/new-cap": "off", + "eslint/radix": "off", + "import/consistent-type-specifier-style": "warn", + "import/exports-last": "warn", + "import/first": "warn", + "import/group-exports": "warn", + "import/no-amd": "warn", + "import/no-commonjs": "warn", + "import/no-cycle": "warn", + "import/no-dynamic-require": "warn", + "import/no-mutable-exports": "warn", + "import/no-named-default": "warn", + "import/no-webpack-loader-syntax": "warn", + "import/unambiguous": "warn", + "import/max-dependencies": "off", + "jsdoc/check-access": "warn", + "jsdoc/empty-tags": "warn", + "jsdoc/require-param-description": "off", + "jsdoc/require-returns-description": "off", + "node/no-new-require": "warn", + "oxc/bad-bitwise-operator": "warn", + "oxc/no-barrel-file": "warn", + "promise/prefer-await-to-callbacks": "warn", + "promise/prefer-await-to-then": "warn", + "promise/spec-only": "warn", + "unicorn/no-abusive-eslint-disable": "warn", + "unicorn/no-anonymous-default-export": "warn", + "unicorn/no-array-for-each": "warn", + "unicorn/no-array-method-this-argument": "warn", + "unicorn/no-array-reduce": "warn", + "unicorn/no-document-cookie": "warn", + "unicorn/no-for-loop": "warn", + "unicorn/no-length-as-slice-end": "warn", + "unicorn/no-nested-ternary": "warn", + "unicorn/prefer-array-index-of": "warn", + "unicorn/prefer-global-this": "warn", + "unicorn/prefer-modern-math-apis": "warn", + "unicorn/prefer-node-protocol": "warn", + "unicorn/prefer-number-properties": "warn", + "unicorn/prefer-object-from-entries": "warn" + }, + "env": { + "node": true, + "builtin": true + }, + "ignorePatterns": [ + "node_modules/*" + ] +} diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index 16c878e..0000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -semi: false -arrowParens: avoid -insertPragma: false -printWidth: 80 -proseWrap: preserve -singleQuote: true -trailingComma: none -useTabs: false -jsxBracketSameLine: false -bracketSpacing: false diff --git a/README.md b/README.md index 242eb58..f2b2860 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A lightweight and naïve library for decoding lightning network payment requests It doesn't recover payee from signature, doesn't check signature, doesn't parse fallback addresses and doesn't do any encoding -- therefore dependencies are very minimal (no libsecp256k1 here). -Code derived from [bolt11](https://npmjs.com/package/bolt11), which has the full functionality but it's a pain to run in browsers. +Code derived from [bolt11](https://npmjs.com/package/bolt11), which has the full functionality, but it's a pain to run in browsers. Spits out "sections" of the invoice, in a way that is used to make visualizations like https://bolt11.org/. diff --git a/bolt11.d.ts b/bolt11.d.ts deleted file mode 100644 index 74fd82a..0000000 --- a/bolt11.d.ts +++ /dev/null @@ -1,89 +0,0 @@ -declare module "light-bolt11-decoder" { - type NetworkSection = { - name: 'coin_network'; - letters: string; - value?: { - bech32: string; - pubKeyHash: number; - scriptHash: number; - validWitnessVersions: number[]; - }; - }; - - type FeatureBits = { - option_data_loss_protect: string; - initial_routing_sync: string; - option_upfront_shutdown_script: string; - gossip_queries: string; - var_onion_optin: string; - gossip_queries_ex: string; - option_static_remotekey: string; - payment_secret: string; - basic_mpp: string; - option_support_large_channel: string; - extra_bits: { - start_bit: number; - bits: unknown[]; - has_required: boolean; - }; - }; - - type RouteHint = { - pubkey: string; - short_channel_id: string; - fee_base_msat: number; - fee_proportional_millionths: number; - cltv_expiry_delta: number; - }; - - type RouteHintSection = { - name: "route_hint"; - tag: "r"; - letters: string; - value: RouteHint[]; - }; - - type FeatureBitsSection = { - name: "feature_bits"; - tag: "9"; - letters: string; - value: FeatureBits; - }; - - type Section = - | { name: "paymentRequest"; value: string } - | { name: "expiry"; value: number } - | { name: "checksum"; letters: string } - | NetworkSection - | { name: "amount"; letters: string; value: string } - | { name: "separator"; letters: string } - | { name: "timestamp"; letters: string; value: number } - | { name: "payment_hash"; tag: "p"; letters: string; value: string } - | { name: "description"; tag: "d"; letters: string; value: string } - | { name: "payment_secret"; tag: "s"; letters: string; value: string } - | { - name: "min_final_cltv_expiry"; - tag: "c"; - letters: string; - value: number; - } - | FeatureBitsSection - | RouteHintSection - | { name: "signature"; letters: string; value: string }; - - type PaymentJSON = { - paymentRequest: string; - sections: Section[]; - expiry: number; - route_hints: RouteHint[][]; - }; - - type DecodedInvoice = { - paymentRequest: string; - sections: Section[]; - expiry: number; - route_hints: RouteHint[][]; - }; - - function decode(invoice: string): DecodedInvoice; -} diff --git a/bolt11.js b/bolt11.js index d1897ea..c402a85 100644 --- a/bolt11.js +++ b/bolt11.js @@ -1,6 +1,66 @@ -const {bech32, hex, utf8} = require('@scure/base') +import {bech32, hex, utf8} from '@scure/base' + +/** + * + * @typedef {{bech32: string, pubKeyHash: number, scriptHash: number, validWitnessVersions: number[]}} Network + * + * @typedef {{ + * name: 'coin_network', + * letters: string, + * value?: Network + * }} NetworkSection + * + * @typedef {{ + * option_data_loss_protect: string, + * initial_routing_sync: string, + * option_upfront_shutdown_script: string, + * gossip_queries: string, + * var_onion_optin: string, + * gossip_queries_ex: string, + * option_static_remotekey: string, + * payment_secret: string, + * basic_mpp: string, + * option_support_large_channel: string, + * extra_bits: { + * start_bit: number, + * bits: unknown[], + * has_required: boolean + * } + * }} FeatureBits + * + * @typedef {{ pubkey: string, short_channel_id: string, fee_base_msat: number, fee_proportional_millionths: number, cltv_expiry_delta: number }} RouteHint + * @typedef {{ name: "route_hint", tag: "r", letters: string, value: RouteHint[] }} RouteHintSection + * @typedef {{ name: "feature_bits", tag: "9", letters: string, value: FeatureBits }} FeatureBitsSection + * + * @typedef { + * | { name: "paymentRequest", value: string } + * | { name: "expiry", value: number } + * | { name: "checksum", letters: string } + * | NetworkSection + * | { name: "amount", letters: string; value: string } + * | { name: "separator", letters: string } + * | { name: "timestamp", letters: string, value: number } + * | { name: "payment_hash", tag: "p", letters: string, value: string } + * | { name: "description", tag: "d", letters: string, value: string } + * | { name: "payment_secret", tag: "s", letters: string, value: string } + * | { + * name: "min_final_cltv_expiry", + * tag: "c", + * letters: string, + * value: number + * } + * | FeatureBitsSection + * | RouteHintSection + * | { name: "signature", letters: string, value: string } + * | { name: "lightning_network", letters: string } + * } Section + * + * @typedef {{ paymentRequest: string, sections: Section[], expiry: number, route_hints: RouteHint[][] }} DecodedInvoice + * + */ // defaults for encode; default timestamp is current time at call +/** @type {Network} */ const DEFAULTNETWORK = { // default network is bitcoin bech32: 'bc', @@ -8,31 +68,35 @@ const DEFAULTNETWORK = { scriptHash: 0x05, validWitnessVersions: [0] } +/** @type {Network} */ const TESTNETWORK = { bech32: 'tb', pubKeyHash: 0x6f, scriptHash: 0xc4, validWitnessVersions: [0] } +/** @type {Network} */ const SIGNETNETWORK = { bech32: 'tbs', pubKeyHash: 0x6f, scriptHash: 0xc4, validWitnessVersions: [0] } +/** @type {Network} */ const REGTESTNETWORK = { bech32: 'bcrt', pubKeyHash: 0x6f, scriptHash: 0xc4, validWitnessVersions: [0] } +/** @type {Network} */ const SIMNETWORK = { bech32: 'sb', pubKeyHash: 0x3f, scriptHash: 0x7b, validWitnessVersions: [0] } - +/** @type {string[]} */ const FEATUREBIT_ORDER = [ 'option_data_loss_protect', 'initial_routing_sync', @@ -92,38 +156,50 @@ const TAGPARSERS = { 5: featureBitsParser // keep feature bits as array of 5 bit words } +/** + * + * @param {string} tagCode + * @returns {function(*): {tagCode: number, words: `unknown1${string}`}} + */ function getUnknownParser(tagCode) { return words => ({ - tagCode: parseInt(tagCode), + tagCode: Number.parseInt(tagCode), words: bech32.encode('unknown', words, Number.MAX_SAFE_INTEGER) }) } +/** + * + * @param {number[]} words + * @returns {*} + */ function wordsToIntBE(words) { - return words.reverse().reduce((total, item, index) => { - return total + item * Math.pow(32, index) - }, 0) + return words.toReversed().reduce((total, item, index) => total + item * (32**index), 0) } -// first convert from words to buffer, trimming padding where necessary -// parse in 51 byte chunks. See encoder for details. +/** + * First convert from words to buffer, trimming padding where necessary + * parse in 51 byte chunks. See encoder for details. + * @param {number[]} words + * @returns {*[]} + */ function routingInfoParser(words) { const routes = [] - let pubkey, - shortChannelId, - feeBaseMSats, - feeProportionalMillionths, - cltvExpiryDelta + let pubkey + let shortChannelId + let feeBaseMSats + let feeProportionalMillionths + let cltvExpiryDelta let routesBuffer = bech32.fromWordsUnsafe(words) while (routesBuffer.length > 0) { pubkey = hex.encode(routesBuffer.slice(0, 33)) // 33 bytes shortChannelId = hex.encode(routesBuffer.slice(33, 41)) // 8 bytes - feeBaseMSats = parseInt(hex.encode(routesBuffer.slice(41, 45)), 16) // 4 bytes - feeProportionalMillionths = parseInt( + feeBaseMSats = Number.parseInt(hex.encode(routesBuffer.slice(41, 45)), 16) // 4 bytes + feeProportionalMillionths = Number.parseInt( hex.encode(routesBuffer.slice(45, 49)), 16 ) // 4 bytes - cltvExpiryDelta = parseInt(hex.encode(routesBuffer.slice(49, 51)), 16) // 2 bytes + cltvExpiryDelta = Number.parseInt(hex.encode(routesBuffer.slice(49, 51)), 16) // 2 bytes routesBuffer = routesBuffer.slice(51) @@ -138,10 +214,15 @@ function routingInfoParser(words) { return routes } +/** + * + * @param {Uint8Array} words + * @returns {{}} + */ function featureBitsParser(words) { const bools = words .slice() - .reverse() + .toReversed() .map(word => [ !!(word & 0b1), !!(word & 0b10), @@ -150,23 +231,22 @@ function featureBitsParser(words) { !!(word & 0b10000) ]) .reduce((finalArr, itemArr) => finalArr.concat(itemArr), []) - while (bools.length < FEATUREBIT_ORDER.length * 2) { + while (bools.length < FEATUREBIT_ORDER.length * 2) bools.push(false) - } const featureBits = {} - FEATUREBIT_ORDER.forEach((featureName, index) => { + for (const featureName of FEATUREBIT_ORDER) { + const index = FEATUREBIT_ORDER.indexOf(featureName); let status - if (bools[index * 2]) { + if (bools[index * 2]) status = 'required' - } else if (bools[index * 2 + 1]) { + else if (bools[index * 2 + 1]) status = 'supported' - } else { + else status = 'unsupported' - } featureBits[featureName] = status - }) + } const extraBits = bools.slice(FEATUREBIT_ORDER.length * 2) featureBits.extra_bits = { @@ -174,7 +254,7 @@ function featureBitsParser(words) { bits: extraBits, has_required: extraBits.reduce( (result, bit, index) => - index % 2 !== 0 ? result || false : result || bit, + index % 2 === 0 ? result || bit : result || false, false ) } @@ -182,18 +262,23 @@ function featureBitsParser(words) { return featureBits } +/** + * + * @param {string} hrpString + * @param {boolean} outputString + * @returns {string|bigint} + */ function hrpToMillisat(hrpString, outputString) { let divisor, value - if (hrpString.slice(-1).match(/^[munp]$/)) { + if (/^[munp]$/.test(hrpString.slice(-1))) { divisor = hrpString.slice(-1) value = hrpString.slice(0, -1) - } else if (hrpString.slice(-1).match(/^[^munp0-9]$/)) { + } else if (/^[^munp0-9]$/.test(hrpString.slice(-1))) throw new Error('Not a valid multiplier for the amount') - } else { + else value = hrpString - } - if (!value.match(/^\d+$/)) + if (!/^\d+$/.test(value)) throw new Error('Not a valid human readable amount') const valueBN = BigInt(value) @@ -205,31 +290,36 @@ function hrpToMillisat(hrpString, outputString) { if ( (divisor === 'p' && !(valueBN % BigInt(10) === BigInt(0))) || millisatoshisBN > MAX_MILLISATS - ) { + ) throw new Error('Amount is outside of valid range') - } return outputString ? millisatoshisBN.toString() : millisatoshisBN } -// decode will only have extra comments that aren't covered in encode comments. -// also if anything is hard to read I'll comment. +/** + * Decode will only have extra comments that aren't covered in encode comments. + * Also, if anything is hard to read I'll comment. + * @param {string} paymentRequest + * @param {Network=} network + * @returns {DecodedInvoice} + */ function decode(paymentRequest, network) { if (typeof paymentRequest !== 'string') throw new Error('Lightning Payment Request must be string') if (paymentRequest.slice(0, 2).toLowerCase() !== 'ln') throw new Error('Not a proper lightning payment request') + /** @type {Section[]} */ const sections = [] const decoded = bech32.decode(paymentRequest, Number.MAX_SAFE_INTEGER) - paymentRequest = paymentRequest.toLowerCase() + const paymentRequest_lower = paymentRequest.toLowerCase() const prefix = decoded.prefix let words = decoded.words - let letters = paymentRequest.slice(prefix.length + 1) + let letters = paymentRequest_lower.slice(prefix.length + 1) let sigWords = words.slice(-104) words = words.slice(0, -104) - // Without reverse lookups, can't say that the multipier at the end must + // Without reverse lookups, can't say that the multiplier at the end must // have a number before it, so instead we parse, and if the second group // doesn't have anything, there's a good chance the last letter of the // coin type got captured by the third group, so just re-regex without @@ -237,9 +327,8 @@ function decode(paymentRequest, network) { let prefixMatches = prefix.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/) if (prefixMatches && !prefixMatches[2]) prefixMatches = prefix.match(/^ln(\S+)$/) - if (!prefixMatches) { + if (!prefixMatches) throw new Error('Not a proper lightning payment request') - } // "ln" section sections.push({ @@ -250,7 +339,16 @@ function decode(paymentRequest, network) { // "bc" section const bech32Prefix = prefixMatches[1] let coinNetwork - if (!network) { + if (network) { + if ( + network.bech32 === undefined || + network.pubKeyHash === undefined || + network.scriptHash === undefined || + !Array.isArray(network.validWitnessVersions) + ) + throw new Error('Invalid network') + coinNetwork = network + } else { switch (bech32Prefix) { case DEFAULTNETWORK.bech32: coinNetwork = DEFAULTNETWORK @@ -268,19 +366,10 @@ function decode(paymentRequest, network) { coinNetwork = SIMNETWORK break } - } else { - if ( - network.bech32 === undefined || - network.pubKeyHash === undefined || - network.scriptHash === undefined || - !Array.isArray(network.validWitnessVersions) - ) - throw new Error('Invalid network') - coinNetwork = network } - if (!coinNetwork || coinNetwork.bech32 !== bech32Prefix) { + if (!coinNetwork || coinNetwork.bech32 !== bech32Prefix) throw new Error('Unknown coin bech32 prefix') - } + sections.push({ name: 'coin_network', letters: bech32Prefix, @@ -298,9 +387,8 @@ function decode(paymentRequest, network) { letters: prefixMatches[2] + prefixMatches[3], value: millisatoshis }) - } else { + } else millisatoshis = null - } // "1" separator sections.push({ @@ -318,7 +406,10 @@ function decode(paymentRequest, network) { }) letters = letters.slice(7) - let tagName, parser, tagLength, tagWords + let tagName + let parser + let tagLength + let tagWords // we have no tag count to go on, so just keep hacking off words // until we have none. while (words.length > 0) { @@ -371,10 +462,9 @@ function decode(paymentRequest, network) { } for (let name in TAGCODES) { - if (name === 'route_hint') { + if (name === 'route_hint') // route hints can be multiple, so this won't work for them continue - } Object.defineProperty(result, name, { get() { @@ -385,13 +475,18 @@ function decode(paymentRequest, network) { return result + /** + * + * @param {string} name + * @returns {*|undefined} + */ function getValue(name) { let section = sections.find(s => s.name === name) return section ? section.value : undefined } } -module.exports = { +export { decode, hrpToMillisat } diff --git a/examples/.oxlintrc.json b/examples/.oxlintrc.json new file mode 100644 index 0000000..777d298 --- /dev/null +++ b/examples/.oxlintrc.json @@ -0,0 +1,106 @@ +{ + "plugins": [ + "import", + "jsdoc", + "jsx-a11y", + "node", + "oxc", + "promise", + "react", + "react-perf", + "unicorn" + ], + "categories": { + "correctness": "warn", + "suspicious": "warn", + "perf": "warn", + "pedantic": "warn" + }, + "files": ["**/*.js"], + "rules": { + "eslint/array-callback-return": ["warn", { "checkForEach": true }], + "eslint/eqeqeq": ["warn", "always", {"null": "ignore"}], + "eslint/func-style": ["off", "expression"], + "eslint/guard-for-in": "warn", + "eslint/no-duplicate-imports": ["warn", { "includeExports": true }], + "eslint/no-empty": "warn", + "eslint/no-empty-function": "warn", + "eslint/no-iterator": "warn", + "eslint/no-lone-blocks": "warn", + "eslint/no-multi-assign": "warn", + "eslint/no-multi-str": "warn", + "eslint/no-nested-ternary": "warn", + "eslint/no-new-func": "warn", + "eslint/no-proto": "warn", + "eslint/no-return-assign": ["warn", "always"], + "eslint/no-script-url": "warn", + "eslint/no-unused-expressions": "warn", + "eslint/no-var": "warn", + "eslint/no-void": "warn", + "eslint/prefer-exponentiation-operator": "warn", + "eslint/prefer-numeric-literals": "warn", + "eslint/prefer-object-has-own": "warn", + "eslint/prefer-object-spread": "warn", + "eslint/prefer-spread": "warn", + "eslint/yoda": "warn", + "eslint/max-depth": "off", + "eslint/max-classes-per-file": "off", + "eslint/max-lines": "off", + "eslint/max-lines-per-function": "off", + "eslint/max-nested-callbacks": "off", + "eslint/new-cap": "off", + "eslint/radix": "off", + "import/consistent-type-specifier-style": "warn", + "import/exports-last": "warn", + "import/first": "warn", + "import/group-exports": "warn", + "import/no-amd": "warn", + "import/no-commonjs": "warn", + "import/no-cycle": "warn", + "import/no-dynamic-require": "warn", + "import/no-mutable-exports": "warn", + "import/no-named-default": "warn", + "import/no-webpack-loader-syntax": "warn", + "import/unambiguous": "warn", + "import/max-dependencies": "off", + "jsdoc/check-access": "warn", + "jsdoc/empty-tags": "warn", + "jsdoc/require-param-description": "off", + "jsdoc/require-returns-description": "off", + "node/no-new-require": "warn", + "oxc/bad-bitwise-operator": "warn", + "oxc/no-barrel-file": "warn", + "promise/prefer-await-to-callbacks": "warn", + "promise/prefer-await-to-then": "warn", + "promise/spec-only": "warn", + "react/button-has-type": "warn", + "react/no-danger": "warn", + "react/no-unknown-property": "warn", + "react/require-render-return": "warn", + "react/prefer-es6-class": "warn", + "react/self-closing-comp": "warn", + "unicorn/no-abusive-eslint-disable": "warn", + "unicorn/no-anonymous-default-export": "warn", + "unicorn/no-array-for-each": "warn", + "unicorn/no-array-method-this-argument": "warn", + "unicorn/no-array-reduce": "warn", + "unicorn/no-document-cookie": "warn", + "unicorn/no-for-loop": "warn", + "unicorn/no-length-as-slice-end": "warn", + "unicorn/no-nested-ternary": "warn", + "unicorn/prefer-array-index-of": "warn", + "unicorn/prefer-global-this": "warn", + "unicorn/prefer-modern-math-apis": "warn", + "unicorn/prefer-node-protocol": "warn", + "unicorn/prefer-number-properties": "warn", + "unicorn/prefer-object-from-entries": "warn" + }, + "env": { + "builtin": true, + "browser": true, + "node": true + }, + "ignorePatterns": [ + "node_modules/*" + ] +} diff --git a/examples/build.js b/examples/build.js deleted file mode 100755 index 52bf337..0000000 --- a/examples/build.js +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env node - -const esbuild = require('esbuild') -const nodeGlobals = require('@esbuild-plugins/node-globals-polyfill').default - -esbuild - .build({ - entryPoints: ['demo.jsx'], - outfile: 'demo.build.js', - bundle: true, - plugins: [nodeGlobals({buffer: true})], - define: { - window: 'self', - global: 'self' - }, - sourcemap: 'inline' - }) - .then(() => console.log('build success.')) diff --git a/examples/demo.jsx b/examples/demo.jsx index 501d216..5f45894 100644 --- a/examples/demo.jsx +++ b/examples/demo.jsx @@ -1,8 +1,8 @@ import {decode} from 'light-bolt11-decoder' import React, {useState} from 'react' -import {render} from 'react-dom' +import {createRoot} from 'react-dom/client' import useComputedState from 'use-computed-state' -import styled, {css} from 'styled-components' +import {styled, css} from 'styled-components' const TAGCOLORS = { lightning_network: 'rgb(31, 31, 40)', @@ -118,4 +118,5 @@ function Demo() { ) } -render(, document.getElementById('main')) +const root = createRoot(document.querySelector('#main')) +root.render() diff --git a/examples/example.js b/examples/example.js index 3f2f751..d4635e6 100644 --- a/examples/example.js +++ b/examples/example.js @@ -1,5 +1,7 @@ +import {decode} from '../bolt11.js' + let pr = 'lnbc20u1p3y0x3hpp5743k2g0fsqqxj7n8qzuhns5gmkk4djeejk3wkp64ppevgekvc0jsdqcve5kzar2v9nr5gpqd4hkuetesp5ez2g297jduwc20t6lmqlsg3man0vf2jfd8ar9fh8fhn2g8yttfkqxqy9gcqcqzys9qrsgqrzjqtx3k77yrrav9hye7zar2rtqlfkytl094dsp0ms5majzth6gt7ca6uhdkxl983uywgqqqqlgqqqvx5qqjqrzjqd98kxkpyw0l9tyy8r8q57k7zpy9zjmh6sez752wj6gcumqnj3yxzhdsmg6qq56utgqqqqqqqqqqqeqqjq7jd56882gtxhrjm03c93aacyfy306m4fq0tskf83c0nmet8zc2lxyyg3saz8x6vwcp26xnrlagf9semau3qm2glysp7sv95693fphvsp54l567' -let d = require('..').decode(pr) +let d = decode(pr) console.log(d) diff --git a/examples/index.html b/examples/index.html index 4add832..45c0329 100644 --- a/examples/index.html +++ b/examples/index.html @@ -3,6 +3,6 @@ bolt11 demo -
+
diff --git a/examples/package.json b/examples/package.json index 1f87547..7ebfeac 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,12 +1,42 @@ { + "type": "module", "dependencies": { - "@esbuild-plugins/node-globals-polyfill": "^0.1.1", - "buffer": "^6.0.3", - "esbuild": "^0.14.36", - "light-bolt11-decoder": "^2.0.0", - "react": "^18.0.0", - "react-dom": "^18.0.0", - "styled-components": "^5.3.5", - "use-computed-state": "^1.1.0" + "light-bolt11-decoder": "../", + "react": "^19.2.1", + "react-dom": "^19.2.1", + "styled-components": "^6.1.19", + "use-computed-state": "^1.2.0" + }, + "devDependencies": { + "rolldown": "^1.0.0-beta.53" + }, + "scripts": { + "build": "rolldown demo.jsx --file demo.build.js", + "lint": "oxlint" + }, + "pnpm": { + "overrides": { + "vite": "npm:rolldown-vite@latest", + "rolldown-vite>lightningcss": "-", + "rolldown-vite>postcss": "-", + "rolldown>@rolldown/binding-android-arm64": "-", + "rolldown>@rolldown/binding-darwin-arm64": "-", + "rolldown>@rolldown/binding-darwin-x64": "-", + "rolldown>@rolldown/binding-freebsd-x64": "-", + "rolldown>@rolldown/binding-linux-arm-gnueabihf": "-", + "rolldown>@rolldown/binding-linux-arm64-gnu": "-", + "rolldown>@rolldown/binding-linux-arm64-musl": "-", + "rolldown>@rolldown/binding-linux-x64-musl": "-", + "rolldown>@rolldown/binding-openharmony-arm64": "-", + "rolldown>@rolldown/binding-wasm32-wasi": "-", + "rolldown>@rolldown/binding-win32-arm64-msvc": "-", + "rolldown>@rolldown/binding-win32-ia32-msvc": "-", + "rolldown>@rolldown/binding-win32-x64-msvc": "-", + "@vitejs/plugin-react>@babel/core": "-", + "@vitejs/plugin-react>@babel/plugin-transform-react-jsx-self": "-", + "@vitejs/plugin-react>@babel/plugin-transform-react-jsx-source": "-", + "@vitejs/plugin-react>@types/babel__core": "-", + "@vitejs/plugin-react>react-refresh": "-" + } } } diff --git a/package.json b/package.json index 7f3cc8c..bfc70da 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "light-bolt11-decoder", - "version": "3.2.0", + "version": "3.2.2", + "type": "module", "description": "decode lightning invoices without overhead (doesn't check signatures).", "main": "bolt11.js", - "types": "bolt11.d.ts", "repository": { "type": "git", "url": "git+https://github.com/fiatjaf/light-bolt11-decoder.git" @@ -19,12 +19,46 @@ ], "author": "fiatjaf", "license": "MIT", + "scripts": { + "test": "node --test --experimental-test-coverage tests/basic.test.js", + "lint": "oxlint" + }, "dependencies": { - "@scure/base": "1.1.1" + "@scure/base": "2.0.0" }, "devDependencies": { - "eslint": "^8.0.0", - "jest": "^29.4.3", - "prettier": "^2.4.1" + "oxlint": "^1.31.0" + }, + "pnpm": { + "overrides": { + "vite": "npm:rolldown-vite@latest", + "rolldown-vite>lightningcss": "-", + "rolldown-vite>postcss": "-", + "rolldown>@rolldown/binding-android-arm64": "-", + "rolldown>@rolldown/binding-darwin-arm64": "-", + "rolldown>@rolldown/binding-darwin-x64": "-", + "rolldown>@rolldown/binding-freebsd-x64": "-", + "rolldown>@rolldown/binding-linux-arm-gnueabihf": "-", + "rolldown>@rolldown/binding-linux-arm64-gnu": "-", + "rolldown>@rolldown/binding-linux-arm64-musl": "-", + "rolldown>@rolldown/binding-linux-x64-musl": "-", + "rolldown>@rolldown/binding-openharmony-arm64": "-", + "rolldown>@rolldown/binding-wasm32-wasi": "-", + "rolldown>@rolldown/binding-win32-arm64-msvc": "-", + "rolldown>@rolldown/binding-win32-ia32-msvc": "-", + "rolldown>@rolldown/binding-win32-x64-msvc": "-", + "@vitejs/plugin-react>@babel/core": "-", + "@vitejs/plugin-react>@babel/plugin-transform-react-jsx-self": "-", + "@vitejs/plugin-react>@babel/plugin-transform-react-jsx-source": "-", + "@vitejs/plugin-react>@types/babel__core": "-", + "@vitejs/plugin-react>react-refresh": "-", + "oxlint>@oxlint/darwin-arm64": "-", + "oxlint>@oxlint/darwin-x64": "-", + "oxlint>@oxlint/linux-arm64-gnu": "-", + "oxlint>@oxlint/linux-arm64-musl": "-", + "@oxlint/linux-x64-musl": "-", + "@oxlint/win32-arm64": "-", + "@oxlint/win32-x64": "-" + } } } diff --git a/tests/basic.test.js b/tests/basic.test.js index ed5e70d..fe18e0b 100644 --- a/tests/basic.test.js +++ b/tests/basic.test.js @@ -1,13 +1,14 @@ -/* eslint-env jest */ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' -const {decode} = require('..') +import {decode} from '../bolt11.js' describe('decoding', () => { it('should decode an invoice', () => { let inv = decode( 'lnbc20u1p3y0x3hpp5743k2g0fsqqxj7n8qzuhns5gmkk4djeejk3wkp64ppevgekvc0jsdqcve5kzar2v9nr5gpqd4hkuetesp5ez2g297jduwc20t6lmqlsg3man0vf2jfd8ar9fh8fhn2g8yttfkqxqy9gcqcqzys9qrsgqrzjqtx3k77yrrav9hye7zar2rtqlfkytl094dsp0ms5majzth6gt7ca6uhdkxl983uywgqqqqlgqqqvx5qqjqrzjqd98kxkpyw0l9tyy8r8q57k7zpy9zjmh6sez752wj6gcumqnj3yxzhdsmg6qq56utgqqqqqqqqqqqeqqjq7jd56882gtxhrjm03c93aacyfy306m4fq0tskf83c0nmet8zc2lxyyg3saz8x6vwcp26xnrlagf9semau3qm2glysp7sv95693fphvsp54l567' ) - expect(inv).toEqual({ + assert.deepEqual(inv, { paymentRequest: 'lnbc20u1p3y0x3hpp5743k2g0fsqqxj7n8qzuhns5gmkk4djeejk3wkp64ppevgekvc0jsdqcve5kzar2v9nr5gpqd4hkuetesp5ez2g297jduwc20t6lmqlsg3man0vf2jfd8ar9fh8fhn2g8yttfkqxqy9gcqcqzys9qrsgqrzjqtx3k77yrrav9hye7zar2rtqlfkytl094dsp0ms5majzth6gt7ca6uhdkxl983uywgqqqqlgqqqvx5qqjqrzjqd98kxkpyw0l9tyy8r8q57k7zpy9zjmh6sez752wj6gcumqnj3yxzhdsmg6qq56utgqqqqqqqqqqqeqqjq7jd56882gtxhrjm03c93aacyfy306m4fq0tskf83c0nmet8zc2lxyyg3saz8x6vwcp26xnrlagf9semau3qm2glysp7sv95693fphvsp54l567', sections: [ diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 0967ef4..0000000 --- a/tsconfig.json +++ /dev/null @@ -1 +0,0 @@ -{}