Skip to content

Commit 5119e4f

Browse files
committed
feat: add bolt12 decoding support
1 parent 2912e28 commit 5119e4f

File tree

11 files changed

+359
-189
lines changed

11 files changed

+359
-189
lines changed

config-overrides.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,23 @@ module.exports = function override(config, env) {
2727
/**
2828
* Add WASM support
2929
*/
30-
// Make file-loader ignore WASM files
31-
const wasmExtensionRegExp = /\.wasm$/;
30+
config.experiments = {
31+
...config.experiments,
32+
asyncWebAssembly: true, // Native async WASM support in Webpack 5
33+
};
34+
35+
// Add .wasm to resolved extensions
3236
config.resolve.extensions.push('.wasm');
37+
38+
const wasmExtensionRegExp = /\.wasm$/;
3339
config.module.rules.forEach(rule => {
3440
(rule.oneOf || []).forEach(oneOf => {
35-
if (oneOf.loader && oneOf.loader.indexOf('file-loader') >= 0) {
41+
if (oneOf.type === 'asset/resource') {
42+
oneOf.exclude = oneOf.exclude || [];
3643
oneOf.exclude.push(wasmExtensionRegExp);
3744
}
3845
});
3946
});
4047

41-
// Add a dedicated loader for WASM
42-
config.module.rules.push({
43-
test: wasmExtensionRegExp,
44-
include: path.resolve(__dirname, 'src'),
45-
use: [{ loader: require.resolve('wasm-loader'), options: {} }]
46-
});
47-
4848
return config;
4949
}

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@
6767
"eslint-plugin-react": "^7.37.4",
6868
"eslint-plugin-react-hooks": "^5.2.0",
6969
"file-loader": "^6.2.0",
70-
"node": "^18.9.0",
71-
"wasm-loader": "^1.3.0"
70+
"node": "^18.9.0"
7271
},
7372
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
7473
}

public/index.html

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,53 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="utf-8">
5-
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png">
6-
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7-
<meta name="theme-color" content="#000000">
8-
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
9-
10-
<!-- Google Search -->
11-
<meta name="google-site-verification" content="d_5aizbppI7vXFrfNDlfXotnj0l1r3u7jQsVyMUMNPA" />
12-
<!-- End Google Search -->
13-
14-
<!-- Graph Tags -->
15-
<meta property="og:url" content="https://lightningdecoder.com" />
16-
<meta property="og:site_name" content="Lightning Decoder" />
17-
<meta property="og:title" content="Decode Lightning Network Requests - BOLT11, LNURL, and Lightning Address" />
18-
<meta property="og:type" content="text" />
19-
<meta property="og:image" content="https://i.imgur.com/92TblqG.png" />
20-
<meta property="og:determiner" content="auto" />
21-
<meta property="og:description" content="Lightning Decoder is a tool for decoding Lightning Network request codes - BOLT11s, LNURL codes, and Lightning Addresses are supported." />
22-
<!-- End Graph Tags -->
23-
24-
<!-- Twitter Tags -->
25-
<meta name="twitter:card" content="summary_large_image">
26-
<meta name="twitter:site" content="@andreneves">
27-
<meta name="twitter:creator" content="@andreneves">
28-
<meta name="twitter:title" content="Decode Lightning Network Requests - BOLT11, LNURL, and Lightning Address">
29-
<meta name="twitter:description" content="Lightning Decoder is a tool for decoding Lightning Network request codes - BOLT11s, LNURL codes, and Lightning Addresses are supported.">
30-
<meta name="twitter:image" content="https://i.imgur.com/92TblqG.png">
31-
<!-- End Twitter Tags -->
32-
33-
<!-- Plausible -->
34-
<script async defer data-domain="lightningdecoder.com" src="https://plausible.io/js/plausible.js"></script>
35-
36-
37-
<title>
38-
Lightning Decoder - Decode Lightning Network Requests (BOLT11, LNURL, and Lightning Address)
39-
</title>
40-
</head>
41-
<body>
42-
<noscript>
43-
You need to enable JavaScript to run this app.
44-
</noscript>
45-
<div id="root"></div>
46-
</body>
47-
</html>
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png">
7+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
8+
<meta name="theme-color" content="#000000">
9+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
10+
11+
<!-- Google Search -->
12+
<meta name="google-site-verification" content="d_5aizbppI7vXFrfNDlfXotnj0l1r3u7jQsVyMUMNPA" />
13+
<!-- End Google Search -->
14+
15+
<!-- Graph Tags -->
16+
<meta property="og:url" content="https://lightningdecoder.com" />
17+
<meta property="og:site_name" content="Lightning Decoder" />
18+
<meta property="og:title"
19+
content="Decode Lightning Network Requests - BOLT11, BOLT12, LNURL, and Lightning Address" />
20+
<meta property="og:type" content="text" />
21+
<meta property="og:image" content="https://i.imgur.com/92TblqG.png" />
22+
<meta property="og:determiner" content="auto" />
23+
<meta property="og:description"
24+
content="Lightning Decoder is a tool for decoding Lightning Network request codes - BOLT11s, LNURL codes, and Lightning Addresses are supported." />
25+
<!-- End Graph Tags -->
26+
27+
<!-- Twitter Tags -->
28+
<meta name="twitter:card" content="summary_large_image">
29+
<meta name="twitter:site" content="@andreneves">
30+
<meta name="twitter:creator" content="@andreneves">
31+
<meta name="twitter:title" content="Decode Lightning Network Requests - BOLT11, LNURL, BOLT12 and Lightning Address">
32+
<meta name="twitter:description"
33+
content="Lightning Decoder is a tool for decoding Lightning Network request codes - BOLT11s, LNURL codes, and Lightning Addresses are supported.">
34+
<meta name="twitter:image" content="https://i.imgur.com/92TblqG.png">
35+
<!-- End Twitter Tags -->
36+
37+
<!-- Plausible -->
38+
<script async defer data-domain="lightningdecoder.com" src="https://plausible.io/js/plausible.js"></script>
39+
40+
41+
<title>
42+
Lightning Decoder - Decode Lightning Network Requests (BOLT11, BOLT12, LNURL, and Lightning Address)
43+
</title>
44+
</head>
45+
46+
<body>
47+
<noscript>
48+
You need to enable JavaScript to run this app.
49+
</noscript>
50+
<div id="root"></div>
51+
</body>
52+
53+
</html>

