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 ( +
+ {!!data &&
{data}
} +
+ ); +}; + +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 ( +
+ {!!data &&
{data}
} +
+ ); +}; + +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 }); + }); +});