Skip to content

Commit 218ce94

Browse files
committed
Merge branch 'Release-0.0.7' of github.com:ElementsProject/cln-application into evansmj/sats-flow
2 parents dc95773 + 4713afd commit 218ce94

26 files changed

+303
-225
lines changed

.github/docs/Contributing.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ Development
1212

1313
Releasing and packaging on Github
1414
----------------------------------
15-
* Go to repo's `Releases` page and draft a new release.
15+
* Merge the `Release-<x.y.z>` branch into `main` branch.
16+
* Set VERSION env `VERSION=v<x.y.z>`.
17+
* Tag the commit with `git tag -a -s ${VERSION} -m ${VERSION} && git push --tags`.
18+
* Go to repo's `Releases` page and draft a new release from above tag.
1619
* Prepare release notes with the help of milestone, issues and PRs. Add them on the release page.
1720
* Signing the release:
18-
** `VERSION=vx.y.z`
1921
** `mkdir -p ./release & git archive --format zip --output ./release/cln-application-${VERSION}.zip main`
2022
** `cd release`
2123
** `sha256sum cln* > SHA256SUMS`

README.md

+10-7
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,24 @@
5252
- APP_CORE_LIGHTNING_IP: IP address of this application (cln-application) container (required)
5353
- APP_CORE_LIGHTNING_PORT: Port on which this application should be served (required)
5454
- APP_CORE_LIGHTNING_DAEMON_IP: IP address of Core lightning node container (required)
55+
- COMMANDO_CONFIG: Full Path including file name for commando auth with PUBKEY & RUNE (required)
5556
- APP_CORE_LIGHTNING_WEBSOCKET_PORT: Core lightning's websocket port (required; from cln's config.json; starting with `bind-addr=ws:`)
5657
- APP_CONFIG_DIR: Path for cln-application's configuration file (required; config.json)
57-
- COMMANDO_CONFIG: Full Path including file name for commando auth with PUBKEY & RUNE (required)
58-
- APP_BITCOIN_NODE_IP: IP address of bitcoin node container (required)
59-
- APP_CORE_LIGHTNING_BITCOIN_NETWORK: Bitcoin network type (optional; for entrypoint.sh; valid values: bitcoin/signet/testnet/regtest)
60-
- APP_CORE_LIGHTNING_REST_PORT: c-lightning-REST server port (optional; for connect wallet screen)
61-
- APP_CORE_LIGHTNING_REST_CERT_DIR: Path for c-lightning-REST certificates (optional; for connect wallet screen)
62-
- APP_CORE_LIGHTNING_DAEMON_GRPC_PORT: Core lightning's GRPC port (optional; future proofing for connect wallet screen)
63-
- APP_CORE_LIGHTNING_REST_HIDDEN_SERVICE: REST hidden service url (optional; for connect wallet screen; Used for Tor Domain also)
6458
- DEVICE_DOMAIN_NAME: Device name/IP for lnmessage connect url feature (optional; for connect wallet screen)
6559
- LOCAL_HOST: Device url for connect url links (optional; for connect wallet screen)
6660
- APP_MODE: Mode for logging and other settings (valid values: production/development/testing, default: production)
6761
- SINGLE_SIGN_ON: Flag to bypass application level authentication (valid values: true/false, default: false)
6862
- APP_PROTOCOL: Protocol on which the application will be served (valid values: http/https, default: http)
6963
- CORE_LIGHTNING_PATH: Path for core lightning (optional; required for entrypoint.sh)
64+
- APP_BITCOIN_NODE_IP: IP address of bitcoin node container (required)
65+
- APP_CORE_LIGHTNING_BITCOIN_NETWORK: Bitcoin network type (optional; for entrypoint.sh; valid values: bitcoin/signet/testnet/regtest)
66+
- APP_CONNECT: Choose how to connect to CLN (valid values: COMMANDO/REST/GRPC, default: commando)
67+
- APP_CORE_LIGHTNING_REST_PROTOCOL: Protocol on which cln-rest is served (valid values: http/https, default: https)
68+
- APP_CORE_LIGHTNING_REST_PORT: c-lightning-REST server port (optional; for connect wallet screen)
69+
- APP_CORE_LIGHTNING_REST_CERT_DIR: Path for c-lightning-REST certificates (optional; for connect wallet screen)
70+
- APP_CORE_LIGHTNING_REST_HIDDEN_SERVICE: REST hidden service url (optional; for connect wallet screen; Used for Tor Domain also)
71+
- APP_CORE_LIGHTNING_DAEMON_GRPC_PROTOCOL: Core lightning's GRPC protocol (valid values: http/https, default: https)
72+
- APP_CORE_LIGHTNING_DAEMON_GRPC_PORT: Core lightning's GRPC port (optional; future proofing for connect wallet screen)
7073
```
7174
7275
Set these variables either via terminal OR by env.sh script OR by explicitly loading variables from .env files.

apps/backend/dist/controllers/lightning.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import handleError from '../shared/error-handler.js';
2-
import { LNMessage } from '../service/lightning.service.js';
2+
import { CLNService } from '../service/lightning.service.js';
33
import { logger } from '../shared/logger.js';
4-
const lnMessage = LNMessage;
4+
import { AppConnect, APP_CONSTANTS } from '../shared/consts.js';
5+
const clnService = CLNService;
56
class LightningController {
67
callMethod(req, res, next) {
78
try {
89
logger.info('Calling method: ' + req.body.method);
9-
lnMessage
10+
clnService
1011
.call(req.body.method, req.body.params)
1112
.then((commandRes) => {
1213
logger.info('Controller received response for ' +
1314
req.body.method +
1415
': ' +
1516
JSON.stringify(commandRes));
16-
if (req.body.method && req.body.method === 'listpeers') {
17+
if (APP_CONSTANTS.APP_CONNECT == AppConnect.COMMANDO &&
18+
req.body.method &&
19+
req.body.method === 'listpeers') {
1720
// Filter out ln message pubkey from peers list
18-
const lnmPubkey = lnMessage.getLNMsgPubkey();
21+
const lnmPubkey = clnService.getLNMsgPubkey();
1922
commandRes.peers = commandRes.peers.filter((peer) => peer.id !== lnmPubkey);
2023
res.status(200).json(commandRes);
2124
}

apps/backend/dist/controllers/shared.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import handleError from '../shared/error-handler.js';
66
import { APIError } from '../models/errors.js';
77
import { setSharedApplicationConfig, overrideSettingsWithEnvVariables } from '../shared/utils.js';
88
import { sep } from 'path';
9-
import { LNMessage } from '../service/lightning.service.js';
9+
import { CLNService } from '../service/lightning.service.js';
1010
class SharedController {
1111
getApplicationSettings(req, res, next) {
1212
try {
@@ -73,7 +73,7 @@ class SharedController {
7373
.replace('-----BEGIN CERTIFICATE-----', '')
7474
.replace('-----END CERTIFICATE-----', '');
7575
}
76-
LNMessage.refreshEnvVariables();
76+
CLNService.refreshEnvVariables();
7777
const CONNECT_WALLET_SETTINGS = {
7878
LOCAL_HOST: process.env.LOCAL_HOST || '',
7979
DEVICE_DOMAIN_NAME: process.env.DEVICE_DOMAIN_NAME || '',
@@ -106,9 +106,9 @@ class SharedController {
106106
return axios
107107
.get(FIAT_RATE_API + FIAT_VENUE + '/pairs/XBT/' + req.params.fiatCurrency)
108108
.then((response) => {
109-
logger.info('Fiat Response: ' + JSON.stringify(response.data));
110-
if (response.data.rate) {
111-
return res.status(200).json({ venue: FIAT_VENUE, rate: response.data.rate });
109+
logger.info('Fiat Response: ' + JSON.stringify(response?.data));
110+
if (response.data?.rate) {
111+
return res.status(200).json({ venue: FIAT_VENUE, rate: response.data?.rate });
112112
}
113113
else {
114114
return handleError(new APIError('Price Not Found', 'Price Not Found'), req, res, next);
@@ -125,7 +125,7 @@ class SharedController {
125125
async saveInvoiceRune(req, res, next) {
126126
try {
127127
logger.info('Saving Invoice Rune');
128-
const showRunes = await LNMessage.call('showrunes', []);
128+
const showRunes = await CLNService.call('showrunes', []);
129129
const invoiceRune = showRunes.runes.find(rune => rune.restrictions.some(restriction => restriction.alternatives.some(alternative => alternative.value === 'invoice')) &&
130130
rune.restrictions.some(restriction => restriction.alternatives.some(alternative => alternative.value === 'listinvoices')));
131131
if (invoiceRune && fs.existsSync(APP_CONSTANTS.COMMANDO_ENV_LOCATION)) {
+66-30
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
import * as fs from 'fs';
22
import * as crypto from 'crypto';
3+
import axios from 'axios';
34
import Lnmessage from 'lnmessage';
45
import { LightningError } from '../models/errors.js';
5-
import { HttpStatusCode, APP_CONSTANTS, LN_MESSAGE_CONFIG } from '../shared/consts.js';
6+
import { HttpStatusCode, APP_CONSTANTS, AppConnect, LN_MESSAGE_CONFIG, REST_CONFIG, GRPC_CONFIG, } from '../shared/consts.js';
67
import { logger } from '../shared/logger.js';
78
import { readFileSync } from 'fs';
89
export class LightningService {
9-
lnMessage = null;
10+
clnService = null;
1011
constructor() {
1112
try {
1213
logger.info('Getting Commando Rune');
1314
if (fs.existsSync(APP_CONSTANTS.COMMANDO_ENV_LOCATION)) {
1415
this.refreshEnvVariables();
15-
logger.info('lnMessage connecting with config: ' + JSON.stringify(LN_MESSAGE_CONFIG));
16-
this.lnMessage = new Lnmessage(LN_MESSAGE_CONFIG);
17-
this.lnMessage.connect();
16+
switch (APP_CONSTANTS.APP_CONNECT) {
17+
case AppConnect.REST:
18+
logger.info('REST connecting with config: ' + JSON.stringify(REST_CONFIG));
19+
break;
20+
case AppConnect.GRPC:
21+
logger.info('GRPC connecting with config: ' + JSON.stringify(GRPC_CONFIG));
22+
break;
23+
default:
24+
logger.info('lnMessage connecting with config: ' + JSON.stringify(LN_MESSAGE_CONFIG));
25+
this.clnService = new Lnmessage(LN_MESSAGE_CONFIG);
26+
this.clnService.connect();
27+
break;
28+
}
1829
}
1930
}
2031
catch (error) {
@@ -23,32 +34,57 @@ export class LightningService {
2334
}
2435
}
2536
getLNMsgPubkey = () => {
26-
return this.lnMessage.publicKey;
37+
return this.clnService.publicKey;
2738
};
2839
call = async (method, methodParams) => {
29-
return this.lnMessage
30-
.commando({
31-
method: method,
32-
params: methodParams,
33-
rune: APP_CONSTANTS.COMMANDO_RUNE,
34-
reqId: crypto.randomBytes(8).toString('hex'),
35-
reqIdPrefix: 'clnapp',
36-
})
37-
.then((commandRes) => {
38-
logger.info('Command Res for ' + method + ': ' + JSON.stringify(commandRes));
39-
return Promise.resolve(commandRes);
40-
})
41-
.catch((err) => {
42-
logger.error('Lightning error from ' + method + ' command');
43-
if (typeof err === 'string') {
44-
logger.error(err);
45-
throw new LightningError(err, err, HttpStatusCode.LIGHTNING_SERVER, 'Core Lightning API Error');
46-
}
47-
else {
48-
logger.error(JSON.stringify(err));
49-
throw new LightningError(err.message || err.error, err.error || err.message, HttpStatusCode.LIGHTNING_SERVER, 'Core Lightning API Error');
50-
}
51-
});
40+
switch (APP_CONSTANTS.APP_CONNECT) {
41+
case AppConnect.REST:
42+
return axios
43+
.post(REST_CONFIG.url + '/v1/' + method, methodParams, {
44+
headers: { rune: APP_CONSTANTS.COMMANDO_RUNE },
45+
})
46+
.then((commandRes) => {
47+
logger.info('REST response for ' + method + ': ' + JSON.stringify(commandRes.data));
48+
return Promise.resolve(commandRes.data);
49+
})
50+
.catch((err) => {
51+
logger.error('REST lightning error from ' + method + ' command');
52+
if (typeof err === 'string') {
53+
logger.error(err);
54+
throw new LightningError(err, err, HttpStatusCode.LIGHTNING_SERVER, 'Core Lightning API Error');
55+
}
56+
else {
57+
logger.error(JSON.stringify(err));
58+
throw new LightningError(err.response?.data?.message || err.message || err.error, err.response?.data || err.error || err.message, HttpStatusCode.LIGHTNING_SERVER, 'Core Lightning API Error');
59+
}
60+
});
61+
case AppConnect.GRPC:
62+
break;
63+
default:
64+
return this.clnService
65+
.commando({
66+
method: method,
67+
params: methodParams,
68+
rune: APP_CONSTANTS.COMMANDO_RUNE,
69+
reqId: crypto.randomBytes(8).toString('hex'),
70+
reqIdPrefix: 'clnapp',
71+
})
72+
.then((commandRes) => {
73+
logger.info('Commando response for ' + method + ': ' + JSON.stringify(commandRes));
74+
return Promise.resolve(commandRes);
75+
})
76+
.catch((err) => {
77+
logger.error('Commando lightning error from ' + method + ' command');
78+
if (typeof err === 'string') {
79+
logger.error(err);
80+
throw new LightningError(err, err, HttpStatusCode.LIGHTNING_SERVER, 'Core Lightning API Error');
81+
}
82+
else {
83+
logger.error(JSON.stringify(err));
84+
throw new LightningError(err.message || err.error, err.error || err.message, HttpStatusCode.LIGHTNING_SERVER, 'Core Lightning API Error');
85+
}
86+
});
87+
}
5288
};
5389
refreshEnvVariables() {
5490
const envVars = this.parseEnvFile(APP_CONSTANTS.COMMANDO_ENV_LOCATION);
@@ -78,4 +114,4 @@ export class LightningService {
78114
}
79115
}
80116
}
81-
export const LNMessage = new LightningService();
117+
export const CLNService = new LightningService();

apps/backend/dist/shared/consts.js

+34-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ export var Environment;
66
Environment["TESTING"] = "testing";
77
Environment["DEVELOPMENT"] = "development";
88
})(Environment || (Environment = {}));
9+
export var AppConnect;
10+
(function (AppConnect) {
11+
AppConnect["COMMANDO"] = "COMMANDO";
12+
AppConnect["REST"] = "REST";
13+
AppConnect["GRPC"] = "GRPC";
14+
})(AppConnect || (AppConnect = {}));
915
export var HttpStatusCode;
1016
(function (HttpStatusCode) {
1117
HttpStatusCode[HttpStatusCode["GET_OK"] = 200] = "GET_OK";
@@ -25,13 +31,18 @@ export var HttpStatusCode;
2531
export const SECRET_KEY = crypto.randomBytes(64).toString('hex');
2632
export const APP_CONSTANTS = {
2733
COMMANDO_RUNE: '',
28-
APP_CORE_LIGHTNING_DAEMON_IP: process.env.APP_CORE_LIGHTNING_DAEMON_IP || 'localhost',
29-
LIGHTNING_WS_PORT: +(process.env.APP_CORE_LIGHTNING_WEBSOCKET_PORT || 5001),
30-
APP_MODE: process.env.APP_MODE || Environment.PRODUCTION,
3134
COMMANDO_ENV_LOCATION: process.env.COMMANDO_CONFIG || './.commando-env',
35+
APP_MODE: process.env.APP_MODE || Environment.PRODUCTION,
3236
MACAROON_PATH: join(process.env.APP_CORE_LIGHTNING_REST_CERT_DIR || '.', 'access.macaroon'),
3337
LOG_FILE_LOCATION: join(process.env.APP_CONFIG_DIR || '.', 'application-cln.log'),
3438
CONFIG_LOCATION: join(process.env.APP_CONFIG_DIR || '.', 'config.json'),
39+
APP_CONNECT: process.env.APP_CONNECT || AppConnect.COMMANDO,
40+
APP_CORE_LIGHTNING_DAEMON_IP: process.env.APP_CORE_LIGHTNING_DAEMON_IP || 'localhost',
41+
LIGHTNING_WS_PORT: +(process.env.APP_CORE_LIGHTNING_WEBSOCKET_PORT || 5001),
42+
LIGHTNING_REST_PROTOCOL: process.env.APP_CORE_LIGHTNING_REST_PROTOCOL || 'https',
43+
LIGHTNING_REST_PORT: +(process.env.APP_CORE_LIGHTNING_REST_PORT || 3010),
44+
LIGHTNING_GRPC_PROTOCOL: process.env.APP_CORE_LIGHTNING_DAEMON_GRPC_PROTOCOL || 'https',
45+
LIGHTNING_GRPC_PORT: +(process.env.APP_CORE_LIGHTNING_DAEMON_GRPC_PORT || 9736),
3546
};
3647
export const DEFAULT_CONFIG = {
3748
unit: 'SATS',
@@ -54,6 +65,26 @@ export const LN_MESSAGE_CONFIG = {
5465
error: console.error,
5566
},
5667
};
68+
export const GRPC_CONFIG = {
69+
protocol: APP_CONSTANTS.LIGHTNING_GRPC_PROTOCOL,
70+
ip: APP_CONSTANTS.APP_CORE_LIGHTNING_DAEMON_IP,
71+
port: APP_CONSTANTS.LIGHTNING_GRPC_PORT,
72+
url: APP_CONSTANTS.LIGHTNING_GRPC_PROTOCOL +
73+
'://' +
74+
APP_CONSTANTS.APP_CORE_LIGHTNING_DAEMON_IP +
75+
':' +
76+
APP_CONSTANTS.LIGHTNING_GRPC_PORT,
77+
};
78+
export const REST_CONFIG = {
79+
protocol: APP_CONSTANTS.LIGHTNING_REST_PROTOCOL,
80+
ip: APP_CONSTANTS.APP_CORE_LIGHTNING_DAEMON_IP,
81+
port: APP_CONSTANTS.LIGHTNING_REST_PORT,
82+
url: APP_CONSTANTS.LIGHTNING_REST_PROTOCOL +
83+
'://' +
84+
APP_CONSTANTS.APP_CORE_LIGHTNING_DAEMON_IP +
85+
':' +
86+
APP_CONSTANTS.LIGHTNING_REST_PORT,
87+
};
5788
export const API_VERSION = '/v1';
5889
export const FIAT_RATE_API = 'https://green-bitcoin-mainnet.blockstream.com/prices/v0/venues/';
5990
export const FIAT_VENUES = {

apps/backend/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cln-application-backend",
3-
"version": "0.0.6",
3+
"version": "0.0.7",
44
"description": "Core lightning application backend",
55
"private": true,
66
"license": "MIT",
@@ -12,6 +12,7 @@
1212
"watch": "tsc --project tsconfig.json --watch & prettier --write source/"
1313
},
1414
"dependencies": {
15+
"axios": "^1.7.7",
1516
"cookie-parser": "^1.4.6",
1617
"cors": "^2.8.5",
1718
"csurf": "^1.11.0",

apps/backend/source/controllers/lightning.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { Request, Response, NextFunction } from 'express';
22
import handleError from '../shared/error-handler.js';
3-
import { LNMessage, LightningService } from '../service/lightning.service.js';
3+
import { CLNService, LightningService } from '../service/lightning.service.js';
44
import { logger } from '../shared/logger.js';
5-
import { LightningError } from '../models/errors.js';
6-
import { HttpStatusCode } from '../shared/consts.js';
5+
import { AppConnect, APP_CONSTANTS } from '../shared/consts.js';
76

8-
const lnMessage: LightningService = LNMessage;
7+
const clnService: LightningService = CLNService;
98

109
class LightningController {
1110
callMethod(req: Request, res: Response, next: NextFunction) {
1211
try {
1312
logger.info('Calling method: ' + req.body.method);
14-
lnMessage
13+
clnService
1514
.call(req.body.method, req.body.params)
1615
.then((commandRes: any) => {
1716
logger.info(
@@ -20,9 +19,13 @@ class LightningController {
2019
': ' +
2120
JSON.stringify(commandRes),
2221
);
23-
if (req.body.method && req.body.method === 'listpeers') {
22+
if (
23+
APP_CONSTANTS.APP_CONNECT == AppConnect.COMMANDO &&
24+
req.body.method &&
25+
req.body.method === 'listpeers'
26+
) {
2427
// Filter out ln message pubkey from peers list
25-
const lnmPubkey = lnMessage.getLNMsgPubkey();
28+
const lnmPubkey = clnService.getLNMsgPubkey();
2629
commandRes.peers = commandRes.peers.filter((peer: any) => peer.id !== lnmPubkey);
2730
res.status(200).json(commandRes);
2831
} else {

0 commit comments

Comments
 (0)