diff --git a/README.md b/README.md
index 28172b1..39560ce 100644
--- a/README.md
+++ b/README.md
@@ -10,79 +10,85 @@ https://t.me/lndboss
```shell
# See an accounting formatted list of various types of transactions
-bos accounting "category"
+accounting "category"
# See total balance, including pending funds, excluding future commit fees
-bos balance
+balance
# Call ln-service raw APIs
-bos call "method"
+call "method"
# Get the number of days the node cert remains valid
-bos cert-validity-days
+cert-validity-days
# Receive on-chain funds via a regular address
-bos chain-deposit
+chain-deposit
# See the current fee estimates confirmation targets
-bos chainfees
+chainfees
# Show chain fees paid
-bos chart-chain-fees
+chart-chain-fees
# Show routing fees earned
-bos chart-fees-earned
+chart-fees-earned
# Show routing fees paid
-bos chart-fees-paid
+chart-fees-paid
# Show a chart of payments received
-bos chart-payments-received
+chart-payments-received
# See details on how closed channels resolved on-chain
-bos closed
+closed
+
+# Create a group channel request
+create-group-channel
# View outbound fee rates and update outbound fee rates to peers
-bos fees
+fees
# Query the node to find something like a payment, channel or node
-bos find "query"
+find "query"
# Output a summarized version of peers forwarded towards
-bos forwards
+forwards
# Look up the channels and fee rates of a node by its public key
-bos graph "pubkey"
+graph "pubkey"
+
+# Joins a group channel request
+create-group-channel "invite_code"
# Collection of lnurl features
-bos lnurl "function"
+lnurl "function"
# Batch open channels, zero conf supported
-bos open "pubkeys"
+open "pubkeys"
# Pay a payment request (invoice), probing first
-bos pay "payment_request"
+pay "payment_request"
# Show channel-connected peers
-bos peers
+peers
# Output the price of BTC
-bos price
+price
# Test if funds can be sent to a destination
-bos probe "payment_request/public_key"
+probe "payment_request/public_key"
# Rebalance funds between peers
-bos rebalance
+rebalance
# Reconnect to offline peers
-bos reconnect
+reconnect
# Send funds using keysend or lnurl/lightning address and an optional message to a node
-bos send
+send
# Tags can be used in other commands via tag and avoid options
-bos tags
+tags
```
### LndBoss is now available for one click install in AppStores of:
diff --git a/SERVER.md b/SERVER.md
index 3eaaefe..3b186b5 100644
--- a/SERVER.md
+++ b/SERVER.md
@@ -617,6 +617,80 @@ try {
+### CreateGroupChannel
+
+```javascript
+/**
+@PostRequest
+
+@Url
+http://localhost:8055/api/create-group-channel
+
+@Body
+/**
+ {
+ capacity:
+ count:
+ rate:
+ }
+
+@Response
+ {
+ transaction_id:
+ }
+*/
+*/
+
+import { io } from 'socket.io-client';
+
+try {
+ const url = 'http://localhost:8055/api/create-group-channel';
+
+ // Unique connection name for websocket connection.
+ const dateString = Date.now().toString();
+
+ const postBody = {
+ capacity: 1000000,
+ count: 3,
+ rate: 2,
+ };
+
+ // You will need live logs for create-group-channel, you can start a websocket connection with the server and add an event listener.
+ // Websocket url is the same as the server url http://localhost:8055
+ // Messages from the server are passed to client using the dateString passed from above.
+ const socket = io();
+
+ socket.on('connect', () => {
+ console.log('connected');
+ });
+
+ socket.on('disconnect', () => {
+ console.log('disconnected');
+ });
+
+ // Make sure to pass the same dateString from above
+ socket.on(`${dateString}`, data => {
+ console.log(data);
+ });
+
+ socket.on('error', err => {
+ throw err;
+ });
+
+ // End websocket code.
+
+ const config = {
+ headers: { Authorization: `Bearer ${accessToken}` },
+ };
+
+ const response = await axios.post(url, postBody, config);
+} catch (error) {
+ console.error(error);
+}
+```
+
+
+
### Fees
```javascript
@@ -640,7 +714,7 @@ http://localhost:8055/api/fees
*/
try {
- const url = 'http://localhost:8055/api/open';
+ const url = 'http://localhost:8055/api/fees';
const postBody = {
fee_rate: ['100'],
@@ -833,6 +907,76 @@ try {
+### JoinGroupChannel
+
+```javascript
+/**
+@PostRequest
+
+@Url
+http://localhost:8055/api/join-group-channel
+
+@Body
+/**
+ {
+ code:
+ max_rate:
+ }
+@Response
+ {
+ transaction_id:
+ }
+*/
+
+import { io } from 'socket.io-client';
+
+try {
+ const url = 'http://localhost:8055/api/join-group-channel';
+
+ // Unique connection name for websocket connection.
+ const dateString = Date.now().toString();
+
+ const postBody = {
+ code: 'invite_code',
+ max_rate: 3,
+ };
+
+ // You will need live logs for join-group-channel, you can start a websocket connection with the server and add an event listener.
+ // Websocket url is the same as the server url http://localhost:8055
+ // Messages from the server are passed to client using the dateString passed from above.
+ const socket = io();
+
+ socket.on('connect', () => {
+ console.log('connected');
+ });
+
+ socket.on('disconnect', () => {
+ console.log('disconnected');
+ });
+
+ // Make sure to pass the same dateString from above
+ socket.on(`${dateString}`, data => {
+ console.log(data);
+ });
+
+ socket.on('error', err => {
+ throw err;
+ });
+
+ // End websocket code.
+
+ const config = {
+ headers: { Authorization: `Bearer ${accessToken}` },
+ };
+
+ const response = await axios.post(url, postBody, config);
+} catch (error) {
+ console.error(error);
+}
+```
+
+
+
### Lnurl
```javascript
diff --git a/src/client/commands.ts b/src/client/commands.ts
index 34f57a3..f2ea00d 100644
--- a/src/client/commands.ts
+++ b/src/client/commands.ts
@@ -160,6 +160,17 @@ const commands: Commands = [
limit: 'Limit',
},
},
+ {
+ name: 'Create Group Channel (Experimental)',
+ value: 'CreateGroupChannel',
+ description: 'Coordinate balanced channels group',
+ longDescription: 'Create a group channel invite code, other nodes can join the group using join-group-channel',
+ flags: {
+ capacity: 'Capacity',
+ fee_rate: 'FeeRate',
+ size: 'Size',
+ },
+ },
{
name: 'Fees',
value: 'Fees',
@@ -221,6 +232,18 @@ const commands: Commands = [
sort: 'Sort',
},
},
+ {
+ name: 'Join Group Channel (Experimental)',
+ value: 'JoinGroupChannel',
+ description: 'Join a balanced channels group',
+ longDescription: 'Another node should have run create-group-channel to create group',
+ args: {
+ code: 'Code',
+ },
+ flags: {
+ max_fee_rate: 'MaxFeeRate',
+ },
+ },
{
name: 'Lnurl',
value: 'Lnurl',
diff --git a/src/client/output/CreateGroupChannelOutput.tsx b/src/client/output/CreateGroupChannelOutput.tsx
new file mode 100644
index 0000000..c02f9f3
--- /dev/null
+++ b/src/client/output/CreateGroupChannelOutput.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+
+/*
+ Renders the output of the create-group-channel command.
+*/
+
+type Args = {
+ data: string | undefined;
+};
+const CreateGroupChannelOutput = ({ data }: Args) => {
+ return (
+
+ );
+};
+
+export default CreateGroupChannelOutput;
+
+const styles = {
+ div: {
+ width: '1100px',
+ },
+};
diff --git a/src/client/output/JoinGroupChannelOutput.tsx b/src/client/output/JoinGroupChannelOutput.tsx
new file mode 100644
index 0000000..c4b232c
--- /dev/null
+++ b/src/client/output/JoinGroupChannelOutput.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+
+/*
+ Renders the output of the join-group-channel command.
+*/
+
+type Args = {
+ data: string | undefined;
+};
+const JoinGroupChannelOutput = ({ data }: Args) => {
+ return (
+
+ );
+};
+
+export default JoinGroupChannelOutput;
+
+const styles = {
+ div: {
+ width: '1100px',
+ },
+};
diff --git a/src/client/output/index.ts b/src/client/output/index.ts
index a076da2..392dbe3 100644
--- a/src/client/output/index.ts
+++ b/src/client/output/index.ts
@@ -8,10 +8,15 @@ import ChartFeesEarnedOutput from './ChartFeesEarnedOutput';
import ChartFeesPaidOutput from './ChartFeesPaidOutput';
import ChartPaymentsReceivedOutput from './ChartPaymentsReceivedOutput';
import ClosedOutput from './ClosedOutput';
+import CreateGroupChannelOutput from './CreateGroupChannelOutput';
import FeesOutput from './FeesOutput';
import FindOutput from './FindOutput';
import ForwardsOutput from './ForwardsOutput';
+import JoinGroupChannelOutput from './JoinGroupChannelOutput';
import OpenOutput from './OpenOutput';
+import PeersOutput from './PeersOutput';
+import PriceOutput from './PriceOutput';
+import ReconnectOutput from './ReconnectOutput';
import TagsOutput from './TagsOutput';
export {
@@ -25,9 +30,14 @@ export {
ChartFeesPaidOutput,
ChartPaymentsReceivedOutput,
ClosedOutput,
+ CreateGroupChannelOutput,
FeesOutput,
FindOutput,
ForwardsOutput,
+ JoinGroupChannelOutput,
OpenOutput,
+ PeersOutput,
+ PriceOutput,
+ ReconnectOutput,
TagsOutput,
};
diff --git a/src/client/pages/commands/Accounting.tsx b/src/client/pages/commands/Accounting.tsx
index e8a1eb2..9c5b234 100644
--- a/src/client/pages/commands/Accounting.tsx
+++ b/src/client/pages/commands/Accounting.tsx
@@ -20,42 +20,6 @@ const AccountingCommand = commands.find(n => n.value === 'Accounting');
Passes query parameters to the accounting results page
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '350px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- h4: {
- marginTop: '0px',
- },
- inputLabel: {
- fontWeight: 'bold',
- color: 'black',
- },
- select: {
- width: '300px',
- },
- switch: {
- width: '100px',
- },
-};
-
const Accounting = () => {
const [node, setNode] = useState('');
const [isCsv, setIsCsv] = useState(false);
@@ -89,7 +53,7 @@ const Accounting = () => {
setRateProvider(event.target.value);
};
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -196,7 +160,7 @@ const Accounting = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -211,3 +175,39 @@ const Accounting = () => {
};
export default Accounting;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '350px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ inputLabel: {
+ fontWeight: 'bold',
+ color: 'black',
+ },
+ select: {
+ width: '300px',
+ },
+ switch: {
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/Balance.tsx b/src/client/pages/commands/Balance.tsx
index 91b5b91..d74a228 100644
--- a/src/client/pages/commands/Balance.tsx
+++ b/src/client/pages/commands/Balance.tsx
@@ -21,23 +21,6 @@ import { axiosGet } from '~client/utils/axios';
const BalanceCommand = commands.find(n => n.value === 'Balance');
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '300px',
- },
- h4: {
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
-};
-
const Balance = () => {
const [above, setAbove] = useState('');
const [below, setBelow] = useState('');
@@ -48,7 +31,7 @@ const Balance = () => {
const [data, setData] = useState(undefined);
const [node, setNode] = useState('');
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -152,7 +135,7 @@ const Balance = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -166,3 +149,20 @@ const Balance = () => {
};
export default Balance;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '300px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/Call.tsx b/src/client/pages/commands/Call.tsx
index 989f9b1..b70cb25 100644
--- a/src/client/pages/commands/Call.tsx
+++ b/src/client/pages/commands/Call.tsx
@@ -9,7 +9,7 @@ import { RawApiList } from '~client/standard_components/lndboss';
import { axiosPostWithAlert } from '~client/utils/axios';
import { rawApi } from '~shared/raw_api';
import { useNotify } from '~client/hooks/useNotify';
-import validateCallCommandArgs from '~client/utils/validate_call_command_args';
+import validateCallCommandArgs from '~client/utils/validations/validate_call_command_args';
const CallCommand = commands.find(n => n.value === 'Call');
const argument = (n: string) => rawApi.calls.find(s => s.method === n);
@@ -19,23 +19,6 @@ const argument = (n: string) => rawApi.calls.find(s => s.method === n);
Passes query parameters to the chart-chain-fees results page
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '500px',
- },
- h4: {
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
-};
-
const Call = () => {
const [node, setNode] = useState('');
const [validationArray, setValidationArray] = useState([{ named: '', value: '', type: '', required: false }]);
@@ -72,7 +55,7 @@ const Call = () => {
setValidationArray(newValidationArray);
};
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -150,7 +133,7 @@ const Call = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -164,3 +147,20 @@ const Call = () => {
};
export default Call;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/CertValidityDays.tsx b/src/client/pages/commands/CertValidityDays.tsx
index 3776460..722d607 100644
--- a/src/client/pages/commands/CertValidityDays.tsx
+++ b/src/client/pages/commands/CertValidityDays.tsx
@@ -15,26 +15,12 @@ const CertValidityDaysCommand = commands.find(n => n.value === 'CertValidityDays
GET call to NestJs process to get cert validity days related information
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '300px',
- },
- h4: {
- marginTop: '0px',
- },
-};
-
const CertValidityDays = () => {
const [node, setNode] = useState('');
const [below, setBelow] = useState('');
const [data, setData] = useState('');
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -76,7 +62,7 @@ const CertValidityDays = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -90,3 +76,17 @@ const CertValidityDays = () => {
};
export default CertValidityDays;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '300px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+};
diff --git a/src/client/pages/commands/ChainDeposit.tsx b/src/client/pages/commands/ChainDeposit.tsx
index f441d11..3f54def 100644
--- a/src/client/pages/commands/ChainDeposit.tsx
+++ b/src/client/pages/commands/ChainDeposit.tsx
@@ -16,27 +16,13 @@ import { axiosGet } from '~client/utils/axios';
const ChainDepositCommand = commands.find(n => n.value === 'ChainDeposit');
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '500px',
- },
- h4: {
- marginTop: '0px',
- },
-};
-
const ChainDeposit = () => {
const [amount, setAmount] = useState('');
const [data, setData] = useState({ address: '', url: '' });
const [node, setNode] = useState('');
const [format, setFormat] = useState('');
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -93,7 +79,7 @@ const ChainDeposit = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -107,3 +93,17 @@ const ChainDeposit = () => {
};
export default ChainDeposit;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+};
diff --git a/src/client/pages/commands/Chainfees.tsx b/src/client/pages/commands/Chainfees.tsx
index 58dc741..5d25c11 100644
--- a/src/client/pages/commands/Chainfees.tsx
+++ b/src/client/pages/commands/Chainfees.tsx
@@ -21,30 +21,13 @@ const ChainfeesCommand = commands.find(n => n.value === 'Chainfees');
GET call to the NestJs process to get chainfees information
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- textField: {
- width: '300px',
- },
- h4: {
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
-};
-
const Chainfees = () => {
const [blocks, setBlocks] = useState('');
const [file, setFile] = useState(false);
const [node, setNode] = useState('');
const [data, setData] = useState(undefined);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -98,7 +81,7 @@ const Chainfees = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -112,3 +95,20 @@ const Chainfees = () => {
};
export default Chainfees;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ textField: {
+ width: '300px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/ChartChainFees.tsx b/src/client/pages/commands/ChartChainFees.tsx
index b9bc870..0c9e721 100644
--- a/src/client/pages/commands/ChartChainFees.tsx
+++ b/src/client/pages/commands/ChartChainFees.tsx
@@ -21,38 +21,6 @@ const ChartChainFeesCommand = commands.find(n => n.value === 'ChartChainFees');
Passes query parameters to the chart-chain-fees results page
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '350px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- h4: {
- marginTop: '0px',
- },
- link: {
- fontSize: '20px',
- margin: '0px',
- cursor: 'pointer',
- color: 'white',
- },
-};
-
const ChartChainFees = () => {
const [formValues, setFormValues] = useState([{ node: '' }]);
const [days, setDays] = useState('');
@@ -152,3 +120,35 @@ const ChartChainFees = () => {
};
export default ChartChainFees;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '350px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ link: {
+ fontSize: '20px',
+ margin: '0px',
+ cursor: 'pointer',
+ color: 'white',
+ },
+};
diff --git a/src/client/pages/commands/ChartFeesEarned.tsx b/src/client/pages/commands/ChartFeesEarned.tsx
index bd11323..756e29b 100644
--- a/src/client/pages/commands/ChartFeesEarned.tsx
+++ b/src/client/pages/commands/ChartFeesEarned.tsx
@@ -22,35 +22,6 @@ const ChartFeesEarnedCommand = commands.find(n => n.value === 'ChartFeesEarned')
Passes query parameters to the chart-fees-earned results page
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '380px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- h4: {
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
-};
-
const ChartFeesEarned = () => {
const [count, setCount] = useState(false);
const [forwarded, setForwarded] = useState(false);
@@ -196,3 +167,32 @@ const ChartFeesEarned = () => {
};
export default ChartFeesEarned;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '380px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/ChartFeesPaid.tsx b/src/client/pages/commands/ChartFeesPaid.tsx
index a475049..f933e7c 100644
--- a/src/client/pages/commands/ChartFeesPaid.tsx
+++ b/src/client/pages/commands/ChartFeesPaid.tsx
@@ -22,35 +22,6 @@ const ChartFeesPaidCommand = commands.find(n => n.value === 'ChartFeesPaid');
Passes query parameters to the chart-fees-paid results page
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '380px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- h4: {
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
-};
-
const ChartFeesPaid = () => {
const [days, setDays] = useState('');
const [formValues, setFormValues] = useState([{ node: '' }]);
@@ -261,3 +232,32 @@ const ChartFeesPaid = () => {
};
export default ChartFeesPaid;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '380px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/ChartPaymentsReceived.tsx b/src/client/pages/commands/ChartPaymentsReceived.tsx
index 43ede0d..2109474 100644
--- a/src/client/pages/commands/ChartPaymentsReceived.tsx
+++ b/src/client/pages/commands/ChartPaymentsReceived.tsx
@@ -22,35 +22,6 @@ const ChartPaymentsReceivedCommand = commands.find(n => n.value === 'ChartPaymen
Passes query parameters to the chart-payments-received results page
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '350px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- h4: {
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
-};
-
const ChartPaymentsReceived = () => {
const [count, setCount] = useState(false);
const [days, setDays] = useState('');
@@ -186,3 +157,32 @@ const ChartPaymentsReceived = () => {
};
export default ChartPaymentsReceived;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '350px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/Closed.tsx b/src/client/pages/commands/Closed.tsx
index ebe5b91..ffe7154 100644
--- a/src/client/pages/commands/Closed.tsx
+++ b/src/client/pages/commands/Closed.tsx
@@ -16,26 +16,12 @@ const ClosedCommand = commands.find(n => n.value === 'Closed');
GET call to the NestJs process to get closed transactions
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- textField: {
- width: '300px',
- },
- h4: {
- marginTop: '0px',
- },
-};
-
const Closed = () => {
const [node, setNode] = useState('');
const [limit, setLimit] = useState('');
const [data, setData] = useState(undefined);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -81,7 +67,7 @@ const Closed = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -95,3 +81,17 @@ const Closed = () => {
};
export default Closed;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ textField: {
+ width: '300px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+};
diff --git a/src/client/pages/commands/CreateGroupChannel.tsx b/src/client/pages/commands/CreateGroupChannel.tsx
new file mode 100644
index 0000000..a254a3e
--- /dev/null
+++ b/src/client/pages/commands/CreateGroupChannel.tsx
@@ -0,0 +1,161 @@
+import * as types from '~shared/types';
+
+import { CssBaseline, Stack, TextField } from '@mui/material';
+import React, { useState } from 'react';
+import { StandardHomeButtonLink, StartFlexBox, SubmitButton } from '~client/standard_components/app-components';
+import commands, { globalCommands } from '../../commands';
+
+import { CreateGroupChannelOutput } from '~client/output';
+import Head from 'next/head';
+import { axiosPostWithWebSocket } from '~client/utils/axios';
+import { useNotify } from '~client/hooks/useNotify';
+import validateCreateGroupChannelCommand from '~client/utils/validations/validate_create_group_channel_command';
+
+/*
+ Renders the bos create-group-channel command
+ GET call to the NestJs process to get chain deposit address
+*/
+
+const CreateGroupChannelCommand = commands.find(n => n.value === 'CreateGroupChannel');
+
+const CreateGroupChannel = () => {
+ const [capacity, setCapacity] = useState('');
+ const [data, setData] = useState(undefined);
+ const [feeRate, setFeeRate] = useState('');
+ const [node, setNode] = useState('');
+
+ const [size, setSize] = useState('');
+
+ const handleCapacityChange = (event: React.ChangeEvent) => {
+ setCapacity(event.target.value);
+ };
+
+ const handleFeeRateChange = (event: React.ChangeEvent) => {
+ setFeeRate(event.target.value);
+ };
+
+ const handleNodeChange = (event: React.ChangeEvent) => {
+ setNode(event.target.value);
+ };
+
+ const handleSizeChange = (event: React.ChangeEvent) => {
+ setSize(event.target.value);
+ };
+
+ const fetchData = async () => {
+ try {
+ const result = validateCreateGroupChannelCommand({
+ capacity: Number(capacity),
+ count: Number(size),
+ rate: Number(feeRate),
+ });
+
+ if (!result) {
+ return;
+ }
+
+ const postBody: types.commandCreateGroupChannel = {
+ node,
+ capacity: Number(capacity),
+ count: Number(size),
+ message_id: Date.now().toString(),
+ rate: Number(feeRate),
+ };
+
+ await axiosPostWithWebSocket({
+ id: postBody.message_id,
+ path: 'create-group-channel',
+ postBody,
+ setData,
+ });
+ } catch (error) {
+ useNotify({ type: 'error', message: error.message });
+ }
+ };
+
+ return (
+
+
+ Create Group Channel
+
+
+
+
+ {CreateGroupChannelCommand.name}
+ {CreateGroupChannelCommand.description}
+ {CreateGroupChannelCommand.longDescription}
+
+
+
+
+
+
+ Run Command
+
+
+
+
+
+ );
+};
+
+export default CreateGroupChannel;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '1100px',
+ },
+ textField: {
+ width: '500px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+};
+
+const Instructions = () => {
+ return (
+
+ -
+ Important: DO NOT REFRESH THE PAGE WHEN THE COMMAND IS RUNNING.
+
+ - Capacity (Required): Total capacity of the channel(s) being opened.
+ - FeeRate (Required): On-chain fee rate for the opening transaction(s).
+ - Size (Required): Number of nodes participating in the group open.
+ - Runing the command returns an invite code that needs to be passed to other members.
+ - Members run the Join Group Channel command to join the group.
+ - Minimum party size is 2.
+ - Maximum party size is 420.
+
+ );
+};
diff --git a/src/client/pages/commands/Fees.tsx b/src/client/pages/commands/Fees.tsx
index f0ffa82..c2a656d 100644
--- a/src/client/pages/commands/Fees.tsx
+++ b/src/client/pages/commands/Fees.tsx
@@ -19,37 +19,6 @@ const FeesCommand = commands.find(n => n.value === 'Fees');
POST call to the NestJs process to get fees information
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '800px',
- },
- textField: {
- width: '500px',
- marginTop: '10px',
- },
- h4: {
- marginTop: '0px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- url: {
- fontWeight: 'bold',
- color: 'blue',
- },
-};
-
const Fees = () => {
const [cltvDelta, setCltvDelta] = useState(undefined);
const [data, setData] = useState(undefined);
@@ -57,7 +26,7 @@ const Fees = () => {
const [formValues, setFormValues] = useState([{ node: '' }]);
const [node, setNode] = useState(undefined);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -169,7 +138,7 @@ const Fees = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -182,3 +151,34 @@ const Fees = () => {
};
export default Fees;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '800px',
+ },
+ textField: {
+ width: '500px',
+ marginTop: '10px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ url: {
+ fontWeight: 'bold',
+ color: 'blue',
+ },
+};
diff --git a/src/client/pages/commands/Find.tsx b/src/client/pages/commands/Find.tsx
index 0c1cbd0..27ff030 100644
--- a/src/client/pages/commands/Find.tsx
+++ b/src/client/pages/commands/Find.tsx
@@ -16,26 +16,12 @@ const FindCommand = commands.find(n => n.value === 'Find');
GET call to the NestJs process to fetch data from node db
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '500px',
- },
- h4: {
- marginTop: '0px',
- },
-};
-
const Find = () => {
const [node, setNode] = useState('');
const [queryString, setQueryString] = useState('');
const [data, setData] = useState(undefined);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -79,7 +65,7 @@ const Find = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -93,3 +79,17 @@ const Find = () => {
};
export default Find;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+};
diff --git a/src/client/pages/commands/Forwards.tsx b/src/client/pages/commands/Forwards.tsx
index 060b70a..6ccf7ec 100644
--- a/src/client/pages/commands/Forwards.tsx
+++ b/src/client/pages/commands/Forwards.tsx
@@ -17,32 +17,6 @@ const ForwardsCommand = commands.find(n => n.value === 'Forwards');
GET call to the NestJs process to forwards information
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- textField: {
- width: '300px',
- },
- h4: {
- marginTop: '0px',
- },
-};
-
const Forwards = () => {
const [days, setDays] = useState('');
const [data, setData] = useState(undefined);
@@ -52,7 +26,7 @@ const Forwards = () => {
const [node, setNode] = useState('');
const [formValues, setFormValues] = useState([{ tag: '' }]);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -180,7 +154,7 @@ const Forwards = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -194,3 +168,29 @@ const Forwards = () => {
};
export default Forwards;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ textField: {
+ width: '300px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+};
diff --git a/src/client/pages/commands/Graph.tsx b/src/client/pages/commands/Graph.tsx
index 6c3ce4b..16655fc 100644
--- a/src/client/pages/commands/Graph.tsx
+++ b/src/client/pages/commands/Graph.tsx
@@ -17,33 +17,6 @@ import { axiosGet } from '~client/utils/axios';
const GraphCommand = commands.find(n => n.value === 'Graph');
-const styles = {
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '1000px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- textField: {
- width: '500px',
- },
- h4: {
- marginTop: '0px',
- fontWeight: 'bold',
- },
-};
-
const Graph = () => {
const [aliasOrPubkey, setAliasOrPubkey] = useState('');
const [node, setNode] = useState('');
@@ -51,7 +24,7 @@ const Graph = () => {
const [filter, setFilter] = useState([{ filter: '' }]);
const [data, setData] = useState(undefined);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -154,7 +127,7 @@ const Graph = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -168,3 +141,30 @@ const Graph = () => {
};
export default Graph;
+
+const styles = {
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '1000px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ textField: {
+ width: '500px',
+ },
+ h4: {
+ marginTop: '0px',
+ fontWeight: 'bold',
+ },
+};
diff --git a/src/client/pages/commands/JoinGroupChannel.tsx b/src/client/pages/commands/JoinGroupChannel.tsx
new file mode 100644
index 0000000..e2f88cb
--- /dev/null
+++ b/src/client/pages/commands/JoinGroupChannel.tsx
@@ -0,0 +1,143 @@
+import * as types from '~shared/types';
+
+import { CssBaseline, Stack, TextField } from '@mui/material';
+import React, { useState } from 'react';
+import { StandardHomeButtonLink, StartFlexBox, SubmitButton } from '~client/standard_components/app-components';
+import commands, { globalCommands } from '../../commands';
+
+import Head from 'next/head';
+import { JoinGroupChannelOutput } from '~client/output';
+import { axiosPostWithWebSocket } from '~client/utils/axios';
+import { useNotify } from '~client/hooks/useNotify';
+import validateJoinGroupChannelCommand from '~client/utils/validations/validate_join_group_channel_command';
+
+/*
+ Renders the bos join-group-channel command
+ GET call to the NestJs process to get chain deposit address
+*/
+
+const JoinGroupChannelCommand = commands.find(n => n.value === 'JoinGroupChannel');
+
+const JoinGroupChannel = () => {
+ const [code, setCode] = useState('');
+ const [data, setData] = useState(undefined);
+ const [maxFeeRate, setMaxFeeRate] = useState('');
+ const [node, setNode] = useState('');
+
+ const handleCodeChange = (event: React.ChangeEvent) => {
+ setCode(event.target.value);
+ };
+
+ const handleMaxFeeRateChange = (event: React.ChangeEvent) => {
+ setMaxFeeRate(event.target.value);
+ };
+
+ const handleNodeChange = (event: React.ChangeEvent) => {
+ setNode(event.target.value);
+ };
+
+ const fetchData = async () => {
+ try {
+ const result = validateJoinGroupChannelCommand({
+ code,
+ max_rate: Number(maxFeeRate),
+ });
+
+ if (!result) {
+ return;
+ }
+
+ const postBody: types.commandJoinGroupChannel = {
+ code,
+ node,
+ max_rate: Number(maxFeeRate),
+ message_id: Date.now().toString(),
+ };
+
+ await axiosPostWithWebSocket({
+ id: postBody.message_id,
+ path: 'join-group-channel',
+ postBody,
+ setData,
+ });
+ } catch (error) {
+ useNotify({ type: 'error', message: error.message });
+ }
+ };
+
+ return (
+
+
+ Join Group Channel
+
+
+
+
+ {JoinGroupChannelCommand.name}
+ {JoinGroupChannelCommand.description}
+ {JoinGroupChannelCommand.longDescription}
+
+
+
+
+
+
+
+ Run Command
+
+
+
+
+
+ );
+};
+
+export default JoinGroupChannel;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '1100px',
+ },
+ textField: {
+ width: '500px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+};
+
+const Instructions = () => {
+ return (
+
+ -
+ Important: DO NOT REFRESH THE PAGE WHEN THE COMMAND IS RUNNING.
+
+ - Code (Required): Invite code to join a group.
+ - MaxFeeRate (Required): Maximum onchain fee rate you are willing to pay for the channel open.
+ - The initiator of the group needs to run the Create Group Channel command.
+
+ );
+};
diff --git a/src/client/pages/commands/Lnurl.tsx b/src/client/pages/commands/Lnurl.tsx
index ee5ff09..5bd9227 100644
--- a/src/client/pages/commands/Lnurl.tsx
+++ b/src/client/pages/commands/Lnurl.tsx
@@ -26,46 +26,6 @@ import Link from 'next/link';
const LnurlCommand = commands.find(n => n.value === 'Lnurl');
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '500px',
- },
- pre: {
- fontWeight: 'bold',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
- url: {
- fontWeight: 'bold',
- color: 'blue',
- },
- inputLabel: {
- fontWeight: 'bold',
- color: 'black',
- },
- select: {
- width: '300px',
- },
-};
-
const Lnurl = () => {
const [node, setNode] = useState('');
const [amount, setAmount] = useState('');
@@ -81,7 +41,7 @@ const Lnurl = () => {
setLnurlFunction(event.target.value);
};
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -305,7 +265,7 @@ const Lnurl = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -320,3 +280,43 @@ const Lnurl = () => {
};
export default Lnurl;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ pre: {
+ fontWeight: 'bold',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+ url: {
+ fontWeight: 'bold',
+ color: 'blue',
+ },
+ inputLabel: {
+ fontWeight: 'bold',
+ color: 'black',
+ },
+ select: {
+ width: '300px',
+ },
+};
diff --git a/src/client/pages/commands/Open.tsx b/src/client/pages/commands/Open.tsx
index 23cc64b..f33fd4b 100644
--- a/src/client/pages/commands/Open.tsx
+++ b/src/client/pages/commands/Open.tsx
@@ -27,7 +27,7 @@ import Head from 'next/head';
import { OpenOutput } from '~client/output';
import { axiosPostWithAlert } from '~client/utils/axios';
import { useNotify } from '~client/hooks/useNotify';
-import validateOpenCommandArgs from '~client/utils/validate_open_command_args';
+import validateOpenCommandArgs from '~client/utils/validations/validate_open_command_args';
const knownTypes = ['public', 'private', 'public-trusted', 'private-trusted'];
const OpenCommand = commands.find(n => n.value === 'Open');
@@ -37,46 +37,6 @@ const OpenCommand = commands.find(n => n.value === 'Open');
POST call to the NestJs process to open channels
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '1500px',
- },
- textField: {
- width: '220px',
- marginLeft: '10px',
- },
- h4: {
- marginTop: '0px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
- select: {
- width: '200px',
- },
- inputLabel: {
- color: 'black',
- },
- formControl: {
- marginLeft: '10px',
- width: '100px',
- },
-};
-
const Open = () => {
const [avoidBroadcast, setAvoidBroadcast] = useState(false);
const [data, setData] = useState(undefined);
@@ -85,11 +45,11 @@ const Open = () => {
const [internalFundFeeRate, setInternalFundFeeRate] = useState(undefined);
const [node, setNode] = useState('');
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
- const handeInternalFundFeeRateChange = (event: React.ChangeEvent) => {
+ const handleInternalFundFeeRateChange = (event: React.ChangeEvent) => {
setInternalFundFeeRate(event.target.value);
};
@@ -276,7 +236,7 @@ const Open = () => {
name={OpenCommand.flags.internalFundAtFeeRate}
id={OpenCommand.flags.internalFundAtFeeRate}
placeholder={'OnChain Fee Rate'}
- onChange={handeInternalFundFeeRateChange}
+ onChange={handleInternalFundFeeRateChange}
style={styles.textField}
/>
{
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -310,3 +270,43 @@ const Open = () => {
};
export default Open;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '1500px',
+ },
+ textField: {
+ width: '220px',
+ marginLeft: '10px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+ select: {
+ width: '200px',
+ },
+ inputLabel: {
+ color: 'black',
+ },
+ formControl: {
+ marginLeft: '10px',
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/Pay.tsx b/src/client/pages/commands/Pay.tsx
index e9f442b..d254895 100644
--- a/src/client/pages/commands/Pay.tsx
+++ b/src/client/pages/commands/Pay.tsx
@@ -16,39 +16,6 @@ const PayCommand = commands.find(n => n.value === 'Pay');
GET call to the NestJs process to execute a keysend or lnurl pay
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- textField: {
- width: '500px',
- },
- pre: {
- fontWeight: 'bold',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
- url: {
- fontWeight: 'bold',
- color: 'blue',
- },
-};
-
const Send = () => {
const [node, setNode] = useState('');
const [avoid, setAvoid] = useState([{ avoid: '' }]);
@@ -59,7 +26,7 @@ const Send = () => {
const [message, setMessage] = useState('');
const [paymentRequest, setPaymentRequest] = useState('');
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -233,7 +200,7 @@ const Send = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -248,3 +215,36 @@ const Send = () => {
};
export default Send;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ pre: {
+ fontWeight: 'bold',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+ url: {
+ fontWeight: 'bold',
+ color: 'blue',
+ },
+};
diff --git a/src/client/pages/commands/Peers.tsx b/src/client/pages/commands/Peers.tsx
index 0a48953..132d5f3 100644
--- a/src/client/pages/commands/Peers.tsx
+++ b/src/client/pages/commands/Peers.tsx
@@ -12,7 +12,7 @@ import commands, { globalCommands } from '~client/commands';
import DeleteIcon from '@mui/icons-material/Delete';
import Head from 'next/head';
-import PeersOutput from '~client/output/PeersOutput';
+import { PeersOutput } from '~client/output';
import { axiosGet } from '~client/utils/axios';
/*
@@ -22,39 +22,6 @@ import { axiosGet } from '~client/utils/axios';
const PeersCommand = commands.find(n => n.value === 'Peers');
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- textField: {
- width: '500px',
- },
- pre: {
- fontWeight: 'bold',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
- url: {
- fontWeight: 'bold',
- color: 'blue',
- },
-};
-
const Peers = () => {
const [node, setNode] = useState('');
const [isActive, setIsActive] = useState(false);
@@ -70,7 +37,7 @@ const Peers = () => {
const [tags, setTags] = useState([{ tags: '' }]);
const [data, setData] = useState(undefined);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -327,7 +294,7 @@ const Peers = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -341,3 +308,36 @@ const Peers = () => {
};
export default Peers;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ pre: {
+ fontWeight: 'bold',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+ url: {
+ fontWeight: 'bold',
+ color: 'blue',
+ },
+};
diff --git a/src/client/pages/commands/Price.tsx b/src/client/pages/commands/Price.tsx
index 604524a..c0bfa65 100644
--- a/src/client/pages/commands/Price.tsx
+++ b/src/client/pages/commands/Price.tsx
@@ -10,7 +10,7 @@ import {
} from '~client/standard_components/app-components';
import Head from 'next/head';
-import PriceOutput from '~client/output/PriceOutput';
+import { PriceOutput } from '~client/output';
import { axiosGet } from '~client/utils/axios';
import commands from '../../commands';
@@ -21,23 +21,6 @@ const PriceCommand = commands.find(n => n.value === 'Price');
GET call to the NestJs process to get fiat pricing information
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- textField: {
- width: '500px',
- },
- h4: {
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
-};
-
const Price = () => {
const [symbols, setSymbols] = useState('');
const [file, setFile] = useState(false);
@@ -112,3 +95,20 @@ const Price = () => {
};
export default Price;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+};
diff --git a/src/client/pages/commands/Probe.tsx b/src/client/pages/commands/Probe.tsx
index 6571289..b248096 100644
--- a/src/client/pages/commands/Probe.tsx
+++ b/src/client/pages/commands/Probe.tsx
@@ -21,39 +21,6 @@ const ProbeCommand = commands.find(n => n.value === 'Probe');
GET call to the NestJs process to execute to probe a node on the network.
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- textField: {
- width: '500px',
- },
- pre: {
- fontWeight: 'bold',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
- url: {
- fontWeight: 'bold',
- color: 'blue',
- },
-};
-
const Probe = () => {
const [node, setNode] = useState('');
const [amount, setAmount] = useState('');
@@ -64,7 +31,7 @@ const Probe = () => {
const [maxPaths, setMaxPaths] = useState('');
const [outPeer, setOutPeer] = useState([{ outPeer: '' }]);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -233,7 +200,7 @@ const Probe = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -248,3 +215,36 @@ const Probe = () => {
};
export default Probe;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ pre: {
+ fontWeight: 'bold',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+ url: {
+ fontWeight: 'bold',
+ color: 'blue',
+ },
+};
diff --git a/src/client/pages/commands/Rebalance.tsx b/src/client/pages/commands/Rebalance.tsx
index 169e411..8c62f93 100644
--- a/src/client/pages/commands/Rebalance.tsx
+++ b/src/client/pages/commands/Rebalance.tsx
@@ -25,39 +25,6 @@ const RebalanceCommand = commands.find(n => n.value === 'Rebalance');
GET call to the NestJs process to execute a rebalance
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- textField: {
- width: '500px',
- },
- pre: {
- fontWeight: 'bold',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- url: {
- fontWeight: 'bold',
- color: 'blue',
- },
- link: {
- width: '250px',
- },
-};
-
const Rebalance = () => {
const [node, setNode] = useState('');
const [amount, setAmount] = useState('');
@@ -82,7 +49,7 @@ const Rebalance = () => {
setCronUrl(newCronUrl);
};
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -389,7 +356,7 @@ const Rebalance = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -417,3 +384,36 @@ const Rebalance = () => {
};
export default Rebalance;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ pre: {
+ fontWeight: 'bold',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ url: {
+ fontWeight: 'bold',
+ color: 'blue',
+ },
+ link: {
+ width: '250px',
+ },
+};
diff --git a/src/client/pages/commands/Reconnect.tsx b/src/client/pages/commands/Reconnect.tsx
index 9d4547a..de26007 100644
--- a/src/client/pages/commands/Reconnect.tsx
+++ b/src/client/pages/commands/Reconnect.tsx
@@ -6,7 +6,7 @@ import { StandardHomeButtonLink, StartFlexBox, SubmitButton } from '~client/stan
import commands, { globalCommands } from '~client/commands';
import Head from 'next/head';
-import ReconnectOutput from '~client/output/ReconnectOutput';
+import { ReconnectOutput } from '~client/output';
import { axiosGet } from '~client/utils/axios';
/*
@@ -16,24 +16,10 @@ import { axiosGet } from '~client/utils/axios';
const ReconnectCommand = commands.find(n => n.value === 'Reconnect');
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '800px',
- },
- textField: {
- width: '500px',
- },
- h4: {
- marginTop: '0px',
- },
-};
-
const Reconnect = () => {
const [node, setNode] = useState('');
const [data, setData] = useState(undefined);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -63,7 +49,7 @@ const Reconnect = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -77,3 +63,17 @@ const Reconnect = () => {
};
export default Reconnect;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '800px',
+ },
+ textField: {
+ width: '500px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+};
diff --git a/src/client/pages/commands/Send.tsx b/src/client/pages/commands/Send.tsx
index 9a381c6..d51b676 100644
--- a/src/client/pages/commands/Send.tsx
+++ b/src/client/pages/commands/Send.tsx
@@ -21,39 +21,6 @@ const SendCommand = commands.find(n => n.value === 'Send');
GET call to the NestJs process to execute a keysend or lnurl pay
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- minWidth: '700px',
- },
- textField: {
- width: '500px',
- },
- pre: {
- fontWeight: 'bold',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
- url: {
- fontWeight: 'bold',
- color: 'blue',
- },
-};
-
const Send = () => {
const [node, setNode] = useState('');
const [amount, setAmount] = useState('');
@@ -67,7 +34,7 @@ const Send = () => {
const [message, setMessage] = useState('');
const [isOmittingMessageFrom, setIsOmittingMessageFrom] = useState(false);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -251,7 +218,7 @@ const Send = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
@@ -266,3 +233,36 @@ const Send = () => {
};
export default Send;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ minWidth: '700px',
+ },
+ textField: {
+ width: '500px',
+ },
+ pre: {
+ fontWeight: 'bold',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+ url: {
+ fontWeight: 'bold',
+ color: 'blue',
+ },
+};
diff --git a/src/client/pages/commands/Tags.tsx b/src/client/pages/commands/Tags.tsx
index ab8bcc2..5ab934e 100644
--- a/src/client/pages/commands/Tags.tsx
+++ b/src/client/pages/commands/Tags.tsx
@@ -34,43 +34,6 @@ const TagsCommand = commands.find(n => n.value === 'Tags');
GET call to the NestJs process to get the tags
*/
-const styles = {
- form: {
- marginLeft: '50px',
- marginTop: '100px',
- width: '700px',
- },
- textField: {
- width: '380px',
- marginTop: '20px',
- },
- button: {
- color: 'white',
- fontWeight: 'bold',
- borderRadius: '10px',
- border: '1px solid black',
- marginTop: '20px',
- width: '50px',
- },
- iconButton: {
- width: '50px',
- marginTop: '0px',
- },
- h4: {
- marginTop: '0px',
- },
- switch: {
- width: '100px',
- },
- inputLabel: {
- fontWeight: 'bold',
- color: 'black',
- },
- select: {
- width: '300px',
- },
-};
-
const Tags = () => {
const [avoid, setAvoid] = useState(false);
const [formValues, setFormValues] = useState([{ pubkey: '' }]);
@@ -232,4 +195,42 @@ const Tags = () => {
);
};
+
export default Tags;
+
+const styles = {
+ form: {
+ marginLeft: '50px',
+ marginTop: '100px',
+ width: '700px',
+ },
+ textField: {
+ width: '380px',
+ marginTop: '20px',
+ },
+ button: {
+ color: 'white',
+ fontWeight: 'bold',
+ borderRadius: '10px',
+ border: '1px solid black',
+ marginTop: '20px',
+ width: '50px',
+ },
+ iconButton: {
+ width: '50px',
+ marginTop: '0px',
+ },
+ h4: {
+ marginTop: '0px',
+ },
+ switch: {
+ width: '100px',
+ },
+ inputLabel: {
+ fontWeight: 'bold',
+ color: 'black',
+ },
+ select: {
+ width: '300px',
+ },
+};
diff --git a/src/client/pages/schedulers/FeeScheduler.tsx b/src/client/pages/schedulers/FeeScheduler.tsx
index ded6bb3..b44784e 100644
--- a/src/client/pages/schedulers/FeeScheduler.tsx
+++ b/src/client/pages/schedulers/FeeScheduler.tsx
@@ -9,7 +9,7 @@ import fetchPeersAndTags from '~client/utils/fetch_peers_and_tags';
import { globalCommands } from '~client/commands';
import { useLoading } from '~client/hooks/useLoading';
import { useNotify } from '~client/hooks/useNotify';
-import validateAutoFees from '~client/utils/validate_auto_fees';
+import validateAutoFees from '~client/utils/validations/validate_auto_fees';
const splitValue = n => n.split('\n')[1].trim();
@@ -63,7 +63,7 @@ const FeeScheduler = () => {
const [node, setNode] = useState('');
const [peersAndTags, setPeersAndTags] = useState([]);
- const handeNodeChange = (event: React.ChangeEvent) => {
+ const handleNodeChange = (event: React.ChangeEvent) => {
setNode(event.target.value);
};
@@ -346,7 +346,7 @@ const FeeScheduler = () => {
placeholder={globalCommands.node.name}
label={globalCommands.node.name}
id={globalCommands.node.value}
- onChange={handeNodeChange}
+ onChange={handleNodeChange}
style={styles.textField}
/>
diff --git a/src/client/utils/axios.ts b/src/client/utils/axios.ts
index dba1b61..ac9d77c 100644
--- a/src/client/utils/axios.ts
+++ b/src/client/utils/axios.ts
@@ -1,7 +1,11 @@
+import * as YAML from 'json-to-pretty-yaml';
+
import axios from 'axios';
import getConfig from 'next/config';
+import { io } from 'socket.io-client';
import { useLoading } from '~client/hooks/useLoading';
import { useNotify } from '~client/hooks/useNotify';
+
const { publicRuntimeConfig } = getConfig();
const { apiUrl } = publicRuntimeConfig;
@@ -171,4 +175,62 @@ const axiosPost = async ({ path, postBody }: ArgsPost) => {
console.log(error);
}
};
-export { axiosGet, axiosGetNoAlert, axiosGetNoLoading, axiosGetWebSocket, axiosPost, axiosPostWithAlert };
+
+const axiosPostWithWebSocket = async ({ id, path, postBody, setData }) => {
+ try {
+ const socket = io();
+ const output = [];
+
+ socket.on('connect', () => {
+ console.log('connected');
+ });
+
+ socket.on('disconnect', () => {
+ console.log('disconnected');
+ });
+
+ socket.on(`${id}`, data => {
+ const message = data.message.options;
+
+ output.push(YAML.stringify(message));
+
+ setData(output.join('\n'));
+ });
+
+ socket.on('error', err => {
+ throw err;
+ });
+
+ const url = `${apiUrl}/${path}`;
+ const accessToken = localStorage.getItem('accessToken');
+
+ const config = {
+ headers: { Authorization: `Bearer ${accessToken}` },
+ };
+
+ const response = await axios.post(url, postBody, config);
+
+ const data = await response.data;
+
+ output.push(YAML.stringify(data));
+
+ setData(output.join('\n'));
+
+ return data;
+ } catch (error) {
+ useNotify({
+ type: 'error',
+ message: `Status: ${error.response.data.statusCode}\nMessage: ${error.response.data.message}`,
+ });
+ }
+};
+
+export {
+ axiosGet,
+ axiosGetNoAlert,
+ axiosGetNoLoading,
+ axiosGetWebSocket,
+ axiosPost,
+ axiosPostWithAlert,
+ axiosPostWithWebSocket,
+};
diff --git a/src/client/utils/validate_auto_fees.ts b/src/client/utils/validations/validate_auto_fees.ts
similarity index 100%
rename from src/client/utils/validate_auto_fees.ts
rename to src/client/utils/validations/validate_auto_fees.ts
diff --git a/src/client/utils/validate_call_command_args.ts b/src/client/utils/validations/validate_call_command_args.ts
similarity index 100%
rename from src/client/utils/validate_call_command_args.ts
rename to src/client/utils/validations/validate_call_command_args.ts
diff --git a/src/client/utils/validations/validate_create_group_channel_command.ts b/src/client/utils/validations/validate_create_group_channel_command.ts
new file mode 100644
index 0000000..252bda1
--- /dev/null
+++ b/src/client/utils/validations/validate_create_group_channel_command.ts
@@ -0,0 +1,54 @@
+const isNumber = (n: number) => !isNaN(n);
+const isOdd = n => !!(n % 2);
+const maxGroupSize = 420;
+const minChannelSize = 2e4;
+const minGroupSize = 2;
+
+/** Validate create group channel body
+ {
+ capacity:
+ count:
+ rate:
+ }
+
+ @returns boolean
+*/
+
+type Args = {
+ capacity: number;
+ count: number;
+ rate: number;
+};
+const validateCreateGroupChannelCommand = (args: Args) => {
+ if (!args.capacity || !isNumber(args.capacity)) {
+ throw new Error('Expected Numeric Capacity To Open Group Channel');
+ }
+
+ if (!args.count || !isNumber(args.count)) {
+ throw new Error('Expected Numeric Size To Open Group Channel');
+ }
+
+ if (args.count < minGroupSize) {
+ throw new Error('Expected Group Size Of Minimum 2');
+ }
+
+ if (args.count > maxGroupSize) {
+ throw new Error('Expected Group Size Of Maximum 420');
+ }
+
+ if (args.capacity < minChannelSize) {
+ throw new Error('Expected Channel Capacity Greater Than 20000');
+ }
+
+ if (isOdd(args.capacity)) {
+ throw new Error('Expected Even Number For Capacity');
+ }
+
+ if (!args.rate || !isNumber(args.rate)) {
+ throw new Error('Expected Numeric Fee Rate To Open Group Channel');
+ }
+
+ return true;
+};
+
+export default validateCreateGroupChannelCommand;
diff --git a/src/client/utils/validations/validate_join_group_channel_command.ts b/src/client/utils/validations/validate_join_group_channel_command.ts
new file mode 100644
index 0000000..7cb49ed
--- /dev/null
+++ b/src/client/utils/validations/validate_join_group_channel_command.ts
@@ -0,0 +1,29 @@
+const isCode = n => !!n && n.length === 98;
+const isNumber = (n: number) => !isNaN(n);
+
+/** Validate join group channel body
+ {
+ code:
+ max_rate:
+ }
+
+ @returns boolean
+*/
+
+type Args = {
+ code: string;
+ max_rate: number;
+};
+const validateJoinGroupChannelCommand = (args: Args) => {
+ if (!args.code || !isCode(args.code)) {
+ throw new Error('Expected Valid Invite Code To Join Group Channel');
+ }
+
+ if (!args.max_rate || !isNumber(args.max_rate)) {
+ throw new Error('Expected Numeric Max Fee Rate To Join Group Channel');
+ }
+
+ return true;
+};
+
+export default validateJoinGroupChannelCommand;
diff --git a/src/client/utils/validate_open_command_args.ts b/src/client/utils/validations/validate_open_command_args.ts
similarity index 100%
rename from src/client/utils/validate_open_command_args.ts
rename to src/client/utils/validations/validate_open_command_args.ts
diff --git a/src/server/commands/createGroupChannel/create_group_channel_command.ts b/src/server/commands/createGroupChannel/create_group_channel_command.ts
new file mode 100644
index 0000000..6a415a7
--- /dev/null
+++ b/src/server/commands/createGroupChannel/create_group_channel_command.ts
@@ -0,0 +1,37 @@
+import * as types from '~shared/types';
+
+import { AuthenticatedLnd } from 'lightning';
+import { Logger } from 'winston';
+import { createGroupChannel } from 'paid-services';
+
+/** Create a channel group
+ {
+ capacity:
+ count:
+ lnd:
+ logger:
+ rate:
+ }
+ @returns via Promise
+ {
+ transaction_id:
+ }
+*/
+
+type Args = {
+ args: types.commandCreateGroupChannel;
+ lnd: AuthenticatedLnd;
+ logger: Logger;
+};
+const createGroupChannelCommand = async ({ args, lnd, logger }: Args) => {
+ const result = await createGroupChannel({
+ lnd,
+ logger,
+ capacity: args.capacity,
+ count: args.count,
+ rate: args.rate,
+ });
+ return { result };
+};
+
+export default createGroupChannelCommand;
diff --git a/src/server/commands/createGroupChannel/spawn_process.ts b/src/server/commands/createGroupChannel/spawn_process.ts
new file mode 100644
index 0000000..3616d09
--- /dev/null
+++ b/src/server/commands/createGroupChannel/spawn_process.ts
@@ -0,0 +1,51 @@
+import { Logger, createLogger, format, transports } from 'winston';
+
+import { LndService } from '~server/modules/lnd/lnd.service';
+import { createGroupChannel } from 'paid-services';
+
+async function getLogger({ service }) {
+ const logger: Logger = createLogger({
+ level: 'info',
+ defaultMeta: { service },
+ transports: [
+ new transports.Console({
+ format: format.combine(format.prettyPrint()),
+ }),
+ ],
+ });
+
+ return logger;
+}
+
+const spawnProcess = async () => {
+ try {
+ process.on('message', async (msg: any) => {
+ try {
+ const lnd = await LndService.authenticatedLnd({ node: msg.node });
+
+ const logger = await getLogger({ service: 'create-group-channel' });
+
+ const result = await createGroupChannel({
+ lnd,
+ logger,
+ capacity: msg.args.capacity,
+ count: msg.args.count,
+ rate: msg.args.rate,
+ });
+
+ process.send({ result });
+
+ process.kill(msg.pid);
+ } catch (err) {
+ process.kill(msg.pid);
+ throw err;
+ }
+ });
+
+ process.on('exit', () => console.log('exiting process'));
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+spawnProcess();
diff --git a/src/server/commands/index.ts b/src/server/commands/index.ts
index 591a2ab..a24979d 100644
--- a/src/server/commands/index.ts
+++ b/src/server/commands/index.ts
@@ -9,10 +9,12 @@ import chartFeesEarnedCommand from './chartFeesEarned/chart_fees_earned_command'
import chartFeesPaidCommand from './chartFeesPaid/chart_fees_paid_command';
import chartPaymentsReceivedCommand from './chartPaymentsReceived/chart_payments_received_command';
import closedCommand from './closed/closed_command';
+import createGroupChannelCommand from './createGroupChannel/create_group_channel_command';
import feesCommand from './fees/fees_command';
import findCommand from './find/find_command';
import forwardsCommand from './forwards/forwards_command';
import graphCommand from './graph/graph_command';
+import joinGroupChannelCommand from './joinGroupChannel/join_group_channel_command';
import lnurlCommand from './lnurl/lnurl_command';
import openCommand from './open/open_command';
import payCommand from './pay/pay_command';
@@ -36,10 +38,12 @@ export {
chartFeesPaidCommand,
chartPaymentsReceivedCommand,
closedCommand,
+ createGroupChannelCommand,
feesCommand,
findCommand,
forwardsCommand,
graphCommand,
+ joinGroupChannelCommand,
lnurlCommand,
openCommand,
payCommand,
diff --git a/src/server/commands/joinGroupChannel/join_group_channel_command.ts b/src/server/commands/joinGroupChannel/join_group_channel_command.ts
new file mode 100644
index 0000000..76094c5
--- /dev/null
+++ b/src/server/commands/joinGroupChannel/join_group_channel_command.ts
@@ -0,0 +1,36 @@
+import * as types from '~shared/types';
+
+import { AuthenticatedLnd } from 'lightning';
+import { Logger } from 'winston';
+import { joinGroupChannel } from 'paid-services';
+
+/** Join a channel group
+ {
+ code:
+ lnd:
+ logger:
+ max_rate:
+ }
+ @returns via cbk or Promise
+ {
+ transaction_id:
+ }
+*/
+
+type Args = {
+ args: types.commandJoinGroupChannel;
+ lnd: AuthenticatedLnd;
+ logger: Logger;
+};
+const joinGroupChannelCommand = async ({ args, lnd, logger }: Args) => {
+ const result = await joinGroupChannel({
+ lnd,
+ logger,
+ code: args.code,
+ max_rate: args.max_rate,
+ });
+
+ return { result };
+};
+
+export default joinGroupChannelCommand;
diff --git a/src/server/commands/joinGroupChannel/spawn_process.ts b/src/server/commands/joinGroupChannel/spawn_process.ts
new file mode 100644
index 0000000..a4b94f2
--- /dev/null
+++ b/src/server/commands/joinGroupChannel/spawn_process.ts
@@ -0,0 +1,50 @@
+import { Logger, createLogger, format, transports } from 'winston';
+
+import { LndService } from '~server/modules/lnd/lnd.service';
+import { joinGroupChannel } from 'paid-services';
+
+async function getLogger({ service }) {
+ const logger: Logger = createLogger({
+ level: 'info',
+ defaultMeta: { service },
+ transports: [
+ new transports.Console({
+ format: format.combine(format.prettyPrint()),
+ }),
+ ],
+ });
+
+ return logger;
+}
+
+const spawnProcess = async () => {
+ try {
+ process.on('message', async (msg: any) => {
+ try {
+ const lnd = await LndService.authenticatedLnd({ node: msg.args.node });
+
+ const logger = await getLogger({ service: 'join-group-channel' });
+
+ const result = await joinGroupChannel({
+ lnd,
+ logger,
+ code: msg.args.code,
+ max_rate: msg.args.max_rate,
+ });
+
+ process.send({ result });
+
+ process.kill(msg.pid);
+ } catch (err) {
+ process.kill(msg.pid);
+ throw err;
+ }
+ });
+
+ process.on('exit', () => console.log('exiting process'));
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+spawnProcess();
diff --git a/src/server/modules/auth/auth.module.ts b/src/server/modules/auth/auth.module.ts
index a145575..d10fbc9 100644
--- a/src/server/modules/auth/auth.module.ts
+++ b/src/server/modules/auth/auth.module.ts
@@ -16,7 +16,7 @@ import { UsersModule } from '../users/users.module';
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
- signOptions: { expiresIn: !!isProduction ? process.env.SESSION_DURATION || '30m' : '1h' },
+ signOptions: { expiresIn: !!isProduction ? process.env.SESSION_DURATION || '40m' : '1h' },
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
diff --git a/src/server/modules/commands/commands.controller.ts b/src/server/modules/commands/commands.controller.ts
index 9904a5c..1b5804a 100644
--- a/src/server/modules/commands/commands.controller.ts
+++ b/src/server/modules/commands/commands.controller.ts
@@ -67,6 +67,11 @@ export class CommandsController {
return this.commandsService.closedCommand(args);
}
+ @Post('create-group-channel')
+ async createGroupChannelCommand(@Body() args: dto.createGroupChannelDto) {
+ return this.commandsService.createGroupChannelCommand(args);
+ }
+
@Get('find')
async findCommand(@Query() args: dto.findDto) {
return this.commandsService.findCommand(args);
@@ -82,6 +87,11 @@ export class CommandsController {
return this.commandsService.graphCommand(args);
}
+ @Post('join-group-channel')
+ async joinGroupChannelCommand(@Body() args: dto.joinGroupChannelDto) {
+ return this.commandsService.joinGroupChannelCommand(args);
+ }
+
@Get('lnurl')
async lnurlCommand(@Query() args: dto.lnurlDto) {
return this.commandsService.lnurlCommand(args);
diff --git a/src/server/modules/commands/commands.service.ts b/src/server/modules/commands/commands.service.ts
index 5e64e13..1cbdaac 100644
--- a/src/server/modules/commands/commands.service.ts
+++ b/src/server/modules/commands/commands.service.ts
@@ -132,6 +132,20 @@ export class CommandsService {
return { result };
}
+ async createGroupChannelCommand(args: dto.createGroupChannelDto): Promise<{ result: any }> {
+ try {
+ const logger = await this.logger({ messageId: args.message_id, service: 'create-group-channel' });
+
+ const lnd = await LndService.authenticatedLnd({ node: args.node });
+
+ const { result } = await commands.createGroupChannelCommand({ args, lnd, logger });
+
+ return { result };
+ } catch (error) {
+ httpLogger({ error });
+ }
+ }
+
async findCommand(args: dto.findDto): Promise<{ result: any }> {
const lnd = await LndService.authenticatedLnd({ node: args.node });
@@ -167,6 +181,20 @@ export class CommandsService {
return { result };
}
+ async joinGroupChannelCommand(args: dto.joinGroupChannelDto): Promise<{ result: any }> {
+ try {
+ const logger = await this.logger({ messageId: args.message_id, service: 'join-group-channel' });
+
+ const lnd = await LndService.authenticatedLnd({ node: args.node });
+
+ const { result } = await commands.joinGroupChannelCommand({ args, lnd, logger });
+
+ return { result };
+ } catch (error) {
+ httpLogger({ error });
+ }
+ }
+
async lnurlCommand(args: dto.lnurlDto): Promise<{ result: any }> {
try {
const logger = await this.logger({ messageId: args.message_id, service: 'lnurl' });
diff --git a/src/shared/commands.dto.ts b/src/shared/commands.dto.ts
index c6a2a55..88bae5a 100644
--- a/src/shared/commands.dto.ts
+++ b/src/shared/commands.dto.ts
@@ -327,6 +327,24 @@ export class closedDto {
node: string;
}
+export class createGroupChannelDto {
+ @IsNumber()
+ capacity: number;
+
+ @IsNumber()
+ count: number;
+
+ @IsOptional()
+ @IsString()
+ node: string;
+
+ @IsString()
+ message_id: string;
+
+ @IsNumber()
+ rate: number;
+}
+
export class deleteRebalanceDto {
@Transform(({ value }) => trim(value))
@IsString()
@@ -442,6 +460,20 @@ export class grpcDto {
@IsString()
node: string;
}
+export class joinGroupChannelDto {
+ @IsString()
+ code: string;
+
+ @IsOptional()
+ @IsString()
+ node: string;
+
+ @IsString()
+ message_id: string;
+
+ @IsNumber()
+ max_rate: number;
+}
export class lnurlDto {
@Transform(({ value }) => toNumber(value))
diff --git a/src/shared/types.ts b/src/shared/types.ts
index 1a05ee0..e397df3 100644
--- a/src/shared/types.ts
+++ b/src/shared/types.ts
@@ -99,6 +99,17 @@ export type commandClosed = {
node: string;
};
+// ========================Create Group Channel Command=========================
+
+export type commandCreateGroupChannel = {
+ capacity: number;
+ count: number;
+ node: string;
+ message_id: string;
+ rate: number;
+};
+
+// ========================Fees Command========================================
export type commandFees = {
cltv_delta: number;
fee_rate: string;
@@ -133,6 +144,15 @@ export type commandGraph = {
sort: string;
};
+// ========================Join Group Channel Command=========================
+
+export type commandJoinGroupChannel = {
+ code: string;
+ node: string;
+ message_id: string;
+ max_rate: number;
+};
+
// ========================Lnurl Command=====================================
export type commandLnurl = {
diff --git a/tests/client/createGroupChannel.test.ts b/tests/client/createGroupChannel.test.ts
new file mode 100644
index 0000000..54430d4
--- /dev/null
+++ b/tests/client/createGroupChannel.test.ts
@@ -0,0 +1,36 @@
+import { expect, test } from '@playwright/test';
+import { removeAccessToken, setAccessToken } from '../utils/setAccessToken';
+
+import commands from '../../src/client/commands';
+import { testConstants } from '../utils/constants';
+
+const CreateGroupChannelCommand = commands.find(n => n.value === 'CreateGroupChannel');
+
+test.describe('Test the Create Group Channel command client page', async () => {
+ test.beforeEach(async ({ page }) => {
+ await setAccessToken({ page });
+ });
+
+ test('test the Create Group Channel command page and input values', async ({ page }) => {
+ test.slow();
+ await page.goto(testConstants.commandsPage);
+ await page.click('text=Create Group Channel');
+ await expect(page).toHaveTitle('Create Group Channel');
+
+ await page.type(`#${CreateGroupChannelCommand?.flags?.capacity}`, '100000');
+ await page.type(`#${CreateGroupChannelCommand?.flags?.fee_rate}`, '2');
+ await page.type(`#${CreateGroupChannelCommand?.flags?.size}`, '2');
+
+ await page.type('#node', 'testnode1');
+
+ await page.click('text=run command');
+ await page.waitForTimeout(1000);
+
+ // await expect(page.locator('#creategroupchanneloutput')).toBeVisible(); // Need docker daemons to be able to generate coins
+ await page.click('text=home');
+ });
+
+ test.afterEach(async ({ page }) => {
+ await removeAccessToken({ page });
+ });
+});
diff --git a/tests/client/joinGroupChannel.test.ts b/tests/client/joinGroupChannel.test.ts
new file mode 100644
index 0000000..01dd640
--- /dev/null
+++ b/tests/client/joinGroupChannel.test.ts
@@ -0,0 +1,38 @@
+import { expect, test } from '@playwright/test';
+import { removeAccessToken, setAccessToken } from '../utils/setAccessToken';
+
+import commands from '../../src/client/commands';
+import { testConstants } from '../utils/constants';
+
+const JoinGroupChannelCommand = commands.find(n => n.value === 'JoinGroupChannel');
+
+test.describe('Test the Join Group Channel command client page', async () => {
+ test.beforeEach(async ({ page }) => {
+ await setAccessToken({ page });
+ });
+
+ test('test the Join Group Channel command page and input values', async ({ page }) => {
+ test.slow();
+ await page.goto(testConstants.commandsPage);
+ await page.click('text=Join Group Channel');
+ await expect(page).toHaveTitle('Join Group Channel');
+
+ await page.type(
+ `#${JoinGroupChannelCommand?.args?.code}`,
+ '023d8b17ae417518ef86d5471dfd5010b47cfb896812924f1ac8102ff8b76fa3fe47c004c6c6202060d5be81fe7ae90652'
+ );
+ await page.type(`#${JoinGroupChannelCommand?.flags?.max_fee_rate}`, '2');
+
+ await page.type('#node', 'testnode1');
+
+ await page.click('text=run command');
+ await page.waitForTimeout(1000);
+
+ // await expect(page.locator('#Joingroupchanneloutput')).toBeVisible(); // Need docker daemons to be able to generate coins
+ await page.click('text=home');
+ });
+
+ test.afterEach(async ({ page }) => {
+ await removeAccessToken({ page });
+ });
+});