chore: 🔧 add bos score to channels (#207)

This commit is contained in:
Anthony Potdevin 2021-01-08 22:27:35 +01:00 • committed by GitHub
parent 343f0e79fd
commit e438f85ddf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 726 additions and 402 deletions

View file

@ -96,6 +96,7 @@ export const channelTypes = gql`
channel_age: Int!
pending_payments: [pendingPaymentType]!
pending_resume: pendingResumeType!
bosScore: BosScore
}
type closeChannelType {

View file

@ -105,6 +105,18 @@ const getBosNodeScoresQuery = `
}
`;
const getLastNodeScoreQuery = `
query GetLastNodeScore($publicKey: String!, $token: String!) {
getLastNodeScore(publicKey: $publicKey, token: $token) {
alias
public_key
score
updated
position
}
}
`;
export const tbaseResolvers = {
Query: {
getBaseInfo: async (_: undefined, __: undefined, context: ContextType) => {
@ -318,4 +330,42 @@ export const tbaseResolvers = {
return true;
},
},
channelType: {
bosScore: async (
{
partner_public_key,
}: {
partner_public_key: string;
},
_: undefined,
{ tokenAuth }: ContextType
) => {
if (!tokenAuth) {
return null;
}
const { data, error } = await graphqlFetchWithProxy(
appUrls.tbase,
getLastNodeScoreQuery,
{
publicKey: partner_public_key,
token: tokenAuth,
}
);
if (error) {
return null;
}
return (
data?.getLastNodeScore || {
alias: '',
public_key: partner_public_key,
score: 0,
updated: '',
position: 0,
}
);
},
},
};

View file

@ -54,6 +54,8 @@ interface GetPriceProps {
noUnit?: boolean;
}
export type FormatFnType = (options: GetPriceProps) => string;
export const getPrice = (
currency: string,
displayValues: boolean,

View file

@ -31,6 +31,9 @@ export type GetChannelsQuery = (
{ __typename?: 'nodePolicyType' }
& Pick<Types.NodePolicyType, 'base_fee_mtokens' | 'fee_rate' | 'cltv_delta'>
)> }
)>, bosScore?: Types.Maybe<(
{ __typename?: 'BosScore' }
& Pick<Types.BosScore, 'alias' | 'public_key' | 'score' | 'updated' | 'position'>
)> }
)>> }
);
@ -93,6 +96,13 @@ export const GetChannelsDocument = gql`
cltv_delta
}
}
bosScore {
alias
public_key
score
updated
position
}
}
}
`;

View file

@ -260,6 +260,7 @@ Object {
"data": Object {
"getChannels": Array [
Object {
"bosScore": null,
"capacity": 1000,
"channel_age": 123356,
"commit_transaction_fee": 1000,
@ -310,6 +311,7 @@ Object {
"unsettled_balance": 1000,
},
Object {
"bosScore": null,
"capacity": 1000,
"channel_age": 123356,
"commit_transaction_fee": 1000,
@ -360,6 +362,7 @@ Object {
"unsettled_balance": 1000,
},
Object {
"bosScore": null,
"capacity": 1000,
"channel_age": 123356,
"commit_transaction_fee": 1000,

View file

@ -57,6 +57,13 @@ export const GET_CHANNELS = gql`
cltv_delta
}
}
bosScore {
alias
public_key
score
updated
position
}
}
}
`;

View file

@ -680,6 +680,7 @@ export type ChannelType = {
channel_age: Scalars['Int'];
pending_payments: Array<Maybe<PendingPaymentType>>;
pending_resume: PendingResumeType;
bosScore?: Maybe<BosScore>;
};
export type CloseChannelType = {

View file

@ -0,0 +1,166 @@
import { FC } from 'react';
import { BalanceBars, SingleBar, SumBar } from 'src/components/balance';
import { ProgressBar } from 'src/components/generic/CardGeneric';
import { FormatFnType } from 'src/components/price/Price';
import { useConfigState } from 'src/context/ConfigContext';
import { ChannelType } from 'src/graphql/types';
import { getPercent } from 'src/utils/helpers';
import { ChannelStatsColumn, ChannelStatsLine } from './Channel.style';
import { WUMBO_MIN_SIZE } from './Channels';
const MAX_HTLCS = 483;
const getBar = (top: number, bottom: number) => {
const percent = (top / bottom) * 100;
return Math.min(percent, 100);
};
type ChannelBarsProps = {
info: ChannelType;
format: FormatFnType;
details: {
biggestRateFee: number;
biggestBaseFee: number;
biggestPartner: number;
mostChannels: number;
biggest: number;
};
};
export const ChannelBars: FC<ChannelBarsProps> = ({
info,
format,
details: {
biggestRateFee,
biggestBaseFee,
biggestPartner,
mostChannels,
biggest,
},
}) => {
const {
partner_fee_info,
partner_node_info,
local_balance,
remote_balance,
pending_resume,
} = info;
const { incoming_amount = 200, outgoing_amount = 150 } = pending_resume;
const { capacity: partnerNodeCapacity = 0, channel_count } =
partner_node_info?.node || {};
const { base_fee_mtokens, fee_rate } =
partner_fee_info?.partner_node_policies || {};
const { base_fee_mtokens: node_base, fee_rate: node_rate } =
partner_fee_info?.node_policies || {};
const { channelBarType, subBar } = useConfigState();
const maxRate = Math.min(fee_rate || 0, 10000);
const maxNodeRate = Math.min(node_rate || 0, 10000);
const localBalance = format({
amount: local_balance,
breakNumber: true,
noUnit: true,
});
const remoteBalance = format({
amount: remote_balance,
breakNumber: true,
noUnit: true,
});
switch (channelBarType) {
case 'fees':
return (
<ChannelStatsColumn>
<BalanceBars
local={getBar(maxNodeRate, biggestRateFee)}
remote={getBar(maxRate, biggestRateFee)}
height={10}
/>
<BalanceBars
local={getBar(Number(node_base), biggestBaseFee)}
remote={getBar(Number(base_fee_mtokens), biggestBaseFee)}
height={10}
/>
</ChannelStatsColumn>
);
case 'size':
return (
<ChannelStatsColumn>
<ChannelStatsLine>
<ProgressBar
order={0}
percent={getBar(Number(partnerNodeCapacity), biggestPartner)}
/>
<ProgressBar
order={4}
percent={getBar(
biggestPartner - Number(partnerNodeCapacity),
biggestPartner
)}
/>
</ChannelStatsLine>
{channel_count && (
<ChannelStatsLine>
<ProgressBar
order={6}
percent={getBar(channel_count, mostChannels)}
/>
<ProgressBar
order={4}
percent={getBar(mostChannels - channel_count, mostChannels)}
/>
</ChannelStatsLine>
)}
</ChannelStatsColumn>
);
case 'proportional':
return (
<ChannelStatsColumn>
{subBar === 'fees' && (
<SingleBar value={getBar(maxRate, 2000)} height={4} />
)}
<BalanceBars
local={getBar(local_balance, biggest)}
remote={getBar(remote_balance, biggest)}
formatLocal={localBalance}
formatRemote={remoteBalance}
withBorderColor={local_balance + remote_balance >= WUMBO_MIN_SIZE}
/>
</ChannelStatsColumn>
);
case 'htlcs':
return (
<ChannelStatsColumn>
{subBar === 'fees' && (
<SingleBar value={getBar(maxRate, 2000)} height={4} />
)}
<SumBar
values={[
getPercent(incoming_amount, MAX_HTLCS - incoming_amount),
getPercent(outgoing_amount, MAX_HTLCS - outgoing_amount),
]}
/>
</ChannelStatsColumn>
);
default:
return (
<ChannelStatsColumn>
{subBar === 'fees' && (
<SingleBar value={getBar(maxRate, 2000)} height={4} />
)}
<BalanceBars
local={getPercent(local_balance, remote_balance)}
remote={getPercent(remote_balance, local_balance)}
formatLocal={localBalance}
formatRemote={remoteBalance}
/>
</ChannelStatsColumn>
);
}
};

View file

@ -0,0 +1,106 @@
import { FC } from 'react';
import { renderLine } from 'src/components/generic/helpers';
import { Separation, SubTitle } from 'src/components/generic/Styled';
import { FormatFnType } from 'src/components/price/Price';
import { useConfigState } from 'src/context/ConfigContext';
import { ChannelType } from 'src/graphql/types';
const MAX_HTLCS = 483;
type ChannelBarsInfoProps = {
info: ChannelType;
format: FormatFnType;
};
export const ChannelBarsInfo: FC<ChannelBarsInfoProps> = ({
info: {
local_balance,
remote_balance,
partner_fee_info,
partner_node_info,
pending_resume,
},
format,
}) => {
const { channelBarType } = useConfigState();
const {
total_amount,
incoming_amount = 200,
outgoing_amount = 150,
} = pending_resume;
const { capacity: partnerNodeCapacity = 0, channel_count } =
partner_node_info?.node || {};
const { base_fee_mtokens, fee_rate } =
partner_fee_info?.partner_node_policies || {};
const { base_fee_mtokens: node_base, fee_rate: node_rate } =
partner_fee_info?.node_policies || {};
const feeRate = format({ amount: fee_rate, override: 'ppm' });
const nodeFeeRate = format({ amount: node_rate, override: 'ppm' });
const formatLocal = format({ amount: local_balance });
const formatRemote = format({ amount: remote_balance });
const nodeCapacity = format({ amount: partnerNodeCapacity });
const baseFee = format({
amount: Number(base_fee_mtokens) / 1000,
override: 'sat',
});
const nodeBaseFee = format({
amount: Number(node_base) / 1000,
override: 'sat',
});
switch (channelBarType) {
case 'fees':
return (
<>
{renderLine('Fee Rate', nodeFeeRate)}
{renderLine('Partner Fee Rate', feeRate)}
<Separation />
{renderLine('Base Fee', nodeBaseFee)}
{renderLine('Partner Base Fee', baseFee)}
</>
);
case 'size':
return (
<>
{renderLine('Partner Capacity', nodeCapacity)}
{renderLine('Partner Channels', channel_count)}
</>
);
case 'proportional':
return (
<>
{renderLine('Local Balance', formatLocal)}
{renderLine('Remote Balance', formatRemote)}
</>
);
case 'htlcs':
return (
<>
<SubTitle>Pending HTLCS</SubTitle>
{renderLine('Total', `${total_amount}/${MAX_HTLCS}`)}
{renderLine(
'Incoming',
`${incoming_amount}/${MAX_HTLCS - outgoing_amount}`
)}
{renderLine(
'Outgoing',
`${outgoing_amount}/${MAX_HTLCS - incoming_amount}`
)}
</>
);
default:
return (
<>
{renderLine('Local Balance', formatLocal)}
{renderLine('Remote Balance', formatRemote)}
</>
);
}
};

View file

@ -0,0 +1,62 @@
import { FC } from 'react';
import {
getDateDif,
getFormatDate,
renderLine,
} from 'src/components/generic/helpers';
import { DarkSubTitle } from 'src/components/generic/Styled';
import { Link } from 'src/components/link/Link';
import { BosScore } from 'src/graphql/types';
import { chartColors } from 'src/styles/Themes';
import styled from 'styled-components';
const S = {
missingToken: styled.div`
width: 100%;
border: 1px solid ${chartColors.darkyellow};
padding: 8px;
border-radius: 4px;
margin: 8px 0 0;
font-size: 14px;
text-align: center;
:hover {
background-color: ${chartColors.darkyellow};
}
`,
};
export const ChannelBosScore: FC<{ score?: BosScore | null }> = ({ score }) => {
if (!score) {
return (
<>
BOS Score
<Link to={'/token'} noStyling={true}>
<S.missingToken>
Get a token to view this nodes latest BOS score and historical info.
</S.missingToken>
</Link>
</>
);
}
if (!score.alias) {
return (
<DarkSubTitle>This node has not appeared in the BOS list</DarkSubTitle>
);
}
return (
<>
BOS Score
{renderLine('Score', score.score)}
{renderLine('Position', score.position)}
{renderLine('Last Time on List', `${getDateDif(score.updated)} ago`)}
{renderLine('Last Time Date', getFormatDate(score.updated))}
{renderLine(
'Historical',
<Link to={`/scores/${score.public_key}`}>View History</Link>
)}
</>
);
};

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import ReactTooltip from 'react-tooltip';
import {
ArrowDown,
@ -9,56 +9,34 @@ import {
X,
} from 'react-feather';
import { ChannelType } from 'src/graphql/types';
import { BalanceBars, SingleBar, SumBar } from 'src/components/balance';
import {
useRebalanceState,
useRebalanceDispatch,
} from 'src/context/RebalanceContext';
import { ChangeDetails } from 'src/components/modal/changeDetails/ChangeDetails';
import {
getPercent,
formatSeconds,
blockToTime,
formatSats,
} from '../../../utils/helpers';
import { ProgressBar, MainInfo } from '../../../components/generic/CardGeneric';
import differenceInDays from 'date-fns/differenceInDays';
import { MainInfo } from '../../../components/generic/CardGeneric';
import {
SubCard,
Separation,
Sub4Title,
ResponsiveLine,
DarkSubTitle,
SubTitle,
} from '../../../components/generic/Styled';
import { useConfigState } from '../../../context/ConfigContext';
import {
getFormatDate,
getDateDif,
renderLine,
getTransactionLink,
getNodeLink,
} from '../../../components/generic/helpers';
import Modal from '../../../components/modal/ReactModal';
import { CloseChannel } from '../../../components/modal/closeChannel/CloseChannel';
import { ColorButton } from '../../../components/buttons/colorButton/ColorButton';
import { renderLine } from '../../../components/generic/helpers';
import { getPrice } from '../../../components/price/Price';
import { usePriceState } from '../../../context/PriceContext';
import {
ChannelNodeTitle,
ChannelBarSide,
ChannelIconPadding,
ChannelStatsColumn,
ChannelSingleLine,
ChannelStatsLine,
ChannelBalanceRow,
ChannelBalanceButton,
WumboTag,
ChannelAlias,
} from './Channel.style';
import { WUMBO_MIN_SIZE } from './Channels';
import { getTitleColor } from './helpers';
const MAX_HTLCS = 483;
import { ChannelDetails } from './ChannelDetails';
import { ChannelBars } from './ChannelBars';
import { ChannelBarsInfo } from './ChannelBarsInfo';
const getSymbol = (status: boolean) => {
return status ? <ArrowDown size={14} /> : <ArrowUp size={14} />;
@ -68,11 +46,6 @@ const getPrivate = (status: boolean) => {
return status && <EyeOff size={14} />;
};
const getBar = (top: number, bottom: number) => {
const percent = (top / bottom) * 100;
return Math.min(percent, 100);
};
interface ChannelCardProps {
channelInfo: ChannelType;
index: number;
@ -99,8 +72,7 @@ export const ChannelCard: React.FC<ChannelCardProps> = ({
const dispatch = useRebalanceDispatch();
const { inChannel, outChannel } = useRebalanceState();
const { channelBarType, channelBarStyle, subBar } = useConfigState();
const [modalOpen, setModalOpen] = useState<string>('none');
const { channelBarStyle } = useConfigState();
const { currency, displayValues } = useConfigState();
const priceContext = usePriceState();
@ -108,103 +80,39 @@ export const ChannelCard: React.FC<ChannelCardProps> = ({
const {
capacity,
commit_transaction_fee,
commit_transaction_weight,
id,
is_active,
is_closing,
is_opening,
is_partner_initiated,
is_private,
is_static_remote_key,
local_balance,
local_reserve,
partner_public_key,
received,
remote_balance,
remote_reserve,
sent,
time_offline,
time_online,
transaction_id,
transaction_vout,
unsettled_balance,
partner_node_info,
partner_fee_info,
channel_age,
pending_resume,
bosScore,
} = channelInfo;
const {
total_amount,
total_tokens,
incoming_tokens,
incoming_amount = 200,
outgoing_tokens,
outgoing_amount = 150,
} = pending_resume;
const barDetails = {
biggestRateFee,
biggestBaseFee,
biggestPartner,
mostChannels,
biggest,
};
let withinAWeek = false;
if (bosScore?.updated) {
withinAWeek = differenceInDays(new Date(), new Date(bosScore.updated)) < 7;
}
const isBosNode = !!bosScore?.alias && withinAWeek;
const isIn = inChannel?.id === id;
const isOut = outChannel?.id === id;
const {
alias,
capacity: partnerNodeCapacity = 0,
channel_count,
updated_at,
} = partner_node_info?.node || {};
const { base_fee_mtokens, fee_rate, cltv_delta } =
partner_fee_info?.partner_node_policies || {};
const {
base_fee_mtokens: node_base,
fee_rate: node_rate,
cltv_delta: node_cltv,
max_htlc_mtokens,
min_htlc_mtokens,
} = partner_fee_info?.node_policies || {};
const { alias } = partner_node_info?.node || {};
const formatBalance = format({ amount: capacity });
const formatLocal = format({ amount: local_balance });
const formatRemote = format({ amount: remote_balance });
const formatReceived = format({ amount: received });
const formatSent = format({ amount: sent });
const commitFee = format({ amount: commit_transaction_fee });
const commitWeight = format({ amount: commit_transaction_weight });
const localReserve = format({ amount: local_reserve });
const remoteReserve = format({ amount: remote_reserve });
const nodeCapacity = format({ amount: partnerNodeCapacity });
const localBalance = format({
amount: local_balance,
breakNumber: true,
noUnit: true,
});
const remoteBalance = format({
amount: remote_balance,
breakNumber: true,
noUnit: true,
});
const baseFee = format({
amount: Number(base_fee_mtokens) / 1000,
override: 'sat',
});
const nodeBaseFee = format({
amount: Number(node_base) / 1000,
override: 'sat',
});
const maxRate = Math.min(fee_rate || 0, 10000);
const feeRate = format({ amount: fee_rate, override: 'ppm' });
const maxNodeRate = Math.min(node_rate || 0, 10000);
const nodeFeeRate = format({ amount: node_rate, override: 'ppm' });
const max_htlc = Number(max_htlc_mtokens) / 1000;
const min_htlc = Number(min_htlc_mtokens) / 1000;
const handleClick = () => {
if (indexOpen === index) {
@ -214,256 +122,6 @@ export const ChannelCard: React.FC<ChannelCardProps> = ({
}
};
const renderPartner = () =>
alias ? (
<>
{renderLine('Node Capacity:', nodeCapacity)}
{renderLine('Channel Count:', channel_count)}
{renderLine(
'Last Update:',
`${getDateDif(updated_at)} ago (${getFormatDate(updated_at)})`
)}
{renderLine('Base Fee:', baseFee)}
{renderLine('Fee Rate:', `${feeRate}`)}
{renderLine('CTLV Delta:', cltv_delta)}
</>
) : (
<DarkSubTitle>Partner node not found</DarkSubTitle>
);
const renderWumboInfo = () => {
if (local_balance + remote_balance >= WUMBO_MIN_SIZE) {
return (
<>
<Separation />
<WumboTag>This channel is Wumbo!</WumboTag>
</>
);
}
return null;
};
const renderDetails = () => {
return (
<>
{renderWumboInfo()}
<Separation />
{renderLine('Status:', is_active ? 'Active' : 'Not Active')}
{renderLine('Is Opening:', is_opening ? 'True' : 'False')}
{renderLine('Is Closing:', is_closing ? 'True' : 'False')}
{renderLine(
'Balancedness:',
getPercent(local_balance, remote_balance) / 100
)}
<Separation />
{renderLine('Base Fee:', nodeBaseFee)}
{renderLine('Fee Rate:', `${nodeFeeRate}`)}
{renderLine('CTLV Delta:', node_cltv)}
{renderLine('Max HTLC (sats)', formatSats(max_htlc))}
{renderLine('Min HTLC (sats)', formatSats(min_htlc))}
<ColorButton
fullWidth={true}
withBorder={true}
arrow={true}
onClick={() => setModalOpen('details')}
>
Update Details
</ColorButton>
<Separation />
{renderLine('Local Balance:', formatLocal)}
{renderLine('Remote Balance:', formatRemote)}
{renderLine('Received:', formatReceived)}
{renderLine('Sent:', formatSent)}
{renderLine('Local Reserve:', localReserve)}
{renderLine('Remote Reserve:', remoteReserve)}
<Separation />
{renderLine('Node Public Key:', getNodeLink(partner_public_key))}
{renderLine('Transaction Id:', getTransactionLink(transaction_id))}
<Separation />
<Sub4Title>Pending HTLCS</Sub4Title>
{!total_amount && renderLine('Total Amount', 'None')}
{renderLine('Total Amount', total_amount)}
{renderLine('Total Tokens', total_tokens)}
{renderLine('Incoming Tokens', incoming_tokens)}
{renderLine('Outgoing Tokens', outgoing_tokens)}
{renderLine('Incoming Amount', incoming_amount)}
{renderLine('Outgoing Amount', outgoing_amount)}
<Separation />
{renderLine('Channel Age:', blockToTime(channel_age))}
{renderLine('Channel Block Age:', channel_age)}
{renderLine('Channel Id:', id)}
{renderLine('Commit Fee:', commitFee)}
{renderLine('Commit Weight:', commitWeight)}
<Separation />
{renderLine(
'Is Static Remote Key:',
is_static_remote_key ? 'True' : 'False'
)}
{renderLine('Time Offline:', formatSeconds(time_offline))}
{renderLine('Time Online:', formatSeconds(time_online))}
{renderLine('Transaction Vout:', transaction_vout)}
{renderLine('Unsettled Balance:', unsettled_balance)}
<Separation />
<Sub4Title>Partner Node Info</Sub4Title>
{renderPartner()}
<Separation />
<ColorButton
fullWidth={true}
withBorder={true}
arrow={true}
onClick={() => setModalOpen('close')}
>
Close Channel
</ColorButton>
</>
);
};
const renderBars = () => {
switch (channelBarType) {
case 'fees':
return (
<ChannelStatsColumn>
<BalanceBars
local={getBar(maxNodeRate, biggestRateFee)}
remote={getBar(maxRate, biggestRateFee)}
height={10}
/>
<BalanceBars
local={getBar(Number(node_base), biggestBaseFee)}
remote={getBar(Number(base_fee_mtokens), biggestBaseFee)}
height={10}
/>
</ChannelStatsColumn>
);
case 'size':
return (
<ChannelStatsColumn>
<ChannelStatsLine>
<ProgressBar
order={0}
percent={getBar(Number(partnerNodeCapacity), biggestPartner)}
/>
<ProgressBar
order={4}
percent={getBar(
biggestPartner - Number(partnerNodeCapacity),
biggestPartner
)}
/>
</ChannelStatsLine>
{channel_count && (
<ChannelStatsLine>
<ProgressBar
order={6}
percent={getBar(channel_count, mostChannels)}
/>
<ProgressBar
order={4}
percent={getBar(mostChannels - channel_count, mostChannels)}
/>
</ChannelStatsLine>
)}
</ChannelStatsColumn>
);
case 'proportional':
return (
<ChannelStatsColumn>
{subBar === 'fees' && (
<SingleBar value={getBar(maxRate, 2000)} height={4} />
)}
<BalanceBars
local={getBar(local_balance, biggest)}
remote={getBar(remote_balance, biggest)}
formatLocal={localBalance}
formatRemote={remoteBalance}
withBorderColor={local_balance + remote_balance >= WUMBO_MIN_SIZE}
/>
</ChannelStatsColumn>
);
case 'htlcs':
return (
<ChannelStatsColumn>
{subBar === 'fees' && (
<SingleBar value={getBar(maxRate, 2000)} height={4} />
)}
<SumBar
values={[
getPercent(incoming_amount, MAX_HTLCS - incoming_amount),
getPercent(outgoing_amount, MAX_HTLCS - outgoing_amount),
]}
/>
</ChannelStatsColumn>
);
default:
return (
<ChannelStatsColumn>
{subBar === 'fees' && (
<SingleBar value={getBar(maxRate, 2000)} height={4} />
)}
<BalanceBars
local={getPercent(local_balance, remote_balance)}
remote={getPercent(remote_balance, local_balance)}
formatLocal={localBalance}
formatRemote={remoteBalance}
/>
</ChannelStatsColumn>
);
}
};
const renderBarsInfo = () => {
switch (channelBarType) {
case 'fees':
return (
<>
{renderLine('Fee Rate', nodeFeeRate)}
{renderLine('Partner Fee Rate', feeRate)}
<Separation />
{renderLine('Base Fee', nodeBaseFee)}
{renderLine('Partner Base Fee', baseFee)}
</>
);
case 'size':
return (
<>
{renderLine('Partner Capacity', nodeCapacity)}
{renderLine('Partner Channels', channel_count)}
</>
);
case 'proportional':
return (
<>
{renderLine('Local Balance', formatLocal)}
{renderLine('Remote Balance', formatRemote)}
</>
);
case 'htlcs':
return (
<>
<SubTitle>Pending HTLCS</SubTitle>
{renderLine('Total', `${total_amount}/${MAX_HTLCS}`)}
{renderLine(
'Incoming',
`${incoming_amount}/${MAX_HTLCS - outgoing_amount}`
)}
{renderLine(
'Outgoing',
`${outgoing_amount}/${MAX_HTLCS - incoming_amount}`
)}
</>
);
default:
return (
<>
{renderLine('Local Balance', formatLocal)}
{renderLine('Remote Balance', formatRemote)}
</>
);
}
};
const getSubCardProps = () => {
switch (channelBarStyle) {
case 'ultracompact':
@ -496,7 +154,12 @@ export const ChannelCard: React.FC<ChannelCardProps> = ({
data-for={`node_status_tip_${index}`}
>
<ChannelAlias
textColor={getTitleColor(is_active, is_opening, is_closing)}
textColor={getTitleColor(
is_active,
is_opening,
is_closing,
isBosNode
)}
>
{alias || partner_public_key?.substring(0, 6)}
</ChannelAlias>
@ -514,7 +177,11 @@ export const ChannelCard: React.FC<ChannelCardProps> = ({
)}
</ChannelNodeTitle>
<ChannelBarSide data-tip data-for={`node_balance_tip_${index}`}>
{renderBars()}
<ChannelBars
info={channelInfo}
details={barDetails}
format={format}
/>
{channelBarStyle === 'balancing' && (
<ChannelBalanceRow>
<ChannelBalanceButton
@ -544,12 +211,15 @@ export const ChannelCard: React.FC<ChannelCardProps> = ({
</ChannelBarSide>
</ResponsiveLine>
</MainInfo>
{index === indexOpen && renderDetails()}
{index === indexOpen && (
<ChannelDetails info={channelInfo} format={format} />
)}
<ReactTooltip
id={`node_status_tip_${index}`}
effect={'solid'}
place={'bottom'}
>
{isBosNode && renderLine('BOS Node', 'True')}
{renderLine('Status:', is_active ? 'Active' : 'Not Active')}
{is_opening && renderLine('Is Opening:', 'True')}
{is_closing && renderLine('Is Closing:', 'True')}
@ -559,31 +229,8 @@ export const ChannelCard: React.FC<ChannelCardProps> = ({
effect={'solid'}
place={'bottom'}
>
{renderBarsInfo()}
<ChannelBarsInfo info={channelInfo} format={format} />
</ReactTooltip>
<Modal
isOpen={modalOpen !== 'none'}
closeCallback={() => setModalOpen('none')}
>
{modalOpen === 'close' ? (
<CloseChannel
callback={() => setModalOpen('none')}
channelId={id}
channelName={alias}
/>
) : (
<ChangeDetails
callback={() => setModalOpen('none')}
transaction_id={transaction_id}
transaction_vout={transaction_vout}
base_fee_mtokens={node_base}
max_htlc_mtokens={max_htlc_mtokens}
min_htlc_mtokens={min_htlc_mtokens}
fee_rate={node_rate}
cltv_delta={node_cltv}
/>
)}
</Modal>
</SubCard>
);
};

View file

@ -0,0 +1,247 @@
import { FC, useState } from 'react';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import {
getDateDif,
getFormatDate,
getNodeLink,
getTransactionLink,
renderLine,
} from 'src/components/generic/helpers';
import {
DarkSubTitle,
Separation,
Sub4Title,
} from 'src/components/generic/Styled';
import { ChangeDetails } from 'src/components/modal/changeDetails/ChangeDetails';
import { CloseChannel } from 'src/components/modal/closeChannel/CloseChannel';
import Modal from 'src/components/modal/ReactModal';
import { ChannelType } from 'src/graphql/types';
import {
blockToTime,
formatSats,
formatSeconds,
getPercent,
} from 'src/utils/helpers';
import { FormatFnType } from 'src/components/price/Price';
import { ChannelBosScore } from './ChannelBosScore';
import { WUMBO_MIN_SIZE } from './Channels';
import { WumboTag } from './Channel.style';
type ChannelDetailsProps = {
info: ChannelType;
format: FormatFnType;
};
export const ChannelDetails: FC<ChannelDetailsProps> = ({
info: {
commit_transaction_fee,
commit_transaction_weight,
id,
is_active,
is_closing,
is_opening,
is_static_remote_key,
local_balance,
local_reserve,
partner_public_key,
received,
remote_balance,
remote_reserve,
sent,
time_offline,
time_online,
transaction_id,
transaction_vout,
unsettled_balance,
partner_node_info,
partner_fee_info,
channel_age,
pending_resume,
bosScore,
},
format,
}) => {
const [modalOpen, setModalOpen] = useState<string>('none');
const {
total_amount,
total_tokens,
incoming_tokens,
incoming_amount = 200,
outgoing_tokens,
outgoing_amount = 150,
} = pending_resume;
const {
alias,
capacity: partnerNodeCapacity = 0,
channel_count,
updated_at,
} = partner_node_info?.node || {};
const { base_fee_mtokens, fee_rate, cltv_delta } =
partner_fee_info?.partner_node_policies || {};
const {
base_fee_mtokens: node_base,
fee_rate: node_rate,
cltv_delta: node_cltv,
max_htlc_mtokens,
min_htlc_mtokens,
} = partner_fee_info?.node_policies || {};
const formatLocal = format({ amount: local_balance });
const formatRemote = format({ amount: remote_balance });
const formatReceived = format({ amount: received });
const formatSent = format({ amount: sent });
const commitFee = format({ amount: commit_transaction_fee });
const commitWeight = format({ amount: commit_transaction_weight });
const localReserve = format({ amount: local_reserve });
const remoteReserve = format({ amount: remote_reserve });
const nodeCapacity = format({ amount: partnerNodeCapacity });
const baseFee = format({
amount: Number(base_fee_mtokens) / 1000,
override: 'sat',
});
const nodeBaseFee = format({
amount: Number(node_base) / 1000,
override: 'sat',
});
const feeRate = format({ amount: fee_rate, override: 'ppm' });
const nodeFeeRate = format({ amount: node_rate, override: 'ppm' });
const max_htlc = Number(max_htlc_mtokens) / 1000;
const min_htlc = Number(min_htlc_mtokens) / 1000;
const renderPartner = () =>
alias ? (
<>
{renderLine('Node Capacity:', nodeCapacity)}
{renderLine('Channel Count:', channel_count)}
{renderLine(
'Last Update:',
`${getDateDif(updated_at)} ago (${getFormatDate(updated_at)})`
)}
{renderLine('Base Fee:', baseFee)}
{renderLine('Fee Rate:', `${feeRate}`)}
{renderLine('CTLV Delta:', cltv_delta)}
</>
) : (
<DarkSubTitle>Partner node not found</DarkSubTitle>
);
const renderWumboInfo = () => {
if (local_balance + remote_balance >= WUMBO_MIN_SIZE) {
return (
<>
<Separation />
<WumboTag>This channel is Wumbo!</WumboTag>
</>
);
}
return null;
};
return (
<>
{renderWumboInfo()}
<Separation />
{renderLine('Status:', is_active ? 'Active' : 'Not Active')}
{renderLine('Is Opening:', is_opening ? 'True' : 'False')}
{renderLine('Is Closing:', is_closing ? 'True' : 'False')}
{renderLine(
'Balancedness:',
getPercent(local_balance, remote_balance) / 100
)}
<Separation />
<ChannelBosScore score={bosScore} />
<Separation />
{renderLine('Base Fee:', nodeBaseFee)}
{renderLine('Fee Rate:', `${nodeFeeRate}`)}
{renderLine('CTLV Delta:', node_cltv)}
{renderLine('Max HTLC (sats)', formatSats(max_htlc))}
{renderLine('Min HTLC (sats)', formatSats(min_htlc))}
<ColorButton
fullWidth={true}
withBorder={true}
arrow={true}
onClick={() => setModalOpen('details')}
>
Update Details
</ColorButton>
<Separation />
{renderLine('Local Balance:', formatLocal)}
{renderLine('Remote Balance:', formatRemote)}
{renderLine('Received:', formatReceived)}
{renderLine('Sent:', formatSent)}
{renderLine('Local Reserve:', localReserve)}
{renderLine('Remote Reserve:', remoteReserve)}
<Separation />
{renderLine('Node Public Key:', getNodeLink(partner_public_key))}
{renderLine('Transaction Id:', getTransactionLink(transaction_id))}
<Separation />
<Sub4Title>Pending HTLCS</Sub4Title>
{!total_amount && renderLine('Total Amount', 'None')}
{renderLine('Total Amount', total_amount)}
{renderLine('Total Tokens', total_tokens)}
{renderLine('Incoming Tokens', incoming_tokens)}
{renderLine('Outgoing Tokens', outgoing_tokens)}
{renderLine('Incoming Amount', incoming_amount)}
{renderLine('Outgoing Amount', outgoing_amount)}
<Separation />
{renderLine('Channel Age:', blockToTime(channel_age))}
{renderLine('Channel Block Age:', channel_age)}
{renderLine('Channel Id:', id)}
{renderLine('Commit Fee:', commitFee)}
{renderLine('Commit Weight:', commitWeight)}
<Separation />
{renderLine(
'Is Static Remote Key:',
is_static_remote_key ? 'True' : 'False'
)}
{renderLine('Time Offline:', formatSeconds(time_offline))}
{renderLine('Time Online:', formatSeconds(time_online))}
{renderLine('Transaction Vout:', transaction_vout)}
{renderLine('Unsettled Balance:', unsettled_balance)}
<Separation />
<Sub4Title>Partner Node Info</Sub4Title>
{renderPartner()}
<Separation />
<ColorButton
fullWidth={true}
withBorder={true}
arrow={true}
onClick={() => setModalOpen('close')}
>
Close Channel
</ColorButton>
<Modal
isOpen={modalOpen !== 'none'}
closeCallback={() => setModalOpen('none')}
>
{modalOpen === 'close' ? (
<CloseChannel
callback={() => setModalOpen('none')}
channelId={id}
channelName={alias}
/>
) : (
<ChangeDetails
callback={() => setModalOpen('none')}
transaction_id={transaction_id}
transaction_vout={transaction_vout}
base_fee_mtokens={node_base}
max_htlc_mtokens={max_htlc_mtokens}
min_htlc_mtokens={min_htlc_mtokens}
fee_rate={node_rate}
cltv_delta={node_cltv}
/>
)}
</Modal>
</>
);
};

View file

@ -1,17 +1,20 @@
import { themeColors } from 'src/styles/Themes';
import { chartColors, themeColors } from 'src/styles/Themes';
export const getTitleColor = (
active: boolean,
opening: boolean,
closing: boolean
closing: boolean,
isBosNode: boolean
): string | undefined => {
switch (true) {
case active:
return undefined;
case !active:
return 'red';
case opening:
case closing:
return themeColors.blue2;
case isBosNode && active:
return chartColors.darkyellow;
default:
return 'red';
return undefined;
}
};

View file

@ -22,7 +22,7 @@ const tooltipStyles = {
};
const getDate = (d: DataType) => new Date(d.date);
const getValue = (d: DataType) => d.value;
const getValue = (d: DataType) => d?.value || 0;
const bisectDate = bisector<DataType, Date>(d => new Date(d.date)).left;
type DataType = {

View file

@ -50,6 +50,10 @@ export const Graph = () => {
}))
.reverse();
if (!final.length) {
return null;
}
return (
<Wrapper>
<ParentSize>

View file

@ -1,7 +1,12 @@
import React, { FC, useEffect } from 'react';
import { useRouter } from 'next/router';
import { isArray } from 'underscore';
import { Card, CardWithTitle, SubTitle } from 'src/components/generic/Styled';
import {
Card,
CardWithTitle,
DarkSubTitle,
SubTitle,
} from 'src/components/generic/Styled';
import { LoadingCard } from 'src/components/loading/LoadingCard';
import { useBaseState } from 'src/context/BaseContext';
import { useGetBosNodeScoresQuery } from 'src/graphql/queries/__generated__/getBosNodeScores.generated';
@ -59,7 +64,15 @@ export const NodeScores: FC<NodeScoresProps> = ({
<CardWithTitle>
<SubTitle>Historical Scores</SubTitle>
<Card>
<Table withBorder={true} tableData={tableData} tableColumns={columns} />
{!tableData.length ? (
<DarkSubTitle>This node has no BOS score history</DarkSubTitle>
) : (
<Table
withBorder={true}
tableData={tableData}
tableColumns={columns}
/>
)}
</Card>
</CardWithTitle>
);

View file

@ -27,6 +27,7 @@ export const PaidCard: FC<{ id: string }> = ({ id }) => {
const [getToken, { data, loading }] = useCreateBaseTokenMutation({
onError: err => toast.error(getErrorContent(err)),
variables: { id },
refetchQueries: ['GetChannels'],
});
useEffect(() => {

View file

@ -15,6 +15,7 @@ export const RecoverToken = () => {
const dispatch = useBaseDispatch();
const [getToken, { data, loading }] = useCreateBaseTokenMutation({
onError: err => toast.error(getErrorContent(err)),
refetchQueries: ['GetChannels'],
});
useEffect(() => {