src/app.js

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ import {
2929
TIMESTAMP_STRING_KEY,
3030
CALLBACK_KEY,
3131
LNURL_TAG_KEY,
32+
OFFER_PATHS_KEY,
33+
OFFER_PATH_HOPS_KEY,
3234
} from './constants/keys';
3335

36+
import { LNADDRESS, LNURL, BOLT12 } from './constants/invoices';
3437
// Styles
3538
import './assets/styles/main.scss';
3639

@@ -76,7 +79,7 @@ export class App extends PureComponent {
7679
hasError: true,
7780
decodedInvoice: {},
7881
isInvoiceLoaded: false,
79-
error: { message: 'Please enter a valid request or address and try again.'},
82+
error: { message: 'Please enter a valid request or address and try again.' },
8083
}));
8184
}
8285

@@ -90,11 +93,11 @@ export class App extends PureComponent {
9093
hasError: true,
9194
decodedInvoice: {},
9295
isInvoiceLoaded: false,
93-
error: { message: 'Please enter a valid request or address and try again.'},
96+
error: { message: 'Please enter a valid request or address and try again.' },
9497
}));
9598
}
9699

97-
const { isLNURL, data, error, isLNAddress } = parsedInvoiceResponse;
100+
const { isLNURL, data, error, isLNAddress, encodingType } = parsedInvoiceResponse;
98101

99102
// If an error comes back from a nested operation in parsing it must
100103
// propagate back to the end user
@@ -113,7 +116,7 @@ export class App extends PureComponent {
113116
hasError: true,
114117
decodedInvoice: {},
115118
isInvoiceLoaded: false,
116-
error: { message: 'Could not parse/understand this invoice or request. Please try again.'},
119+
error: { message: 'Could not parse/understand this invoice or request. Please try again.' },
117120
}));
118121
}
119122

