diff --git a/config.ts b/config.ts index f92fad5f..16a39c32 100644 --- a/config.ts +++ b/config.ts @@ -1,4 +1,4 @@ -export const environment: 'dev' | 'node' | 'web3' = 'node'; //'node'; +export const environment: 'dev' | 'node' | 'web3' = 'node'; //'node'; //'node'; // Smart Contracts export const mHOPR_TOKEN_SMART_CONTRACT_ADDRESS = '0x66225dE86Cac02b32f34992eb3410F59DE416698'; diff --git a/package.json b/package.json index c4cd1e71..2f9e8792 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,7 @@ "dev": "vite", "dev:clean": "npm run dev -- --force", "build-vite": "tsc && vite build", - "build-hub": "node ./scripts/prepare-build-hub.js && tsc && vite build && node ./scripts/post-build.js", - "build": "node ./scripts/prepare-build-node.js && tsc && vite build && node ./scripts/post-build.js", + "build": "tsc && vite build", "test": "echo \"Error: no test specified\" && exit 0", "serve": "vite preview --port 3000", "format": "prettier --write src/ .github/ *.ts *.json *.md", diff --git a/public/assets/scalar-removebg-preview.png b/public/assets/scalar-removebg-preview.png new file mode 100644 index 00000000..cc18facf Binary files /dev/null and b/public/assets/scalar-removebg-preview.png differ diff --git a/scripts/post-build.js b/scripts/post-build.js deleted file mode 100644 index 90ec348d..00000000 --- a/scripts/post-build.js +++ /dev/null @@ -1,13 +0,0 @@ -const fs = require('fs'); - -configPath = `${__dirname.replace('scripts', 'config.ts')}`; - -fs.unlink(configPath, function (err) { - if (err) { throw err } - else { - fs.rename(`${configPath}_`, configPath, function (err) { - if (err) throw err - console.log('Original config brought back.') - }); - } -}); \ No newline at end of file diff --git a/scripts/prepare-build-hub.js b/scripts/prepare-build-hub.js deleted file mode 100644 index 0160e133..00000000 --- a/scripts/prepare-build-hub.js +++ /dev/null @@ -1,22 +0,0 @@ -const fs = require('fs'); -configPath = `${__dirname.replace('scripts', 'config.ts')}`; - -fs.copyFile(configPath, `${configPath}_`, (err) => { - if (err) { - console.log("Error Found:", err); - } else { - console.log("Config backed up."); - - fs.readFile(configPath, function(err, data) { - if(err) throw err; - data = data.toString(); - data = data.replace(`export const environment: 'dev' | 'node' | 'web3' = `, `export const environment:('dev' | 'node' | 'web3') = 'web3'; //`); - fs.writeFile(configPath, data, function(err) { - err || console.log('Data replaced to build only Staking Hub.'); - }); - }); - - } -}); - - diff --git a/scripts/prepare-build-node.js b/scripts/prepare-build-node.js deleted file mode 100644 index 74b8f7b6..00000000 --- a/scripts/prepare-build-node.js +++ /dev/null @@ -1,25 +0,0 @@ -const fs = require('fs'); -configPath = `${__dirname.replace('scripts', 'config.ts')}`; - - -fs.copyFile(configPath, `${configPath}_`, (err) => { - if (err) { - console.log("Error Found:", err); - } else { - console.log("Config backed up."); - - fs.readFile(configPath, function(err, data) { - if(err) throw err; - data = data.toString(); - data = data.replace(`export const environment: 'dev' | 'node' | 'web3' = `, `export const environment:('dev' | 'node' | 'web3') = 'node'; //`); - fs.writeFile(configPath, data, function(err) { - err || console.log('Data replaced to build only Node Admin.'); - }); - }); - - } -}); - - - - diff --git a/src/components/ConnectNode/index.tsx b/src/components/ConnectNode/index.tsx index bd3b7e01..13c1eb54 100644 --- a/src/components/ConnectNode/index.tsx +++ b/src/components/ConnectNode/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import styled from '@emotion/styled'; import { Link, useNavigate } from 'react-router-dom'; -import { generateBase64Jazz } from '../../utils/functions'; +import { toHexMD5, generateBase64Jazz } from '../../utils/functions'; // Components import Modal from './modal'; @@ -62,10 +62,15 @@ const NodeButton = styled.div` .node-info { color: #414141; line-height: 12px; + height: 12px; + white-space: nowrap; } .node-info-localname { font-weight: 700; color: #000050; + height: 12px; + line-height: 12px; + white-space: nowrap; } `; @@ -98,11 +103,16 @@ export default function ConnectNode() { const error = useAppSelector((store) => store.auth.status.error); const openLoginModalToNode = useAppSelector((store) => store.auth.helper.openLoginModalToNode); const peerId = useAppSelector((store) => store.node.addresses.data.hopr); - const localName = useAppSelector((store) => store.auth.loginData.localName); + const localNameFromLocalStorage = useAppSelector((store) => store.auth.loginData.localName); + const jazzIconFromLocalStorage = useAppSelector((store) => store.auth.loginData.jazzIcon); + const nodeAddress = useAppSelector((store) => store.node.addresses.data.native); const localNameToDisplay = - localName && localName.length > 17 - ? `${localName?.substring(0, 5)}…${localName?.substring(localName.length - 11, localName.length)}` - : localName; + localNameFromLocalStorage && localNameFromLocalStorage.length > 17 + ? `${localNameFromLocalStorage?.substring(0, 5)}…${localNameFromLocalStorage?.substring( + localNameFromLocalStorage.length - 11, + localNameFromLocalStorage.length, + )}` + : localNameFromLocalStorage; const apiEndpoint = useAppSelector((store) => store.auth.loginData.apiEndpoint); const [nodeAddressIcon, set_nodeAddressIcon] = useState(null); const [anchorEl, setAnchorEl] = useState(null); // State variable to hold the anchor element for the menu @@ -124,10 +134,15 @@ export default function ConnectNode() { }, []); useEffect(() => { + if (!connected) set_nodeAddressIcon(null); if (!apiEndpoint) return; - const b64 = generateBase64Jazz(apiEndpoint); - if (b64) set_nodeAddressIcon(b64); - }, [apiEndpoint]); + console.log(jazzIconFromLocalStorage); + const md5 = toHexMD5(apiEndpoint); + const b64 = generateBase64Jazz( + nodeAddress ? nodeAddress : jazzIconFromLocalStorage ? jazzIconFromLocalStorage : md5, + ); + if (connected && b64) set_nodeAddressIcon(b64); + }, [connected, apiEndpoint, nodeAddress, jazzIconFromLocalStorage]); useEffect(() => { if (error) set_modalVisible(true); diff --git a/src/components/ConnectNode/modal.tsx b/src/components/ConnectNode/modal.tsx index bf9e46a5..eaa4c035 100644 --- a/src/components/ConnectNode/modal.tsx +++ b/src/components/ConnectNode/modal.tsx @@ -30,6 +30,7 @@ type ParsedNode = { value: string; apiEndpoint: string; apiToken: string; + jazzIcon?: string; }; type ConnectNodeModalProps = { @@ -138,8 +139,10 @@ function ConnectNodeModal(props: ConnectNodeModalProps) { const [nodesSavedLocallyParsed, set_nodesSavedLocallyParsed] = useState([] as ParsedNode[]); const errorMessage = useAppSelector((store) => store.auth.status.error?.data); const loginData = useAppSelector((store) => store.auth.loginData); + const loginPending = useAppSelector((store) => store.auth.status.connecting); const [searchParams, set_searchParams] = useSearchParams(); const [localName, set_localName] = useState(loginData.localName ? loginData.localName : ''); + const [jazzIcon, set_jazzIcon] = useState(loginData.jazzIcon ? loginData.jazzIcon : null); const [apiEndpoint, set_apiEndpoint] = useState(loginData.apiEndpoint ? loginData.apiEndpoint : ''); const [apiToken, set_apiToken] = useState(loginData.apiToken ? loginData.apiToken : ''); const [saveApiToken, set_saveApiToken] = useState(false); @@ -156,6 +159,7 @@ function ConnectNodeModal(props: ConnectNodeModalProps) { value: index.toString(), apiEndpoint: node.apiEndpoint, apiToken: node.apiToken, + jazzIcon: node.jazzIcon, }; }) as ParsedNode[]; set_nodesSavedLocallyParsed(parsed); @@ -261,6 +265,7 @@ function ConnectNodeModal(props: ConnectNodeModalProps) { apiEndpoint: formattedApiEndpoint, apiToken, localName, + jazzIcon, }), ); dispatch( @@ -350,6 +355,7 @@ function ConnectNodeModal(props: ConnectNodeModalProps) { set_apiToken(chosenNode.apiToken); set_saveApiToken(chosenNode.apiToken?.length > 0); set_localName(chosenNode.localName); + chosenNode.jazzIcon && set_jazzIcon(chosenNode.jazzIcon); }; return ( @@ -434,6 +440,7 @@ function ConnectNodeModal(props: ConnectNodeModalProps) { diff --git a/src/components/Modal/node/OpenChannelModal.tsx b/src/components/Modal/node/OpenChannelModal.tsx index 57eb3d38..b9867188 100644 --- a/src/components/Modal/node/OpenChannelModal.tsx +++ b/src/components/Modal/node/OpenChannelModal.tsx @@ -28,11 +28,18 @@ export const OpenChannelModal = ({ ...props }: OpenChannelModalProps) => { const dispatch = useAppDispatch(); const loginData = useAppSelector((store) => store.auth.loginData); const outgoingOpening = useAppSelector((store) => store.node.channels.parsed.outgoingOpening); + const aliases = useAppSelector((store) => store.node.aliases.data); + const peerIdToAliasLink = useAppSelector((store) => store.node.links.peerIdToAlias); const channelIsBeingOpened = props.peerAddress ? !!outgoingOpening[props.peerAddress] : false; const [openChannelModal, set_openChannelModal] = useState(false); const [amount, set_amount] = useState(''); const [peerAddress, set_peerAddress] = useState(props.peerAddress ? props.peerAddress : ''); + const getAliasByPeerId = (peerId: string): string => { + if (aliases && peerId && peerIdToAliasLink[peerId]) return `${peerIdToAliasLink[peerId]} (${peerId})`; + return peerId; + }; + const handleOpenChannelDialog = () => { set_openChannelModal(true); }; diff --git a/src/components/Modal/node/PingModal.tsx b/src/components/Modal/node/PingModal.tsx index acdfaf2f..71765884 100644 --- a/src/components/Modal/node/PingModal.tsx +++ b/src/components/Modal/node/PingModal.tsx @@ -22,10 +22,17 @@ type PingModalProps = { export const PingModal = (props: PingModalProps) => { const dispatch = useAppDispatch(); const loginData = useAppSelector((selector) => selector.auth.loginData); + const aliases = useAppSelector((store) => store.node.aliases.data); + const peerIdToAliasLink = useAppSelector((store) => store.node.links.peerIdToAlias); const [peerId, set_peerId] = useState(props.peerId ? props.peerId : ''); const [openModal, set_OpenModal] = useState(false); const [disableButton, set_disableButton] = useState(false); + const getAliasByPeerId = (peerId: string): string => { + if (aliases && peerId && peerIdToAliasLink[peerId]) return `${peerIdToAliasLink[peerId]} (${peerId})`; + return peerId; + }; + const handleChange = (event: React.ChangeEvent) => { set_peerId(event.target.value); }; @@ -57,7 +64,7 @@ export const PingModal = (props: PingModalProps) => { ) .unwrap() .then((resp: any) => { - const msg = `Ping of ${peerId} succeded with latency of ${resp.latency}ms`; + const msg = `Ping of ${getAliasByPeerId(peerId)} succeeded with latency of ${resp.latency}ms`; console.log(msg, resp); sendNotification({ notificationPayload: { @@ -76,7 +83,7 @@ export const PingModal = (props: PingModalProps) => { ).unwrap(); if (!isCurrentApiEndpointTheSame) return; - let errMsg = `Ping of ${peerId} failed`; + let errMsg = `Ping of ${getAliasByPeerId(peerId)} failed`; if (e instanceof sdkApiError && e.hoprdErrorPayload?.status) errMsg = errMsg + `.\n${e.hoprdErrorPayload.status}`; if (e instanceof sdkApiError && e.hoprdErrorPayload?.error) diff --git a/src/future-hopr-lib-components/PeerInfo/index.tsx b/src/future-hopr-lib-components/PeerInfo/index.tsx new file mode 100644 index 00000000..6d9c3d0e --- /dev/null +++ b/src/future-hopr-lib-components/PeerInfo/index.tsx @@ -0,0 +1,92 @@ +import { useEffect } from 'react'; +import { useAppDispatch, useAppSelector } from '../../store'; +import { Link } from 'react-router-dom'; +import styled from '@emotion/styled'; + +// HOPR Components +import SmallActionButton from '../../future-hopr-lib-components/Button/SmallActionButton'; +import { generateBase64Jazz } from '../../utils/functions'; + +//Mui +import CopyIcon from '@mui/icons-material/ContentCopy'; +import LaunchIcon from '@mui/icons-material/Launch'; + +interface Props { + peerId?: string; + nodeAddress?: string; + shortenPeerId?: boolean; +} + +const Container = styled.div` + display: flex; + align-items: center; + .node-jazz-icon { + height: 30px; + width: 30px; + } +`; + +const PeersInfo: React.FC = (props) => { + const { peerId, nodeAddress, ...rest } = props; + const aliases = useAppSelector((store) => store.node.aliases.data); + const peerIdToAliasLink = useAppSelector((store) => store.node.links.peerIdToAlias); + + const getAliasByPeerId = (peerId: string): string | JSX.Element => { + const shortPeerId = peerId && `${peerId.substring(0, 6)}...${peerId.substring(peerId.length - 8, peerId.length)}`; + const displayPeerId = props.shortenPeerId ? shortPeerId : peerId; + if (aliases && peerId && peerIdToAliasLink[peerId]) + return ( + <> + {peerIdToAliasLink[peerId]} ({displayPeerId}) + + ); + return displayPeerId; + }; + + const noCopyPaste = !( + window.location.protocol === 'https:' || + window.location.hostname === 'localhost' || + window.location.hostname === '127.0.0.1' + ); + + const icon = nodeAddress && generateBase64Jazz(nodeAddress); + + return ( + + +
+ {peerId && getAliasByPeerId(peerId)}{' '} + navigator.clipboard.writeText(peerId as string)} + disabled={noCopyPaste} + tooltip={noCopyPaste ? 'Clipboard not supported on HTTP' : 'Copy Peer Id'} + > + + +
+ {nodeAddress}{' '} + navigator.clipboard.writeText(nodeAddress as string)} + disabled={noCopyPaste} + tooltip={noCopyPaste ? 'Clipboard not supported on HTTP' : 'Copy Node Address'} + > + + + + + + + +
+
+ ); +}; + +export default PeersInfo; diff --git a/src/future-hopr-lib-components/Select/index.tsx b/src/future-hopr-lib-components/Select/index.tsx index 211d5395..988bde87 100644 --- a/src/future-hopr-lib-components/Select/index.tsx +++ b/src/future-hopr-lib-components/Select/index.tsx @@ -8,7 +8,7 @@ import FormControl from '@mui/material/FormControl'; import SelectMui, { SelectProps as SelectMuiProps } from '@mui/material/Select'; import { Tooltip, IconButton } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; -import { generateBase64Jazz } from '../../utils/functions'; +import { toHexMD5, generateBase64Jazz } from '../../utils/functions'; const SFormControl = styled(FormControl)` margin-bottom: 16px; @@ -50,12 +50,14 @@ interface Props extends SelectMuiProps { value: string | number; name: string | number | null; apiEndpoint: string | null; + jazzIcon?: string | null; disabled?: boolean; }[]; native?: boolean; } const Select: React.FC = (props) => { + console.log('props.values', props.values); return ( = (props) => { > {props.values && props.values.map((elem, index) => { - const icon = elem.apiEndpoint && generateBase64Jazz(elem.apiEndpoint); + const jazzMd5apiEndpoint = elem.apiEndpoint && toHexMD5(elem.apiEndpoint); + const icon = elem.jazzIcon + ? generateBase64Jazz(elem.jazzIcon) + : jazzMd5apiEndpoint && generateBase64Jazz(jazzMd5apiEndpoint); return ( = (props) => { )} {elem.name} diff --git a/src/future-hopr-lib-components/Table/table-pro.tsx b/src/future-hopr-lib-components/Table/table-pro.tsx index c5f28892..16a40555 100644 --- a/src/future-hopr-lib-components/Table/table-pro.tsx +++ b/src/future-hopr-lib-components/Table/table-pro.tsx @@ -128,8 +128,8 @@ const STextField = styled(TextField)` interface Props { data: { - [key: string]: string | JSX.Element; - id: string; + [key: string]: string | number | JSX.Element; + id: string | number; actions: JSX.Element; }[]; id?: string; @@ -148,6 +148,44 @@ interface Props { search?: boolean; loading?: boolean; onRowClick?: Function; + orderByDefault?: string; +} + +type Order = 'asc' | 'desc'; + +const isString = (value: any) => typeof value === 'string' || value instanceof String; + +function descendingComparator( + a: { [key in string]: number | string }, + b: { [key in string]: number | string }, + orderBy: string, +) { + if (isString(b[orderBy]) && isString(a[orderBy])) { + if ((b[orderBy] as string).toLowerCase() < (a[orderBy] as string).toLowerCase()) { + return -1; + } + if ((b[orderBy] as string).toLowerCase() > (a[orderBy] as string).toLowerCase()) { + return 1; + } + } + + if (b[orderBy] < a[orderBy]) { + return -1; + } + if (b[orderBy] > a[orderBy]) { + return 1; + } + + return 0; +} + +function getComparator( + order: Order, + orderBy: string, +): (a: { [key in Key]: number | string }, b: { [key in Key]: number | string }) => number { + return order === 'desc' + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); } export default function CustomPaginationActionsTable(props: Props) { @@ -156,6 +194,8 @@ export default function CustomPaginationActionsTable(props: Props) { const [rowsPerPage, set_RowsPerPage] = React.useState( props.id && rowsPerPageFromLocalStorage ? rowsPerPageFromLocalStorage : 10, ); + const [order, setOrder] = React.useState('asc'); + const [orderBy, setOrderBy] = React.useState(props.orderByDefault || props.header[0].key || 'id'); const [searchPhrase, set_searchPhrase] = React.useState(''); const [filteredData, set_filteredData] = React.useState([]); @@ -212,6 +252,15 @@ export default function CustomPaginationActionsTable(props: Props) { return; } + const visibleRows = React.useMemo( + () => + [...filteredData] + //@ts-ignore as we can input JSX into the data, but we will not sort by it + .sort(getComparator(order, orderBy)) + .slice(page * rowsPerPage, rowsPerPage !== -1 ? page * rowsPerPage + rowsPerPage : filteredData.length), + [rowsPerPage, filteredData, order, orderBy, page, rowsPerPage], + ); + return ( - {(rowsPerPage > 0 - ? filteredData.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - : filteredData - ).map((row) => ( + {visibleRows.map((row) => ( ))} @@ -306,10 +353,12 @@ export default function CustomPaginationActionsTable(props: Props) { } const CustomTableRow = ({ + id, row, header, onRowClick, }: { + id: string; row: Props['data'][0]; header: Props['header']; onRowClick?: Function; @@ -330,6 +379,7 @@ const CustomTableRow = ({ return ( { onRowClick && onRowClick(row); }} diff --git a/src/pages/node/aliases.tsx b/src/pages/node/aliases.tsx index 3de1a957..c0446c26 100644 --- a/src/pages/node/aliases.tsx +++ b/src/pages/node/aliases.tsx @@ -12,6 +12,7 @@ import IconButton from '../../future-hopr-lib-components/Button/IconButton'; import { SendMessageModal } from '../../components/Modal/node/SendMessageModal'; import RemoveAliasIcon from '../../future-hopr-lib-components/Icons/RemoveAlias'; import TablePro from '../../future-hopr-lib-components/Table/table-pro'; +import PeersInfo from '../../future-hopr-lib-components/PeerInfo'; // Modals import { PingModal } from '../../components/Modal/node/PingModal'; @@ -26,7 +27,7 @@ import DriveFolderUploadIcon from '@mui/icons-material/DriveFolderUpload'; function AliasesPage() { const dispatch = useAppDispatch(); const aliases = useAppSelector((store) => store.node.aliases.data); - const peers = useAppSelector((store) => store.node.peers.data); + const peersObject = useAppSelector((store) => store.node.peers.parsed.connected); const aliasesFetching = useAppSelector((store) => store.node.aliases.isFetching); const hoprAddress = useAppSelector((store) => store.node.addresses.data.hopr); const myNodeAddress = useAppSelector((store) => store.node.addresses.data.native); @@ -122,10 +123,32 @@ function AliasesPage() { const parsedTableData = Object.entries(aliases ?? {}).map(([alias, peerId], key) => { const peerAddress = getNodeAddressByPeerId(peerId); + const lastSeenNumeric = peerId && peersObject[peerId]?.lastSeen; + const lastSeen = + (lastSeenNumeric as number) > 0 + ? new Date(lastSeenNumeric) + .toLocaleString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + timeZoneName: 'short', + }) + .replace(', ', '\n') + : 'Not seen'; + return { id: peerId, key: key.toString(), alias, + node: ( + + ), + lastSeen: {peerId === hoprAddress ? '-' : lastSeen}, peerId, peerAddress: peerAddress ?? '', actions: ( @@ -169,6 +192,45 @@ function AliasesPage() { }; }); + const header = [ + { + key: 'alias', + name: 'Alias', + search: true, + hidden: true, + }, + { + key: 'node', + name: 'Node', + maxWidth: '350px', + }, + { + key: 'lastSeen', + name: 'Last Seen', + maxWidth: '20px', + }, + { + key: 'peerId', + name: 'Peer Id', + search: true, + hidden: true, + }, + { + key: 'peerAddress', + name: 'Node Address', + search: true, + maxWidth: '60px', + hidden: true, + }, + { + key: 'actions', + name: 'Actions', + search: false, + width: '168px', + maxWidth: '168px', + }, + ]; + return (
); diff --git a/src/pages/node/channelsIncoming.tsx b/src/pages/node/channelsIncoming.tsx index 1aa867a9..0fb9de79 100644 --- a/src/pages/node/channelsIncoming.tsx +++ b/src/pages/node/channelsIncoming.tsx @@ -16,6 +16,7 @@ import { SubpageTitle } from '../../components/SubpageTitle'; import IconButton from '../../future-hopr-lib-components/Button/IconButton'; import TablePro from '../../future-hopr-lib-components/Table/table-pro'; import CloseChannelIcon from '../../future-hopr-lib-components/Icons/CloseChannel'; +import PeersInfo from '../../future-hopr-lib-components/PeerInfo'; // Modals import { PingModal } from '../../components/Modal/node/PingModal'; @@ -26,10 +27,12 @@ import { SendMessageModal } from '../../components/Modal/node/SendMessageModal'; // Mui import GetAppIcon from '@mui/icons-material/GetApp'; +import { truncateEthereumAddress } from '../../utils/blockchain'; function ChannelsPage() { const dispatch = useAppDispatch(); const channels = useAppSelector((store) => store.node.channels.data); + const channelsIncoming = useAppSelector((store) => store.node.channels.data?.incoming); const channelsIncomingObject = useAppSelector((store) => store.node.channels.parsed.incoming); const channelsFetching = useAppSelector((store) => store.node.channels.isFetching); const aliases = useAppSelector((store) => store.node.aliases.data); @@ -101,13 +104,23 @@ function ChannelsPage() { key: 'id', name: '#', }, + { + key: 'node', + name: 'Node', + maxWidth: '568px', + }, { key: 'peerAddress', name: 'Node Address', search: true, copy: true, - maxWidth: '568px', - tooltip: true, + hidden: true, + }, + { + key: 'peerId', + name: 'Peer Id', + search: true, + hidden: true, }, { key: 'status', @@ -183,8 +196,36 @@ function ChannelsPage() { }); }; - const parsedTableData = Object.keys(channelsIncomingObject) - .map((id, index) => { + const peersWithAliases = (channelsIncoming || []).filter( + (peer) => aliases && peer.peerAddress && getAliasByPeerAddress(peer.peerAddress) !== peer.peerAddress, + ); + const peersWithAliasesSorted = peersWithAliases.sort((a, b) => { + if (getAliasByPeerAddress(b.peerAddress).toLowerCase() > getAliasByPeerAddress(a.peerAddress).toLowerCase()) { + return -1; + } + if (getAliasByPeerAddress(b.peerAddress).toLowerCase() < getAliasByPeerAddress(a.peerAddress).toLowerCase()) { + return 1; + } + return 0; + }); + const peersWithoutAliases = (channelsIncoming || []).filter( + (peer) => aliases && peer.peerAddress && getAliasByPeerAddress(peer.peerAddress) === peer.peerAddress, + ); + const peersWithoutAliasesSorted = peersWithoutAliases.sort((a, b) => { + if (b.peerAddress > a.peerAddress) { + return -1; + } + if (b.peerAddress < a.peerAddress) { + return 1; + } + return 0; + }); + + const peersSorted = [...peersWithAliasesSorted, ...peersWithoutAliasesSorted]; + + const parsedTableData = peersSorted + .map((channel, index) => { + const id = channel.id; if ( !channelsIncomingObject[id].peerAddress || !channelsIncomingObject[id].balance || @@ -197,6 +238,7 @@ function ChannelsPage() { !!nodeAddressToOutgoingChannelLink[channelsIncomingObject[id].peerAddress] ); const peerId = getPeerIdFromPeerAddress(channelsIncomingObject[id].peerAddress as string); + const peerAddress = channelsIncomingObject[id].peerAddress; const totalTicketsPerChannel = `${formatEther( BigInt(tickets?.redeemed[id]?.value || '0') + BigInt(tickets?.unredeemed[id]?.value || '0'), @@ -207,7 +249,15 @@ function ChannelsPage() { return { id: (index + 1).toString(), key: id, - peerAddress: getAliasByPeerAddress(channelsIncomingObject[id].peerAddress as string), + node: ( + + ), + peerAddress: getAliasByPeerAddress(peerAddress as string), + peerId: peerId, status: channelsIncomingObject[id].status, funds: `${utils.formatEther(channelsIncomingObject[id].balance as string)} ${HOPR_TOKEN_USED}`, tickets: unredeemedTicketsPerChannel, @@ -324,6 +374,7 @@ function ChannelsPage() { header={headerIncoming} search loading={parsedTableData.length === 0 && channelsFetching} + orderByDefault="number" /> ); diff --git a/src/pages/node/channelsOutgoing.tsx b/src/pages/node/channelsOutgoing.tsx index b07e8b25..efcb53c5 100644 --- a/src/pages/node/channelsOutgoing.tsx +++ b/src/pages/node/channelsOutgoing.tsx @@ -15,6 +15,7 @@ import { SubpageTitle } from '../../components/SubpageTitle'; import IconButton from '../../future-hopr-lib-components/Button/IconButton'; import CloseChannelIcon from '../../future-hopr-lib-components/Icons/CloseChannel'; import TablePro from '../../future-hopr-lib-components/Table/table-pro'; +import PeersInfo from '../../future-hopr-lib-components/PeerInfo'; // Modals import { OpenMultipleChannelsModal } from '../../components/Modal/node/OpenMultipleChannelsModal'; @@ -32,6 +33,7 @@ function ChannelsPage() { const dispatch = useAppDispatch(); const channels = useAppSelector((store) => store.node.channels.data); const channelsOutgoingObject = useAppSelector((store) => store.node.channels.parsed.outgoing); + const channelsOutgoing = useAppSelector((store) => store.node.channels.data?.outgoing); const channelsFetching = useAppSelector((store) => store.node.channels.isFetching); const aliases = useAppSelector((store) => store.node.aliases.data); const loginData = useAppSelector((store) => store.auth.loginData); @@ -148,13 +150,22 @@ function ChannelsPage() { key: 'id', name: '#', }, + { + key: 'node', + name: 'Node', + maxWidth: '350px', + }, { key: 'peerAddress', name: 'Node Address', search: true, - tooltip: true, - copy: true, - maxWidth: '168px', + hidden: true, + }, + { + key: 'peerId', + name: 'Peer Id', + search: true, + hidden: true, }, { key: 'status', @@ -178,20 +189,58 @@ function ChannelsPage() { }, ]; - const parsedTableData = Object.keys(channelsOutgoingObject) - .map((id, index) => { + const peersWithAliases = (channelsOutgoing || []).filter( + (peer) => aliases && peer.peerAddress && getAliasByPeerAddress(peer.peerAddress) !== peer.peerAddress, + ); + const peersWithAliasesSorted = peersWithAliases.sort((a, b) => { + if (getAliasByPeerAddress(b.peerAddress).toLowerCase() > getAliasByPeerAddress(a.peerAddress).toLowerCase()) { + return -1; + } + if (getAliasByPeerAddress(b.peerAddress).toLowerCase() < getAliasByPeerAddress(a.peerAddress).toLowerCase()) { + return 1; + } + return 0; + }); + const peersWithoutAliases = (channelsOutgoing || []).filter( + (peer) => aliases && peer.peerAddress && getAliasByPeerAddress(peer.peerAddress) === peer.peerAddress, + ); + const peersWithoutAliasesSorted = peersWithoutAliases.sort((a, b) => { + if (b.peerAddress > a.peerAddress) { + return -1; + } + if (b.peerAddress < a.peerAddress) { + return 1; + } + return 0; + }); + + const peersSorted = [...peersWithAliasesSorted, ...peersWithoutAliasesSorted]; + + const parsedTableData = peersSorted + .map((channel, index) => { + const id = channel.id; if ( !channelsOutgoingObject[id].peerAddress || !channelsOutgoingObject[id].balance || !channelsOutgoingObject[id].status ) return; - const peerId = getPeerIdFromPeerAddress(channelsOutgoingObject[id].peerAddress as string); + + const peerAddress = channelsOutgoingObject[id].peerAddress; + const peerId = getPeerIdFromPeerAddress(peerAddress as string); return { - id: index.toString(), + id: (index + 1).toString(), key: id, - peerAddress: getAliasByPeerAddress(channelsOutgoingObject[id].peerAddress as string), + node: ( + + ), + peerAddress: getAliasByPeerAddress(peerAddress as string), + peerId: peerId, status: channelsOutgoingObject[id].status as string, funds: `${utils.formatEther(channelsOutgoingObject[id].balance as string)} ${HOPR_TOKEN_USED}`, actions: ( @@ -305,6 +354,7 @@ function ChannelsPage() { header={header} search loading={parsedTableData.length === 0 && channelsFetching} + orderByDefault="number" /> ); diff --git a/src/pages/node/configuration.tsx b/src/pages/node/configuration.tsx index 93a24558..20785f0f 100644 --- a/src/pages/node/configuration.tsx +++ b/src/pages/node/configuration.tsx @@ -1,5 +1,7 @@ import { useEffect, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../../store'; +import { formatEther } from 'viem'; +import { rounder } from '../../utils/functions'; // HOPR Components import { SubpageTitle } from '../../components/SubpageTitle'; @@ -19,12 +21,37 @@ const NotificationsContainer = styled.div` gap: 1rem; `; +const DECIMALS_MULTIPLIER = BigInt(1e18); // For HOPR token's 18 decimals + +interface StrategyConfig { + path: ['AutoFunding' | 'AutoRedeeming', string]; + value: string; +} + +const calculateTickets = (value: string, ticketPrice: string) => { + const valueBigInt = BigInt(value) * DECIMALS_MULTIPLIER; + const ticketBigInt = BigInt(ticketPrice); + return valueBigInt / ticketBigInt; +}; + +const updateStrategyString = (originalString: string, key: string, value: string, tickets: bigint): string => { + const stringToReplace = `"${key}": "${value} HOPR"`; + const formattedEther = formatEther(BigInt(value)); + const replacement = `"${key}": "${value}" // ${formattedEther} HOPR, tickets: ${rounder(Number(tickets))}`; + + return originalString.includes(stringToReplace + ',') + ? originalString.replace(stringToReplace + ',', replacement + ',') + : originalString.replace(stringToReplace, replacement); +}; + function SettingsPage() { const dispatch = useAppDispatch(); const prevNotificationSettings = useAppSelector((store) => store.app.configuration.notifications); const strategies = useAppSelector((store) => store.node.configuration.data?.hopr?.strategy); + const configuration = useAppSelector((store) => store.node.configuration.data); const ticketPrice = useAppSelector((store) => store.node.ticketPrice.data); const [strategiesString, set_strategiesString] = useState(null); + const [configurationString, set_configurationString] = useState(null); const [localNotificationSettings, set_localNotificationSettings] = useState(); useEffect(() => { @@ -33,112 +60,56 @@ function SettingsPage() { } }, [prevNotificationSettings]); + // Usage in useEffect useEffect(() => { - if (strategies) { - let tmp = JSON.stringify(strategies, null, 2); + if (!strategies || !ticketPrice) return; - try { - if (ticketPrice) { - // min_stake_threshold - if (strategies?.strategies?.AutoFunding?.min_stake_threshold) { - const key = 'min_stake_threshold'; - const min_stake_threshold = strategies.strategies.AutoFunding[key].replace(' HOPR', ''); - const min_stake_thresholdBigInt = BigInt(min_stake_threshold) * BigInt(1e18); - const ticketBigInt = BigInt(ticketPrice); - const ticketsBigInt = min_stake_thresholdBigInt / ticketBigInt; - const ticketsString = ticketsBigInt.toString(); - const stringToReplace = `"${key}": "${strategies.strategies.AutoFunding[key]}"`; - if (tmp.includes(stringToReplace + ',')) { - tmp = tmp.replace( - stringToReplace + ',', - `"${key}": "${strategies.strategies.AutoFunding[key]}", // tickets: ${ticketsString}`, - ); - } else { - tmp = tmp.replace( - stringToReplace, - `"${key}": "${strategies.strategies.AutoFunding[key]}" // tickets: ${ticketsString}`, - ); - } - } + try { + const configs: StrategyConfig[] = [ + { + path: ['AutoFunding', 'min_stake_threshold'], + value: strategies.strategies?.AutoFunding?.min_stake_threshold?.replace(' HOPR', ''), + }, + { + path: ['AutoFunding', 'funding_amount'], + value: strategies.strategies?.AutoFunding?.funding_amount?.replace(' HOPR', ''), + }, + { + path: ['AutoRedeeming', 'minimum_redeem_ticket_value'], + value: strategies.strategies?.AutoRedeeming?.minimum_redeem_ticket_value?.replace(' HOPR', ''), + }, + { + path: ['AutoRedeeming', 'on_close_redeem_single_tickets_value_min'], + value: strategies.strategies?.AutoRedeeming?.on_close_redeem_single_tickets_value_min?.replace(' HOPR', ''), + }, + ]; - // funding_amount - if (strategies?.strategies?.AutoFunding?.funding_amount) { - const key = 'funding_amount'; - const funding_amount = strategies.strategies.AutoFunding[key].replace(' HOPR', ''); - const funding_amountBigInt = BigInt(funding_amount) * BigInt(1e18); - const ticketBigInt = BigInt(ticketPrice); - const ticketsBigInt = funding_amountBigInt / ticketBigInt; - const ticketsString = ticketsBigInt.toString(); - const stringToReplace = `"${key}": "${strategies.strategies.AutoFunding[key]}"`; - if (tmp.includes(stringToReplace + ',')) { - tmp = tmp.replace( - stringToReplace + ',', - `"${key}": "${strategies.strategies.AutoFunding[key]}", // tickets: ${ticketsString}`, - ); - } else { - tmp = tmp.replace( - stringToReplace, - `"${key}": "${strategies.strategies.AutoFunding[key]}" // tickets: ${ticketsString}`, - ); - } - } + console.log('configs', configs); - // minimum_redeem_ticket_value - if (strategies?.strategies?.AutoRedeeming?.minimum_redeem_ticket_value) { - const key = 'minimum_redeem_ticket_value'; - const minimum_redeem_ticket_value = strategies.strategies.AutoRedeeming[key].replace(' HOPR', ''); - const minimum_redeem_ticket_valueBigInt = BigInt(minimum_redeem_ticket_value) * BigInt(1e18); - const ticketBigInt = BigInt(ticketPrice); - const ticketsBigInt = minimum_redeem_ticket_valueBigInt / ticketBigInt; - const ticketsString = ticketsBigInt.toString(); - const stringToReplace = `"${key}": "${strategies.strategies.AutoRedeeming[key]}"`; - if (tmp.includes(stringToReplace + ',')) { - tmp = tmp.replace( - stringToReplace + ',', - `"${key}": "${strategies.strategies.AutoRedeeming[key]}", // tickets: ${ticketsString}`, - ); - } else { - tmp = tmp.replace( - stringToReplace, - `"${key}": "${strategies.strategies.AutoRedeeming[key]}" // tickets: ${ticketsString}`, - ); - } - } + let result = JSON.stringify(strategies, null, 2); - // on_close_redeem_single_tickets_value_min - if (strategies?.strategies?.AutoRedeeming?.on_close_redeem_single_tickets_value_min) { - const key = 'on_close_redeem_single_tickets_value_min'; - const on_close_redeem_single_tickets_value_min = strategies.strategies.AutoRedeeming[key].replace( - ' HOPR', - '', - ); - const on_close_redeem_single_tickets_value_minBigInt = - BigInt(on_close_redeem_single_tickets_value_min) * BigInt(1e18); - const ticketBigInt = BigInt(ticketPrice); - const ticketsBigInt = on_close_redeem_single_tickets_value_minBigInt / ticketBigInt; - const ticketsString = ticketsBigInt.toString(); - const stringToReplace = `"${key}": "${strategies.strategies.AutoRedeeming[key]}"`; - if (tmp.includes(stringToReplace + ',')) { - tmp = tmp.replace( - stringToReplace + ',', - `"${key}": "${strategies.strategies.AutoRedeeming[key]}", // tickets: ${ticketsString}`, - ); - } else { - tmp = tmp.replace( - stringToReplace, - `"${key}": "${strategies.strategies.AutoRedeeming[key]}" // tickets: ${ticketsString}`, - ); - } - } + for (const config of configs) { + if (config.value) { + const tickets = calculateTickets(config.value, ticketPrice); + result = updateStrategyString(result, config.path[1], config.value, tickets); } - } catch (e) { - console.warn('Error while counting strategies against current ticket price.', e); } - set_strategiesString(tmp); + set_strategiesString(result); + } catch (e) { + console.warn('Error while counting strategies against current ticket price.', e); } }, [strategies, ticketPrice]); + useEffect(() => { + if (configuration) { + let tmp = JSON.parse(JSON.stringify(configuration)); + tmp.hopr['strategy'] && delete tmp.hopr['strategy']; + tmp = JSON.stringify(tmp, null, 2); + set_configurationString(tmp); + } + }, [configuration]); + const handleSaveSettings = async () => { if (localNotificationSettings) { dispatch(appActions.setNotificationSettings(localNotificationSettings)); @@ -233,8 +204,18 @@ function SettingsPage() { True + + Strategies @@ -246,17 +227,19 @@ function SettingsPage() { )} + + Configuration + + {configurationString && ( + + )} + + - ); diff --git a/src/pages/node/info/index.tsx b/src/pages/node/info/index.tsx index 0d3cfe46..b63c1c81 100644 --- a/src/pages/node/info/index.tsx +++ b/src/pages/node/info/index.tsx @@ -18,10 +18,12 @@ import WithdrawModal from '../../../components/Modal/node/WithdrawModal'; import SmallActionButton from '../../../future-hopr-lib-components/Button/SmallActionButton'; import { ColorStatus } from '../../../components/InfoBar/details'; import ProgressBar from '../../../future-hopr-lib-components/Progressbar'; +import IconButton from '../../../future-hopr-lib-components/Button/IconButton'; //Icons import CopyIcon from '@mui/icons-material/ContentCopy'; import LaunchIcon from '@mui/icons-material/Launch'; +import DataObjectIcon from '@mui/icons-material/DataObject'; //Info Components import NodeUptime from './node-uptime'; @@ -60,9 +62,10 @@ function InfoPage() { const blockNumberFromInfo = useAppSelector((store) => store.node.info.data?.indexerBlock); // >=2.1.3 const blockNumberCheckSumFromInfo = useAppSelector((store) => store.node.info.data?.indexerChecksum); // >=2.1.3 const blockNumberPrevIndexedWithHOPRdata = useAppSelector((store) => store.node.info.data?.indexBlockPrevChecksum); // >=2.1.4 - const blockNumberIndexedWithHOPRdata = blockNumberPrevIndexedWithHOPRdata - ? blockNumberPrevIndexedWithHOPRdata + 1 - : null; + const blockNumberIndexedWithHOPRdata = + blockNumberPrevIndexedWithHOPRdata && blockNumberFromInfo !== blockNumberPrevIndexedWithHOPRdata + ? blockNumberPrevIndexedWithHOPRdata + 1 + : null; const blockNumber = blockNumberFromInfo ?? blockNumberFromMetrics; const blockNumberCheckSum = blockNumberCheckSumFromInfo ?? blockNumberCheckSumFromMetrics; const ticketPrice = useAppSelector((store) => store.node.ticketPrice.data); @@ -188,7 +191,46 @@ function InfoPage() { title="INFO" refreshFunction={fetchInfoData} reloading={isFetchingAnyData} - actions={} + actions={ + <> + + } + tooltipText={ + + OPEN +
+ Swagger UI +
+ } + onClick={() => { + const externalUrl = apiEndpoint + '/swagger-ui/index.html#/'; + const w = window.open(externalUrl, '_blank'); + w && w.focus(); + }} + /> + + } + tooltipText={ + + OPEN +
+ Scalar UI +
+ } + onClick={() => { + const externalUrl = apiEndpoint + '/scalar'; + const w = window.open(externalUrl, '_blank'); + w && w.focus(); + }} + /> + + } /> { - return { - id: id, - number: id, - peerId: getAliasByPeerId(peer.peerId), - peerAddress: peer.peerAddress, - quality: , - lastSeen: - peer.lastSeen > 0 - ? new Date(peer.lastSeen).toLocaleString('en-US', { + const peersWithAliases = (peers?.connected || []).filter( + (peer) => aliases && peer.peerId && peerIdToAliasLink[peer.peerId], + ); + const peersWithAliasesSorted = peersWithAliases.sort((a, b) => { + if (getAliasByPeerId(b.peerId).toLowerCase() > getAliasByPeerId(a.peerId).toLowerCase()) { + return -1; + } + if (getAliasByPeerId(b.peerId).toLowerCase() < getAliasByPeerId(a.peerId).toLowerCase()) { + return 1; + } + return 0; + }); + const peersWithoutAliases = (peers?.connected || []).filter( + (peer) => aliases && peer.peerId && !peerIdToAliasLink[peer.peerId], + ); + const peersWithoutAliasesSorted = peersWithoutAliases.sort((a, b) => { + if (b.peerId > a.peerId) { + return -1; + } + if (b.peerId < a.peerId) { + return 1; + } + return 0; + }); + + const peersSorted = [...peersWithAliasesSorted, ...peersWithoutAliasesSorted]; + + const parsedTableData = peersSorted.map((peer, index) => { + const lastSeen = + (peer.lastSeen as number) > 0 + ? new Date(peer.lastSeen) + .toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', @@ -132,7 +157,22 @@ function PeersPage() { minute: '2-digit', timeZoneName: 'short', }) - : 'Not seen', + .replace(', ', '\n') + : 'Not seen'; + + return { + id: index + 1, + node: ( + + ), + peerId: getAliasByPeerId(peer.peerId), + peerAddress: peer.peerAddress, + quality: , + lastSeen: {lastSeen}, actions: ( <> diff --git a/src/router.tsx b/src/router.tsx index 1431677a..c77c20bd 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -230,6 +230,7 @@ const LayoutEnhanced = () => { if (!apiEndpoint) return; if (loginData.apiEndpoint === apiEndpoint && loginData.apiToken === apiToken) return; const formattedApiEndpoint = parseAndFormatUrl(apiEndpoint); + console.log('Node Admin login from url', formattedApiEndpoint); dispatch( authActions.useNodeData({ apiEndpoint, diff --git a/src/store/slices/auth/index.ts b/src/store/slices/auth/index.ts index 50488e9e..e9f392b6 100644 --- a/src/store/slices/auth/index.ts +++ b/src/store/slices/auth/index.ts @@ -3,6 +3,7 @@ import { bubbleSortObject } from '../../../utils/functions'; import { loadStateFromLocalStorage } from '../../../utils/localStorage'; import { actionsAsync, createAsyncReducer } from './actionsAsync'; import { initialState } from './initialState'; +import { isAddress } from 'viem'; const authSlice = createSlice({ name: 'auth', @@ -20,9 +21,10 @@ const authSlice = createSlice({ apiToken: string; apiEndpoint: string; localName?: string; + jazzIcon?: string | null; }>, ) { - // Check if we have name saved locally + // Check if we have a name saved locally let localName: string | null = action.payload.localName ? action.payload.localName : ''; if (!localName) { const existingItem = state.nodes.findIndex((item) => item.apiEndpoint === action.payload.apiEndpoint); @@ -30,9 +32,18 @@ const authSlice = createSlice({ localName = state.nodes[existingItem].localName ? state.nodes[existingItem].localName : ''; } + // Check if we have a jazzIcon saved locally + let jazzIcon: string | null = action.payload.jazzIcon ? action.payload.jazzIcon : null; + if (!jazzIcon) { + const existingItem = state.nodes.findIndex((item) => item.apiEndpoint === action.payload.apiEndpoint); + if (existingItem !== -1) + jazzIcon = state.nodes[existingItem].jazzIcon ? (state.nodes[existingItem].jazzIcon as string) : null; + } + state.loginData.apiEndpoint = action.payload.apiEndpoint; state.loginData.apiToken = action.payload.apiToken ? action.payload.apiToken : ''; state.loginData.localName = localName; + state.loginData.jazzIcon = jazzIcon; }, setConnected(state) { state.status.connecting = false; @@ -65,6 +76,23 @@ const authSlice = createSlice({ localStorage.setItem('admin-ui-node-list', JSON.stringify(state.nodes)); }, + addNodeJazzIcon( + state, + action: PayloadAction<{ + apiEndpoint: string; + jazzIcon: string; + }>, + ) { + const existingItem = state.nodes.findIndex((item) => item.apiEndpoint === action.payload.apiEndpoint); + if (existingItem === -1) return; + if (isAddress(action.payload.jazzIcon)) { + state.nodes[existingItem].jazzIcon = action.payload.jazzIcon; + } else { + state.nodes[existingItem].jazzIcon = action.payload.jazzIcon; + } + + localStorage.setItem('admin-ui-node-list', JSON.stringify(state.nodes)); + }, clearLocalNodes(state) { state.nodes = []; localStorage.removeItem('admin-ui-node-list'); diff --git a/src/store/slices/auth/initialState.ts b/src/store/slices/auth/initialState.ts index 4bc47393..11d103c0 100644 --- a/src/store/slices/auth/initialState.ts +++ b/src/store/slices/auth/initialState.ts @@ -1,7 +1,7 @@ import { loadStateFromLocalStorage } from '../../../utils/localStorage'; const ADMIN_UI_NODE_LIST = loadStateFromLocalStorage('admin-ui-node-list') as - | { apiEndpoint: string | null; apiToken: string | null; localName: string | null }[] + | { apiEndpoint: string | null; apiToken: string | null; localName: string | null; jazzIcon?: string | null }[] | null; type InitialState = { @@ -18,11 +18,13 @@ type InitialState = { apiToken: string | null; localName: string | null; peerId: string | null; + jazzIcon: string | null; }; nodes: { apiEndpoint: string | null; apiToken: string | null; localName: string | null; + jazzIcon?: string | null; }[]; helper: { openLoginModalToNode: boolean; @@ -40,6 +42,7 @@ export const initialState: InitialState = { apiToken: null, localName: null, peerId: null, + jazzIcon: null, }, nodes: ADMIN_UI_NODE_LIST ? ADMIN_UI_NODE_LIST : [], helper: { openLoginModalToNode: false }, diff --git a/src/store/slices/node/actionsAsync.ts b/src/store/slices/node/actionsAsync.ts index a4571454..6e95c88d 100644 --- a/src/store/slices/node/actionsAsync.ts +++ b/src/store/slices/node/actionsAsync.ts @@ -48,6 +48,8 @@ import { formatEther } from 'viem'; import { nodeActionsFetching } from './actionsFetching'; import { sendNotification } from '../../../hooks/useWatcher/notifications'; import { useAppDispatch } from '../../../store'; +import { authActions } from '../auth'; + const { sdkApiError } = utils; const { closeChannel, @@ -145,6 +147,14 @@ const getAddressesThunk = createAsyncThunk< dispatch(nodeActionsFetching.setAddressesFetching(true)); try { const addresses = await getAddresses(payload); + if (addresses?.native) { + dispatch( + authActions.addNodeJazzIcon({ + apiEndpoint: payload.apiEndpoint as string, + jazzIcon: addresses.native, + }), + ); + } return addresses; } catch (e) { if (e instanceof sdkApiError) { @@ -281,7 +291,6 @@ const getConfigurationThunk = createAsyncThunk< async (payload, { rejectWithValue, dispatch }) => { try { const configuration = await getConfiguration(payload); - console.log('Configuration:\n', configuration); return configuration; } catch (e) { if (e instanceof sdkApiError) { @@ -1184,13 +1193,18 @@ export const createAsyncReducer = (builder: ActionReducerMapBuilder