Skip to content

Commit

Permalink
Merge pull request #355 from shocknet/test
Browse files Browse the repository at this point in the history
Test
  • Loading branch information
shocknet-justin authored Dec 2, 2024
2 parents 63f3f75 + 3c03487 commit be92bb9
Show file tree
Hide file tree
Showing 7 changed files with 899 additions and 503 deletions.
4 changes: 2 additions & 2 deletions src/Api/nostrHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ export default class RelayCluster {
}

SendNip69 = async (relays: string[], pubKey: string, data: nip69.NofferData, keys: NostrKeyPair): Promise<nip69.Nip69Response> => {
return nip69.SendNofferRequest(this.pool, Buffer.from(keys.privateKey, 'hex'), relays, pubKey, data)
return nip69.SendNofferRequest(this.pool, new Uint8Array(Buffer.from(keys.privateKey, 'hex')), relays, pubKey, data)
}

sendRaw = async (relays: string[], event: UnsignedEvent, privateKey: string) => {
const signed = finalizeEvent(event, Buffer.from(privateKey, 'hex'))
const signed = finalizeEvent(event, new Uint8Array(Buffer.from(privateKey, 'hex')))
this.pool.publish(relays, signed).forEach(p => {
p.then(() => logger.info("sent ok"))
p.catch(() => logger.error("failed to send"))
Expand Down
352 changes: 201 additions & 151 deletions src/Api/pub/autogenerated/ts/http_client.ts

Large diffs are not rendered by default.

347 changes: 193 additions & 154 deletions src/Api/pub/autogenerated/ts/nostr_client.ts

Large diffs are not rendered by default.

394 changes: 219 additions & 175 deletions src/Api/pub/autogenerated/ts/nostr_transport.ts

Large diffs are not rendered by default.

150 changes: 150 additions & 0 deletions src/Pages/Channels/EditChannel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { useEffect, useMemo, useState } from "react";
import * as Icons from "../../Assets/SvgIconLibrary";
import { useSelector } from "../../State/store";
import { getNostrClient } from "../../Api";
import { NostrKeyPair } from "../../Api/nostrHandler";
import PromptForActionModal, { ActionType } from "../../Components/Modals/PromptForActionModal";
import { SpendFrom } from "../../globalTypes";
import * as Types from '../../Api/pub/autogenerated/ts/types'
import { toast } from "react-toastify";
import Checkbox from "../../Components/Checkbox";
export type SelectedChannel = { id: number, name: string }
export const EditChannel = ({ adminSource, selectedChannel, deselect }: { adminSource: SpendFrom, selectedChannel: Types.OpenChannel | null, deselect: () => void }) => {
const [openModal, setOpenModal] = useState<'showPolicy' | 'updatePolocy' | 'closeChannel'>('showPolicy');
const [baseFeeMsat, setBaseFeeMsat] = useState<number>();
const [feeRatePpm, setFeeRatePpm] = useState<number>(0);
const [maxHtlcMsat, setMaxHtlcMsat] = useState<number>(0);
const [minHtlcMsat, setMinHtlcMsat] = useState<number>(0);
const [timeLockDelta, setTimeLockDelta] = useState<number>(0);
const [satsPerVByte, setSatsPerVByte] = useState<number>(0);
const [force, setForce] = useState<boolean>(false);
useEffect(() => {
if (selectedChannel && selectedChannel.policy) {
setBaseFeeMsat(selectedChannel.policy.base_fee_msat)
setFeeRatePpm(selectedChannel.policy.fee_rate_ppm)
setMaxHtlcMsat(selectedChannel.policy.max_htlc_msat)
setMinHtlcMsat(selectedChannel.policy.min_htlc_msat)
setTimeLockDelta(selectedChannel.policy.timelock_delta)
}
}, [selectedChannel])
const updatePolicy = async () => {
if (!selectedChannel) {
toast.error('No channel selected')
return
}
if (!baseFeeMsat || !feeRatePpm || !maxHtlcMsat || !minHtlcMsat || !timeLockDelta) {
toast.error('Please enter all fields')
return
}
const client = await getNostrClient(adminSource.pasteField, adminSource.keys)
const res = await client.UpdateChannelPolicy({
policy: { base_fee_msat: baseFeeMsat, fee_rate_ppm: feeRatePpm, max_htlc_msat: maxHtlcMsat, min_htlc_msat: minHtlcMsat, timelock_delta: timeLockDelta },
update: { type: Types.UpdateChannelPolicyRequest_update_type.CHANNEL_POINT, channel_point: selectedChannel.channel_point }
})
if (res.status === 'ERROR') {
toast.error(res.reason)
return
}
toast.success('Policy updated successfully')
}
const closeChannel = async () => {
if (!selectedChannel) {
toast.error('No channel selected')
return
}
if (satsPerVByte <= 0) {
toast.error('Please enter a valid sats per vbyte')
return
}
const client = await getNostrClient(adminSource.pasteField, adminSource.keys)
const [txid, output] = selectedChannel.channel_point.split(':')
const res = await client.CloseChannel({
funding_txid: txid,
output_index: +output,
force: force,
sat_per_v_byte: satsPerVByte
})
if (res.status === 'ERROR') {
toast.error(res.reason)
return
}
toast.success('Channel closed successfully')
}
if (!selectedChannel) {
return null
}

if (openModal === 'showPolicy') {
return <PromptForActionModal title="Channel Policy"
actionText="OK"
actionType={ActionType.NORMAL}
closeModal={() => { deselect() }}
action={() => { deselect() }}
jsx={<>
{selectedChannel.policy && <div>
<p>Base fee Msat: {selectedChannel.policy.base_fee_msat}</p>
<p>Fee rate Ppm: {selectedChannel.policy.fee_rate_ppm}</p>
<p>Max HTLC Msat: {selectedChannel.policy.max_htlc_msat}</p>
<p>Min HTLC Msat: {selectedChannel.policy.min_htlc_msat}</p>
<p>Time Lock Delta: {selectedChannel.policy.timelock_delta}</p>
<button onClick={e => { setOpenModal('updatePolocy') }}>EDIT POLICY</button>
<button onClick={e => { setOpenModal('closeChannel') }}>CLOSE CHANNEL</button>
</div>}
{!selectedChannel.policy && <div>No Policy found for channel</div>}
</>}
/>
}
if (openModal === 'updatePolocy') {
return <PromptForActionModal title="Update Policy"
actionText="Update Policy"
actionType={ActionType.NORMAL}
closeModal={() => { setOpenModal('showPolicy') }}
action={() => { updatePolicy(); setOpenModal('showPolicy') }}
jsx={<>
{selectedChannel.policy && <div>
<div>
<label>Base Fee Msat:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="baseFeeMsat" value={baseFeeMsat} onChange={e => { setBaseFeeMsat(+e.target.value) }}></input>
</div>
<div>
<label>Fee Rate Ppm:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="feeRatePpm" value={feeRatePpm} onChange={e => { setFeeRatePpm(+e.target.value) }}></input>
</div>
<div>
<label>Max HTLC Msat:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="maxHtlcMsat" value={maxHtlcMsat} onChange={e => { setMaxHtlcMsat(+e.target.value) }}></input>
</div>
<div>
<label>Min HTLC Msat:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="minHtlcMsat" value={minHtlcMsat} onChange={e => { setMinHtlcMsat(+e.target.value) }}></input>
</div>
<div>
<label>Time Lock Delta:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="timeLockDelta" value={timeLockDelta} onChange={e => { setTimeLockDelta(+e.target.value) }}></input>
</div>
</div>}
</>}
/>
}
if (openModal === 'closeChannel') {
return <PromptForActionModal title="Close Channel"
actionText={force ? "Force Close Channel" : "Close Channel"}
actionType={ActionType.NORMAL}
closeModal={() => { setOpenModal('showPolicy') }}
action={() => { closeChannel(); setOpenModal('showPolicy') }}
jsx={<>
<p>Are you sure you want to close the channel?</p>
<div>
<label >Sats per VByte:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="satPerVByte" value={satsPerVByte} onChange={e => { setSatsPerVByte(+e.target.value) }}></input>
</div>
<div>
<label >Force:</label>
<Checkbox state={force} setState={(e) => { setForce(e.target.checked) }} id="forceClose" />
</div>
</>}
/>
}

return null
};
100 changes: 100 additions & 0 deletions src/Pages/Channels/OpenChannel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useEffect, useMemo, useState } from "react";
import * as Icons from "../../Assets/SvgIconLibrary";
import { useSelector } from "../../State/store";
import { getNostrClient } from "../../Api";
import { NostrKeyPair } from "../../Api/nostrHandler";
import PromptForActionModal, { ActionType } from "../../Components/Modals/PromptForActionModal";
import { SpendFrom } from "../../globalTypes";
import { toast } from "react-toastify";

export const OpenChannel = ({ adminSource }: { adminSource: SpendFrom }) => {
const [openModal, setOpenModal] = useState<'addPeer' | 'openChannel' | ''>('');
const [peerUri, setPeerUri] = useState<string>('');
const [peerPubkey, setPeerPubkey] = useState<string>('');
const [channelAmount, setChannelAmount] = useState<number>(0);
const [satsPerVByte, setSatsPerVByte] = useState<number>(0);
const addPeer = async () => {
if (!peerUri) {
toast.error('Please enter a valid peer uri')
return
}
const [pubkey, addr] = peerUri.split('@')
if (!pubkey || !addr) {
toast.error('Please enter a valid peer uri')
return
}
const [host, port] = addr.split(':')
if (!host || !port || isNaN(+port)) {
toast.error('Please enter a valid peer uri')
return
}
const client = await getNostrClient(adminSource.pasteField, adminSource.keys)
const res = await client.AddPeer({ pubkey, host, port: +port })
if (res.status === 'ERROR') {
toast.error(res.reason)
return
}
toast.success('Peer added successfully')
}

const openChannel = async () => {
if (!peerPubkey) {
toast.error('Please enter a valid peer pubkey')
return
}
if (channelAmount <= 0) {
toast.error('Please enter a valid channel amount')
return
}
if (satsPerVByte <= 0) {
toast.error('Please enter a valid sats per vbyte')
return
}
const client = await getNostrClient(adminSource.pasteField, adminSource.keys)
const res = await client.OpenChannel({ node_pubkey: peerPubkey, local_funding_amount: channelAmount, sat_per_v_byte: satsPerVByte })
if (res.status === 'ERROR') {
toast.error(res.reason)
return
}
toast.success('Channel opened successfully')
}

return (
<div>
<div>
<button onClick={() => setOpenModal('addPeer')}>ADD PEER</button>
<button onClick={() => setOpenModal('openChannel')}>ADD CHANNEL</button>

</div>
{openModal === 'addPeer' && <PromptForActionModal title="Add Peer"
actionText="Add Peer"
actionType={ActionType.NORMAL}
closeModal={() => { setOpenModal('') }}
action={() => { addPeer(); setOpenModal('') }}
jsx={<>
<label>Peer Uri:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="pubkey@address:port" value={peerUri} onChange={e => { setPeerUri(e.target.value) }}></input></>}
/>}
{openModal === 'openChannel' && <PromptForActionModal title="Open Channel"
actionText="Open Channel"
actionType={ActionType.NORMAL}
closeModal={() => { setOpenModal('') }}
action={() => { openChannel(); setOpenModal('') }}
jsx={<>
<div>
<label>Peer Pubkey:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="pubkey" value={peerPubkey} onChange={e => { setPeerPubkey(e.target.value) }}></input>
</div>
<div>
<label>Channel Amount:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="amount" value={channelAmount} onChange={e => { setChannelAmount(+e.target.value) }}></input>
</div>
<div>
<label>Sats Per VByte:</label>
<input type="text" style={{ backgroundColor: 'black' }} placeholder="satsPerVByte" value={satsPerVByte} onChange={e => { setSatsPerVByte(+e.target.value) }}></input>
</div>
</>}
/>}
</div>
);
};
55 changes: 34 additions & 21 deletions src/Pages/Channels/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import * as Icons from "../../Assets/SvgIconLibrary";
import { useSelector } from "../../State/store";
import { getNostrClient } from "../../Api";
import { NostrKeyPair } from "../../Api/nostrHandler";
import PromptForActionModal, { ActionType } from "../../Components/Modals/PromptForActionModal";
import { OpenChannel } from "./OpenChannel";
import { EditChannel, SelectedChannel } from "./EditChannel";
import * as Types from '../../Api/pub/autogenerated/ts/types'
interface OfflineChannel {
id: number;
avatar: string;
name: string;
subNode: string;
timeStamp?: number;
satAmount: number;
channel: Types.OpenChannel
}

interface ActiveChannel {
Expand All @@ -18,16 +23,32 @@ interface ActiveChannel {
name: string;
localSatAmount: number;
RemoteSatAmount: number;
channel: Types.OpenChannel
}



export const Channels = () => {
const [maxBalance, setMaxBalance] = useState<number>(0);
const [activeChannels, setActiveChannels] = useState<ActiveChannel[]>([]);
const [offlineChannels, setOfflineChannels] = useState<OfflineChannel[]>([]);
const [selectedChannel, setSelectedChannel] = useState<Types.OpenChannel | null>(null);


const spendSources = useSelector(state => state.spendSource)
const selectedSource = useMemo(() => {
return spendSources.order.find(p => !!spendSources.sources[p].adminToken)
}, [spendSources])
useEffect(() => {
if (!selectedSource) {
throw new Error("admin source not found")
}
const source = spendSources.sources[selectedSource]
if (!source || !source.adminToken) {
throw new Error("admin source not found")
}
fetchChannels(source.pasteField, source.keys)
}, [selectedSource])
const fetchChannels = async (nprofile: string, keys: NostrKeyPair) => {
const client = await getNostrClient(nprofile, keys)
const res = await client.ListChannels()
Expand All @@ -45,9 +66,9 @@ export const Channels = () => {
max = c.remote_balance
}
if (c.active) {
active.push({ avatar: "", id: i, name: c.label, localSatAmount: c.local_balance, RemoteSatAmount: c.remote_balance })
active.push({ avatar: "", id: i, name: c.label, localSatAmount: c.local_balance, RemoteSatAmount: c.remote_balance, channel: c })
} else {
offline.push({ avatar: "", id: i, name: c.label, satAmount: c.capacity, timeStamp: c.lifetime, subNode: "Initiate force-close" })
offline.push({ avatar: "", id: i, name: c.label, satAmount: c.capacity, timeStamp: c.lifetime, subNode: "Initiate force-close", channel: c })
}
})
setMaxBalance(max)
Expand All @@ -56,16 +77,7 @@ export const Channels = () => {

}

useEffect(() => {
if (!selectedSource) {
throw new Error("admin source not found")
}
const source = spendSources.sources[selectedSource]
if (!source || !source.adminToken) {
throw new Error("admin source not found")
}
fetchChannels(source.pasteField, source.keys)
}, [selectedSource])

const totalSatAmount: number = offlineChannels.reduce(
(acc, obj) => acc + obj.satAmount,
0
Expand Down Expand Up @@ -98,7 +110,7 @@ export const Channels = () => {
</div>
<div className="channel-group">
{offlineChannels.map((channel: OfflineChannel, index: number) => (
<div className="channel" key={index}>
<div className="channel" key={index} onClick={() => { setSelectedChannel(channel.channel); }}>
<div>
<div className="avatar">
<img
Expand All @@ -120,13 +132,12 @@ export const Channels = () => {
</div>
<div className="time">
<span>
{`${
channel.timeStamp
? channel.timeStamp > 1000
? "💀 Last seen 10 days ago"
: "🔍 Last seen 2 hours ago"
: "🔗 Pending Force Close"
}`}
{`${channel.timeStamp
? channel.timeStamp > 1000
? "💀 Last seen 10 days ago"
: "🔍 Last seen 2 hours ago"
: "🔗 Pending Force Close"
}`}
</span>
</div>
</div>
Expand Down Expand Up @@ -159,7 +170,7 @@ export const Channels = () => {
</div>
<div className="channel-group">
{activeChannels.map((channel: ActiveChannel, index: number) => (
<div className="channel" key={index}>
<div className="channel" key={index} onClick={() => { setSelectedChannel(channel.channel); }}>
<div>
<div className="avatar">
<img
Expand Down Expand Up @@ -200,6 +211,8 @@ export const Channels = () => {
</div>
</div>
</div>
<OpenChannel adminSource={spendSources.sources[selectedSource || ""]} />
<EditChannel adminSource={spendSources.sources[selectedSource || ""]} selectedChannel={selectedChannel} deselect={() => setSelectedChannel(null)} />
<div className="Channels_footer">
Connected to <br />
{spendSources.sources[selectedSource || ""].pasteField}
Expand Down

0 comments on commit be92bb9

Please sign in to comment.