@@ -151,12 +154,13 @@ export class App extends PureComponent {
151154
isLNURL,
152155
error: {},
153156
isLNAddress,
157+
encodingType,
154158
hasError: false,
155159
isInvoiceLoaded: true,
156160
decodedInvoice: response,
157161
}));
158162
}
159-
} catch(error) {
163+
} catch (error) {
160164
this.setState(() => ({
161165
error: error,
162166
hasError: true,
@@ -302,11 +306,11 @@ export class App extends PureComponent {
302306
{(
303307
!Array.isArray(tag.data) && (
304308
(typeof tag.data !== 'string') || (typeof tag.data !== 'number'))
305-
) && (
306-
<>
307-
{Object.keys(tag.data).map((label) => renderNestedItem(label, tag.data[label]))}
308-
</>
309-
)}
309+
) && (
310+
<>
311+
{Object.keys(tag.data).map((label) => renderNestedItem(label, tag.data[label]))}
312+
</>
313+
)}
310314
</div>
311315
</div>
312316
);
@@ -364,6 +368,84 @@ export class App extends PureComponent {
364368
</div>
365369
);
366370

371+
renderOfferDetails = () => {
372+
const { decodedInvoice, isInvoiceLoaded } = this.state;
373+
const invoiceContainerClassnames = cx(
374+
'invoice',
375+
{ 'invoice--opened': isInvoiceLoaded },
376+
);
377+
378+
const renderInnerObject = (object, innerKey) => {
379+
return <div key={innerKey} className='invoice__item invoice__item--nested'>
380+
<div className='invoice__item-title'>
381+
{formatDetailsKey(innerKey)}
382+
</div>
383+
<div className='invoice__item-value'>
384+
{object[innerKey]}
385+
</div>
386+
</div>
387+
};
388+
389+
const renderHops = (hops) => {
390+
const child = hops.map((hop, index) => {
391+
const hop_child = Object.keys(hop).map((key) => (renderInnerObject(hop, key)));
392+
393+
return <div key={OFFER_PATH_HOPS_KEY + index}>
394+
<div className='invoice__item invoice__item-value'>
395+
<p> {formatDetailsKey(OFFER_PATH_HOPS_KEY) + ` ${index}`} </p>
396+
</div>
397+
{hop_child}
398+
</div>
399+
});
400+
401+
return <div key={OFFER_PATH_HOPS_KEY}>
402+
{child}
403+
</div>
404+
}
405+
406+
const renderPaths = (paths) => {
407+
const child = paths.map((path, index) => {
408+
const path_child = Object.keys(path).map((key) => {
409+
switch (key) {
410+
case OFFER_PATH_HOPS_KEY:
411+
return renderHops(path[key]);
412+
default:
413+
return renderInnerObject(path, key);
414+
}
415+
})
416+
417+
return <div key={path.offer_path_introduction_node}>
418+
<div className='invoice__item invoice__item-value'>
419+
<p> {formatDetailsKey(OFFER_PATHS_KEY) + ` ${index}`} </p>
420+
</div>
421+
{path_child}
422+
</div>
423+
});
424+
425+
return <div key={OFFER_PATHS_KEY}>
426+
{child}
427+
</div>
428+
}
429+
430+
const invoiceDetails = Object.keys(decodedInvoice)
431+
.map((key) => {
432+
switch (key) {
433+
case OFFER_PATHS_KEY:
434+
return renderPaths(decodedInvoice[key]);
435+
436+
default:
437+
return this.renderInvoiceItem(key);
438+
}
439+
});
440+
441+
442+
return !isInvoiceLoaded ? null : (
443+
<div className={invoiceContainerClassnames}>
444+
{invoiceDetails}
445+
</div>
446+
);
447+
}
448+
367449
renderLNURLDetails = () => {
368450
const { decodedInvoice, isInvoiceLoaded } = this.state;
369451
const invoiceContainerClassnames = cx(
@@ -377,11 +459,11 @@ export class App extends PureComponent {
377459
<div className={invoiceContainerClassnames}>
378460
{Object.keys(requestContents).map((key) => {
379461
let text = decodedInvoice[key];
380-
462+
381463
if (typeof decodedInvoice[key] === 'object') {
382464
return <></>;
383465
}
384-
466+
385467
if (key === 'status') {
386468
return <></>
387469
}
@@ -554,7 +636,7 @@ export class App extends PureComponent {
554636
const { isQRCodeOpened, isInvoiceLoaded } = this.state;
555637

556638
const styleQRWrapper = cx({
557-
'qrcode' : true,
639+
'qrcode': true,
558640
'qrcode--opened': isQRCodeOpened,
559641
});
560642
const styleQRContainer = cx(
@@ -621,7 +703,7 @@ export class App extends PureComponent {
621703
}
622704

623705
render() {
624-
const { isLNURL, isInvoiceLoaded, hasError } = this.state;
706+
const { isInvoiceLoaded, encodingType, hasError } = this.state;
625707

626708
const appClasses = cx(
627709
'app',
@@ -651,7 +733,10 @@ export class App extends PureComponent {
651733
</div>
652734
</div>
653735
<div className={appColumnClasses}>
654-
{isLNURL ? this.renderLNURLDetails() : this.renderInvoiceDetails()}
736+
{((encodingType === LNADDRESS || encodingType === LNURL) && this.renderLNURLDetails()) ||
737+
(encodingType === BOLT12 && this.renderOfferDetails())
738+
|| this.renderInvoiceDetails()
739+
}
655740
{this.renderErrorDetails()}
656741
</div>
657742
</div>

src/constants/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// App-Wide Constants
22
export const APP_NAME = 'Lightning Decoder';
33
export const APP_TAGLINE = 'Decode Lightning Network Requests';
4-
export const APP_SUBTAGLINE = 'BOLT11, LNURL, and Lightning Address'
4+
export const APP_SUBTAGLINE = 'BOLT11, BOLT12, LNURL, and Lightning Address'
55
export const APP_INPUT_PLACEHOLDER = 'Enter Invoice';
66
export const APP_GITHUB = 'https://github.com/andrerfneves/lightning-decoder';

src/constants/invoices.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Invoice Encoding Types
2+
export const LNADDRESS = 'LNDADDRESS';
3+
export const LNURL = 'LNURL';
4+
export const BOLT11 = 'BOLT11';
5+
export const BOLT12 = 'BOLT12';
6+
7+
// Invoice Prefixes
8+
export const LIGHTNING_SCHEME = 'lightning';
9+
export const BOLT11_SCHEME_MAINNET = 'lnbc';
10+
export const BOLT11_SCHEME_TESTNET = 'lntb';
11+
export const LNURL_SCHEME = 'lnurl';
12+
export const BOLT12_SCHEME = 'lno';

src/constants/keys.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,18 @@ export const MAX_SENDABLE_KEY = 'maxSendable';
4545
export const MAX_WITHDRAWABLE_KEY = 'minWithdrawable';
4646
export const MIN_WITHDRAWABLE_KEY = 'minWithdrawable';
4747
export const LN_ADDRESS_DOMAIN_KEY = 'domain';
48-
export const LN_ADDRESS_USERNAME_KEY = 'username';
48+
export const LN_ADDRESS_USERNAME_KEY = 'username'
49+
// Offer Keys
50+
export const OFFER_ID_KEY = 'offer_id';
51+
export const OFFER_SIGNING_PUBKEY_KEY = 'offer_signing_pubkey';
52+
export const OFFER_AMOUNT_MSATS_KEY = 'offer_amount_msats';
53+
export const OFFER_AMOUNT_CURRENCY_KEY = 'offer_amount_currency';
54+
export const OFFER_DESCRIPTION_KEY = 'offer_description';
55+
export const OFFER_PATHS_KEY = 'offer_paths';
56+
// Offer Path Keys
57+
export const OFFER_PATH_INTRODUCTION_NODE_KEY = 'offer_path_introduction_node';
58+
export const OFFER_PATH_BLINDING_POINT_KEY = 'offer_path_blinding_point';
59+
export const OFFER_PATH_HOPS_KEY = 'offer_path_hops';
60+
// Offer Path Hop Keys
61+
export const OFFER_HOP_PUBKEY_KEY = 'offer_hop_pubkey';
62+
export const OFFER_HOP_ENCRYPTED_PAYLOAD_KEY = 'offer_hop_encrypted_payload';

0 commit comments

Comments
 (0)