From 5c2800633b52bd45c30fbce1b1ff03db484da096 Mon Sep 17 00:00:00 2001 From: drcpu Date: Tue, 11 Mar 2025 20:30:12 +0100 Subject: [PATCH 01/19] [info] Bump version to 2.0.0 --- package-lock.json | 4 ++-- package.json | 2 +- src/Pages/Info.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 943f1ea..4c0343b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "witnet-explorer-frontend", - "version": "1.5.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "witnet-explorer-frontend", - "version": "1.5.0", + "version": "2.0.0", "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/free-brands-svg-icons": "^5.15.4", diff --git a/package.json b/package.json index da5b8f2..2d6aea1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "witnet-explorer-frontend", - "version": "1.5.0", + "version": "2.0.0", "private": true, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.36", diff --git a/src/Pages/Info.js b/src/Pages/Info.js index 538d4ac..394d706 100644 --- a/src/Pages/Info.js +++ b/src/Pages/Info.js @@ -106,7 +106,7 @@ export default class Info extends Component { - {"Source code (version 1.5)"} + {"Source code (version 2.0.0)"} From 623c64ca5d7005bbef5012b83949b1af3682dc4b Mon Sep 17 00:00:00 2001 From: drcpu Date: Tue, 11 Mar 2025 20:43:30 +0100 Subject: [PATCH 02/19] [package] Update fortawesome versions --- package-lock.json | 89 ++++++++++++++++++++++------------------------- package.json | 8 ++--- 2 files changed, 46 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c0343b..3028227 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,10 @@ "name": "witnet-explorer-frontend", "version": "2.0.0", "dependencies": { - "@fortawesome/fontawesome-svg-core": "^1.2.36", - "@fortawesome/free-brands-svg-icons": "^5.15.4", - "@fortawesome/free-regular-svg-icons": "^5.15.4", - "@fortawesome/free-solid-svg-icons": "^5.15.4", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/free-regular-svg-icons": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.1.16", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.2", @@ -2309,57 +2309,52 @@ } }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", - "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==", - "hasInstallScript": true, + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", "engines": { "node": ">=6" } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "1.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", - "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", - "hasInstallScript": true, + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.7.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz", - "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==", - "hasInstallScript": true, + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz", + "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==", "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.7.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", - "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", - "hasInstallScript": true, + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz", + "integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==", "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.7.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", - "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", - "hasInstallScript": true, + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.7.2" }, "engines": { "node": ">=6" @@ -20015,40 +20010,40 @@ } }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", - "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==" + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==" }, "@fortawesome/fontawesome-svg-core": { - "version": "1.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", - "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.7.2" } }, "@fortawesome/free-brands-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz", - "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz", + "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.7.2" } }, "@fortawesome/free-regular-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", - "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz", + "integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.7.2" } }, "@fortawesome/free-solid-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", - "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.7.2" } }, "@fortawesome/react-fontawesome": { diff --git a/package.json b/package.json index 2d6aea1..f7d4fd7 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,10 @@ "version": "2.0.0", "private": true, "dependencies": { - "@fortawesome/fontawesome-svg-core": "^1.2.36", - "@fortawesome/free-brands-svg-icons": "^5.15.4", - "@fortawesome/free-regular-svg-icons": "^5.15.4", - "@fortawesome/free-solid-svg-icons": "^5.15.4", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/free-regular-svg-icons": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.1.16", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.2", From 4a66eebfac7d27adfb6506b6434b76c295a22967 Mon Sep 17 00:00:00 2001 From: drcpu Date: Tue, 11 Mar 2025 21:35:08 +0100 Subject: [PATCH 03/19] [block] Update fields for showing the mint transaction --- src/Pages/SearchPages/BlockPanel.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Pages/SearchPages/BlockPanel.js b/src/Pages/SearchPages/BlockPanel.js index 242a940..577dc76 100644 --- a/src/Pages/SearchPages/BlockPanel.js +++ b/src/Pages/SearchPages/BlockPanel.js @@ -75,7 +75,7 @@ export default class BlockPanel extends Component { } generateMintCard(mint) { - var mint_link = "/search/" + mint.txn_hash; + var mint_link = "/search/" + mint.hash; return ( @@ -236,11 +236,7 @@ export default class BlockPanel extends Component { { data_requests.map(function(data_request){ const transaction_link = "/search/" + data_request.hash; - const requester_link = ( - data_request.input_addresses.length === 1 - ? "/search/" + data_request.input_addresses[0] - : "" - ); + const requester_link = "/search/" + data_request.requester; return ( @@ -248,11 +244,7 @@ export default class BlockPanel extends Component { {data_request.hash} ); } - // Add script - retrieval.push( - - - - - ); + // Add script if any + if (data.script !== "") { + retrieval.push( + + + + + ); + } return retrieval; } else if (data.kind === "RNG") { From 269fdb0ce891ee309e780d0c38688dba9b9073f5 Mon Sep 17 00:00:00 2001 From: drcpu Date: Tue, 11 Mar 2025 21:47:19 +0100 Subject: [PATCH 05/19] [search] Fix issue with forever-spinner on search page --- src/Pages/Search.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Pages/Search.js b/src/Pages/Search.js index b18bfeb..060c4d8 100644 --- a/src/Pages/Search.js +++ b/src/Pages/Search.js @@ -53,7 +53,7 @@ export default class Search extends Component{ } componentDidUpdate(prevProps) { - if(prevProps.match.params.hash !== this.props.match.params.hash){ + if (prevProps.match.params.hash !== this.props.match.params.hash) { this.searchValue(this.props.match.params.hash); } } @@ -216,7 +216,7 @@ export default class Search extends Component{ else if (loading) { searchResultPanel = ; } - else { + else if (search_response !== null) { if (search_response.response_type === "pending") { searchResultPanel = this.generateTransactionPanel(search_response); } @@ -271,6 +271,9 @@ export default class Search extends Component{ searchResultPanel = ; } } + else { + searchResultPanel =
; + } } else { searchResultPanel = this.generateErrorPanel(error_value); From 674870efaef91949adb43a37c5bba70f2054cd58 Mon Sep 17 00:00:00 2001 From: drcpu Date: Wed, 12 Mar 2025 21:40:30 +0100 Subject: [PATCH 06/19] [services] Update time converter to not use the deprecated substr function --- src/Services/TimeConverter.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Services/TimeConverter.js b/src/Services/TimeConverter.js index 07a11c5..0591161 100644 --- a/src/Services/TimeConverter.js +++ b/src/Services/TimeConverter.js @@ -6,22 +6,22 @@ class TimeConverter { var date = new Date(unix_timestamp * 1000); - var hours = "0" + date.getHours(); - var minutes = "0" + date.getMinutes(); - var seconds = "0" + date.getSeconds(); + var hours = String(date.getHours()).padStart(2, "0"); + var minutes = String(date.getMinutes()).padStart(2, "0"); + var seconds = String(date.getSeconds()).padStart(2, "0"); - var day = "0" + date.getDate(); - var month = "0" + (date.getMonth() + 1); + var day = String(date.getDate()).padStart(2, "0"); + var month = String(date.getMonth() + 1).padStart(2, "0"); var year = date.getFullYear(); if (type === "full") { - return hours.substr(-2) + ':' + minutes.substr(-2) + ':' + seconds.substr(-2) + " " + day.substr(-2) + "/" + month.substr(-2) + "/" + year; + return hours + ':' + minutes + ':' + seconds + " " + day + "/" + month + "/" + year; } else if (type === "hour") { - return hours.substr(-2) + ':' + minutes.substr(-2) + ':' + seconds.substr(-2); + return hours + ':' + minutes + ':' + seconds; } else if (type === "day") { - return day.substr(-2) + "/" + month.substr(-2) + "/" + year; + return day + "/" + month + "/" + year; } } } From eb4c89ebf53b83918638d4718afb0bf3f8002b70 Mon Sep 17 00:00:00 2001 From: drcpu Date: Wed, 12 Mar 2025 21:43:12 +0100 Subject: [PATCH 07/19] [home] Update home page to show staking data and be more responsive --- src/Components/HistoryTypeahead.js | 5 +- src/Pages/Home.js | 557 ++++++++++++++++++----------- src/Services/TimeConverter.js | 2 +- src/fontawesome.js | 8 + 4 files changed, 365 insertions(+), 207 deletions(-) diff --git a/src/Components/HistoryTypeahead.js b/src/Components/HistoryTypeahead.js index 7f65568..1c78ab5 100644 --- a/src/Components/HistoryTypeahead.js +++ b/src/Components/HistoryTypeahead.js @@ -22,8 +22,11 @@ class HistoryTypeahead extends Component { render() { const { search_value } = this.state; + var lPad = Object.hasOwn(this.props, "lPad") ? this.props.lPad : "0px"; + var rPad = Object.hasOwn(this.props, "rPad") ? this.props.rPad : "0px"; + return ( -
+
{ - this.network_stats_card = this.generateNetworkStats(response.network_stats); - this.supply_stats_card = this.generateSupplyInfo(response.supply_info); - this.latest_blocks_card = this.generateLatestBlocks(response.latest_blocks); - this.latest_data_requests_card = this.generateTransactionCard(response.latest_data_requests, ["fas", "align-justify"]); - this.latest_value_transfers_card = this.generateTransactionCard(response.latest_value_transfers, ["fas", "coins"]); + .then((response) => { + this.network_stats_card = this.generateNetworkStats(response.network_stats); + this.supply_stats_card = this.generateSupplyInfo(response.supply_info); + this.total_staked_card = this.generateTotalStakedCard(response.total_staked); + this.latest_blocks_card = this.generateLatestBlocks(response.latest_blocks); + this.latest_data_requests_card = this.generateTransactionCard(response.latest_data_requests); + this.latest_value_transfers_card = this.generateTransactionCard(response.latest_value_transfers); + this.latest_stakes_card = this.generateTransactionCard(response.latest_stakes); + this.latest_unstakes_card = this.generateTransactionCard(response.latest_unstakes); - this.setState({ - update_timestamp : TimeConverter.convertUnixTimestamp(response.last_updated, "hour"), + this.setState({ + update_timestamp: TimeConverter.convertUnixTimestamp(response.last_updated, "hour"), + }); + }) + .catch((e) => { + console.log(e); }); - }) - .catch(e => { - console.log(e); - }); } generateNetworkStats(network_stats) { var table_rows = [ + [["fas", "network-wired"], "Validators", network_stats.validators], [["fas", "history"], "Epochs elapsed", network_stats.epochs], - [["fas", "cubes"], "Blocks minted", network_stats.num_blocks], - [["fas", "align-justify"], "Data requests", network_stats.num_data_requests], - [["fas", "coins"], "Value transfers", network_stats.num_value_transfers], - [["fas", "desktop"], "Active nodes", network_stats.num_active_nodes], - [["fas", "award"], "Reputed nodes", network_stats.num_reputed_nodes], - [["far", "hourglass"], "Pending requests", network_stats.num_pending_requests] + [["fas", "cubes"], "Blocks minted", network_stats.blocks], + [["fas", "align-justify"], "Data requests", network_stats.data_requests], + [["fas", "coins"], "Value transfers", network_stats.value_transfers], + [["fas", "circle-plus"], "Stakes", network_stats.stakes], + [["fas", "circle-minus"], "Unstakes", network_stats.unstakes], + [["far", "hourglass"], "Pending transactions", network_stats.pending_requests], ]; return (
- { - data_request.input_addresses.length === 1 - ? {data_request.input_addresses[0]} - : "(multiple requesters)" - } + {data_request.requester} {Formatter.formatWitValue(data_request.collateral, 2)} From e926629c0153c442a87ccbe6efa6dfaab7a5b7a5 Mon Sep 17 00:00:00 2001 From: drcpu Date: Tue, 11 Mar 2025 21:41:18 +0100 Subject: [PATCH 04/19] [search] Only show RAD script field if there is one --- .../DataRequestPages/DataRequestRadScript.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Pages/SearchPages/DataRequestPages/DataRequestRadScript.js b/src/Pages/SearchPages/DataRequestPages/DataRequestRadScript.js index b2a4e94..c31428d 100644 --- a/src/Pages/SearchPages/DataRequestPages/DataRequestRadScript.js +++ b/src/Pages/SearchPages/DataRequestPages/DataRequestRadScript.js @@ -48,17 +48,19 @@ export default class DataRequestRadScript extends Component {
- {"Script"} - - {data.script} -
+ {"Script"} + + {data.script} +
- { - table_rows.map(function(table_row) { - var icon = table_row[0]; - var label = table_row[1]; - var value = table_row[2]; + {table_rows.map(function (table_row) { + var icon = table_row[0]; + var label = table_row[1]; + var value = table_row[2]; - return ( - - - - - ); - }) - } + return ( + + + + + ); + })}
- {label} - - {Formatter.formatValue(value)} -
+ + {label} + + {Formatter.formatValue(value)} +
); @@ -105,199 +120,331 @@ export default class Home extends Component{ generateSupplyInfo(supply) { var table_rows = [ - [["fas", "hammer"], "WIT minted", (supply.blocks_minted_reward + supply.locked_wits_by_requests) / 1E9], - [["fas", "fire"], "WIT burned", (supply.blocks_missing_reward + supply.supply_burned_lies) / 1E9], - [["fas", "unlock"], "Circulating supply", supply.current_unlocked_supply / 1E9], - [["fas", "lock"], "Time-locked supply", supply.current_locked_supply / 1E9], - [["fas", "gem"], "Total supply", (supply.maximum_supply - supply.blocks_missing_reward) / 1E9] + [["fas", "hammer"], "WIT minted", (supply.blocks_minted_reward + supply.locked_wits_by_requests) / 1e9], + [["fas", "fire"], "WIT burned", (supply.blocks_missing_reward + supply.supply_burned_lies) / 1e9], + [["fas", "cubes-stacked"], "Staked supply", supply.current_staked_supply / 1e9], + [["fas", "unlock"], "Circulating supply", supply.current_unlocked_supply / 1e9], + [["fas", "lock"], "Time-locked supply", supply.current_locked_supply / 1e9], ]; return ( - +
- { - table_rows.map(function(table_row) { - var icon = table_row[0]; - var label = table_row[1]; - var value = table_row[2]; + {table_rows.map(function (table_row) { + var icon = table_row[0]; + var label = table_row[1]; + var value = table_row[2]; - return ( - - - - - ); - }) - } + return ( + + + + + ); + })}
- {label} - - {Formatter.formatValueSuffix(value, 2)} -
+ + {label} + + {Formatter.formatValueSuffix(value, 2)} +
); } + generateTotalStakedCard(staked) { + var ticks = []; + var unique_day_ticks = new Set(); + staked.forEach(function(stake){ + var day = TimeConverter.convertUnixTimestamp(stake.timestamp, "day"); + if (!unique_day_ticks.has(day)) { + unique_day_ticks.add(day); + ticks.push(stake.timestamp); + } + }); + + return ( + + + + TimeConverter.convertUnixTimestamp(tick, "day")} + angle={-35} + textAnchor="end" + /> + Formatter.formatValueSuffix(tick / 1e9, 2)} + /> + TimeConverter.convertUnixTimestamp(value, "full")} + formatter={(value) => [Formatter.formatValueSuffix(value / 1e9, 2), "Staked"]} + /> + + + + ); + } + generateLatestBlocks(blocks) { return ( - +
- { - blocks.slice(0, this.state.rows_per_card).map(function(block) { - var block_link = "/search/" + block.hash; + {blocks.slice(0, this.state.rows_per_card).map(function (block) { + var block_link = "/search/" + block.hash; - return ( - - - - - - - ); - }) - } + return ( + + + + + + + + + ); + })}
- - {block.hash} - - - {block.data_request} - - - {block.value_transfer} - - {TimeConverter.convertUnixTimestamp(block.timestamp, "hour")} -
+ {block.hash} + + + {block.data_request} + + + {block.value_transfer} + + + {block.stake} + + + {block.unstake} + + {TimeConverter.convertUnixTimestamp(block.timestamp, "hour")} +
); } - generateTransactionCard(transactions, icon) { + generateTransactionCard(transactions) { return ( - +
- { - transactions.slice(0, this.state.rows_per_card).map(function(transaction) { - var hash_link = "/search/" + transaction.hash; + {transactions.slice(0, this.state.rows_per_card).map(function (transaction) { + var hash_link = "/search/" + transaction.hash; - return ( - - - - - - ); - }) - } + return ( + + + + + + ); + })}
- - {transaction.hash} - - {TimeConverter.convertUnixTimestamp(transaction.timestamp, "hour")} - - { - transaction.confirmed - ? - : - } -
+ {transaction.hash} + + {TimeConverter.convertUnixTimestamp(transaction.timestamp, "hour")} + + {transaction.confirmed ? ( + + ) : ( + + )} +
); } render() { - const { update_timestamp } = this.state; - - return( - - - - - - - -
- Network stats -
-
- - {this.network_stats_card} - - -
- Supply info -
-
- - {this.supply_stats_card} - -
- - - Last updated: {update_timestamp} - - -
- - - - - -
- Blocks -
-
- - {this.latest_blocks_card} - -
- - - Last updated: {update_timestamp} - - -
- - - - - -
- Data requests -
-
- - {this.latest_data_requests_card} - -
- - - Last updated: {update_timestamp} - - -
- - - - - -
- Value transfers -
-
- - {this.latest_value_transfers_card} - -
- - - Last updated: {update_timestamp} - - -
- -
+ return ( + + + + + + + + + + +
+ Network stats +
+
+ + {this.network_stats_card} + +
+
+ + + + + +
+ Supply info +
+
+ + {this.supply_stats_card} + +
+
+ +
+ + + + + +
+ Total Staked +
+
+ {this.total_staked_card} +
+
+ +
+ + + + + +
Blocks
+
+ {this.latest_blocks_card} +
+
+ + + + + + + +
+ Data requests +
+
+ {this.latest_data_requests_card} +
+
+ + + + + +
+ Value transfers +
+
+ {this.latest_value_transfers_card} +
+
+ + + + + +
Stakes
+
+ {this.latest_stakes_card} +
+
+ + + + + +
+ Unstakes +
+
+ {this.latest_unstakes_card} +
+
+ +
+ +
+
); - }; + } } diff --git a/src/Services/TimeConverter.js b/src/Services/TimeConverter.js index 0591161..58df5ad 100644 --- a/src/Services/TimeConverter.js +++ b/src/Services/TimeConverter.js @@ -21,7 +21,7 @@ class TimeConverter { return hours + ':' + minutes + ':' + seconds; } else if (type === "day") { - return day + "/" + month + "/" + year; + return day + "/" + month; } } } diff --git a/src/fontawesome.js b/src/fontawesome.js index 39a1736..3b49586 100644 --- a/src/fontawesome.js +++ b/src/fontawesome.js @@ -38,6 +38,10 @@ import { faScroll, faHeading, faBold, + faCubesStacked, + faCirclePlus, + faCircleMinus, + faNetworkWired, } from '@fortawesome/free-solid-svg-icons'; import { @@ -106,4 +110,8 @@ library.add( faIdCard, faHeading, faBold, + faCubesStacked, + faCirclePlus, + faCircleMinus, + faNetworkWired, ); From 4a02c10ef9927ea26e2e555a09c363d24283dfce Mon Sep 17 00:00:00 2001 From: drcpu Date: Thu, 13 Mar 2025 19:50:52 +0100 Subject: [PATCH 08/19] [search] Update block panel to remove the mint tab post-wit/2 --- src/Pages/SearchPages/BlockPanel.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Pages/SearchPages/BlockPanel.js b/src/Pages/SearchPages/BlockPanel.js index 577dc76..4ab8eab 100644 --- a/src/Pages/SearchPages/BlockPanel.js +++ b/src/Pages/SearchPages/BlockPanel.js @@ -481,6 +481,10 @@ export default class BlockPanel extends Component { ? "1 tally" : data.transactions.tally.length + " tallies"; + var default_tab = data.transactions.mint.output_values.length !== 0 + ? "mint" + : "value_transfer"; + return ( @@ -493,12 +497,18 @@ export default class BlockPanel extends Component { - - - - {this.generateMintCard(data.transactions.mint)} - - + + { + data.transactions.mint.output_values.length !== 0 + ? ( + + + {this.generateMintCard(data.transactions.mint)} + + + ) + :
+ } {this.generateValueTransferCard(data.transactions.value_transfer)} From ef48f7723670da8c28b5871dc697cd9e9906d356 Mon Sep 17 00:00:00 2001 From: drcpu Date: Thu, 13 Mar 2025 21:57:12 +0100 Subject: [PATCH 09/19] [search] Add stake and unstake pages --- src/Pages/Search.js | 8 + src/Pages/SearchPages/StakePanel.js | 263 ++++++++++++++++++++++++++ src/Pages/SearchPages/UnstakePanel.js | 178 +++++++++++++++++ 3 files changed, 449 insertions(+) create mode 100644 src/Pages/SearchPages/StakePanel.js create mode 100644 src/Pages/SearchPages/UnstakePanel.js diff --git a/src/Pages/Search.js b/src/Pages/Search.js index 060c4d8..007f743 100644 --- a/src/Pages/Search.js +++ b/src/Pages/Search.js @@ -17,6 +17,8 @@ import DataRequestHistoryPanel from "./SearchPages/DataRequestHistoryPanel" import MintPanel from "./SearchPages/MintPanel" import RadHistoryPanel from "./SearchPages/RadHistoryPanel" import ValueTransferPanel from "./SearchPages/ValueTransferPanel" +import StakePanel from "./SearchPages/StakePanel" +import UnstakePanel from "./SearchPages/UnstakePanel" import DataService from "../Services/DataService"; @@ -270,6 +272,12 @@ export default class Search extends Component{ else if (search_response.response_type === "tally") { searchResultPanel = ; } + else if (search_response.response_type === "stake") { + searchResultPanel = ; + } + else if (search_response.response_type === "unstake") { + searchResultPanel = ; + } } else { searchResultPanel =
; diff --git a/src/Pages/SearchPages/StakePanel.js b/src/Pages/SearchPages/StakePanel.js new file mode 100644 index 0000000..e77b6b7 --- /dev/null +++ b/src/Pages/SearchPages/StakePanel.js @@ -0,0 +1,263 @@ +import React, { Component } from "react"; +import { Link } from "react-router-dom"; +import { Card, Container, Form } from "react-bootstrap"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import Formatter from "../../Services/Formatter"; +import TimeConverter from "../../Services/TimeConverter"; + +export default class StakePanel extends Component { + constructor(props) { + super(props); + + this.generateNanoWitValuesCheck = this.generateNanoWitValuesCheck.bind(this); + + this.state = { + showNanoWitValues: false, + }; + } + + generateDetailsCard(transaction) { + console.log(transaction); + + var txn_link = "/search/" + transaction.hash; + var block_link = "/search/" + transaction.block; + + let transaction_time; + if (transaction.timestamp === 0) { + transaction_time = ""; + } + else { + transaction_time = TimeConverter.convertUnixTimestamp(transaction.timestamp, "full") + " (epoch: " + transaction.epoch + ")"; + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {"Transaction"} + + {transaction.hash} +
+ {"Block"} + + {transaction.block} +
+ {"Fee"} + + { + this.state.showNanoWitValues + ? Formatter.formatValue(transaction.fee) + " nWIT" + : Formatter.formatWitValue(transaction.fee, 2) + } +
+ {"Weight"} + + {Formatter.formatValue(transaction.weight, 0)} +
+ {"Priority"} + + {Formatter.formatValue(transaction.priority, 0)} +
+ {"Timestamp"} + + {transaction_time} +
+ {"Status"} + + { + transaction.confirmed + ? "Confirmed" + : "Mined" + } +
+ ); + } + + generateInputOutputAddresses(data, showNanoWitValues) { + // Sum input values by address + var summed_inputs = {}; + data.inputs.forEach(function(input) { + if (!(input.address in summed_inputs)) { + summed_inputs[input.address] = 0 + } + summed_inputs[input.address] += input.value; + }); + + var stake_value = showNanoWitValues + ? Formatter.formatValue(data.stake_value) + " nWIT" + : Formatter.formatWitValue(data.stake_value, 2); + + var validator_link = "/search/" + data.validator; + var withdrawer_link = "/search/" + data.withdrawer; + + var extra_rows = Array.from( + {length: Math.max(0, 3 - Object.keys(summed_inputs).length)}, + (_value, index) => Object.keys(summed_inputs).length + index + ); + + return ( + + + { + Object.keys(summed_inputs).map(function(address, idx) { + var input_link = "/search/" + address; + + return ( + + + + + + + + ); + }) + } + { + extra_rows.map(function(idx) { + return ( + + + + + + + + ); + }) + } + +
+ {address} + + { + showNanoWitValues + ? Formatter.formatValue(summed_inputs[address]) + " nWIT" + : Formatter.formatWitValue(summed_inputs[address], 2) + } + + { + idx === 0 + ? + : "" + } + + { + idx === 0 + ? "Staking " + stake_value + " on" + : idx === 1 + ? {data.validator} + : idx === 2 + ? {data.withdrawer} + : "" + } + + { + idx === 0 + ? "" + : idx === 1 + ? " (validator)" + : idx === 2 + ? " (withdrawer)" + : "" + } +
+ { + idx === 1 + ? {data.validator} + : idx === 2 + ? {data.withdrawer} + : "" + } + + { + idx === 1 + ? " (validator)" + : idx === 2 + ? " (withdrawer)" + : "" + } +
+ ); + } + + generateNanoWitValuesCheck() { + return ( +
+ { + this.setState({ showNanoWitValues: !this.state.showNanoWitValues }) + }} + label="Show values in nWIT" + /> + + ); + } + + render() { + return ( + + + + + + { + this.generateDetailsCard(this.props.data) + } + { + this.generateInputOutputAddresses(this.props.data, this.state.showNanoWitValues) + } + + + { + this.generateNanoWitValuesCheck() + } + + + + + + ); + } +} diff --git a/src/Pages/SearchPages/UnstakePanel.js b/src/Pages/SearchPages/UnstakePanel.js new file mode 100644 index 0000000..cc06ef5 --- /dev/null +++ b/src/Pages/SearchPages/UnstakePanel.js @@ -0,0 +1,178 @@ +import React, { Component } from "react"; +import { Link } from "react-router-dom"; +import { Card, Container, Form } from "react-bootstrap"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import Formatter from "../../Services/Formatter"; +import TimeConverter from "../../Services/TimeConverter"; + +export default class UnstakePanel extends Component { + constructor(props) { + super(props); + + this.generateNanoWitValuesCheck = this.generateNanoWitValuesCheck.bind(this); + + this.state = { + showNanoWitValues: false, + }; + } + + generateDetailsCard(transaction, showNanoWitValues) { + console.log(transaction); + + var txn_link = "/search/" + transaction.hash; + var block_link = "/search/" + transaction.block; + var validator_link = "/search/" + transaction.validator; + var withdrawer_link = "/search/" + transaction.withdrawer; + + let transaction_time; + if (transaction.timestamp === 0) { + transaction_time = ""; + } + else { + transaction_time = TimeConverter.convertUnixTimestamp(transaction.timestamp, "full") + " (epoch: " + transaction.epoch + ")"; + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {"Transaction"} + + {transaction.hash} +
+ {"Block"} + + {transaction.block} +
+ {"Validator"} + + { + {transaction.validator} + } +
+ {"Withdrawer"} + + { + {transaction.withdrawer} + } +
+ {"Unstake"} + + { + this.state.showNanoWitValues + ? Formatter.formatValue(transaction.unstake_value) + " nWIT" + : Formatter.formatWitValue(transaction.unstake_value, 2) + } +
+ {"Fee"} + + { + this.state.showNanoWitValues + ? Formatter.formatValue(transaction.fee) + " nWIT" + : Formatter.formatWitValue(transaction.fee, 2) + } +
+ {"Weight"} + + {Formatter.formatValue(transaction.weight, 0)} +
+ {"Priority"} + + {Formatter.formatValue(transaction.priority, 0)} +
+ {"Timestamp"} + + {transaction_time} +
+ {"Status"} + + { + transaction.confirmed + ? "Confirmed" + : "Mined" + } +
+ ); + } + + generateNanoWitValuesCheck() { + return ( +
+ { + this.setState({ showNanoWitValues: !this.state.showNanoWitValues }) + }} + label="Show values in nWIT" + /> + + ); + } + + render() { + return ( + + + + + + { + this.generateDetailsCard(this.props.data, this.state.showNanoWitValues) + } + + + { + this.generateNanoWitValuesCheck() + } + + + + + + ); + } +} From e2224be75da3b337db40e5f339df3a2961ba7b9f Mon Sep 17 00:00:00 2001 From: drcpu Date: Thu, 13 Mar 2025 22:28:28 +0100 Subject: [PATCH 10/19] [search] Show validator and withdrawer balance on address page --- src/Pages/SearchPages/AddressPanel.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Pages/SearchPages/AddressPanel.js b/src/Pages/SearchPages/AddressPanel.js index 20aa951..1913de2 100644 --- a/src/Pages/SearchPages/AddressPanel.js +++ b/src/Pages/SearchPages/AddressPanel.js @@ -174,14 +174,25 @@ export default class AddressPanel extends Component { - {"Reputation"} + {"Validator"} - {details.reputation} { - details.eligibility === "Could not retrieve eligibility" - ? " (" + details.eligibility + ")" - : " (" + (details.eligibility / details.total_reputation * 100).toFixed(2) + "%)" + details.staked_validator === "Could not retrieve validator staked balance" + ? details.staked_validator + : Formatter.formatWitValue(details.staked_validator) + } + + + + + {"Withdrawer"} + + + { + details.staked_withdrawer === "Could not retrieve withdrawer staked balance" + ? details.staked_withdrawer + : Formatter.formatWitValue(details.staked_withdrawer) } @@ -755,7 +766,7 @@ export default class AddressPanel extends Component { - + { details === null ? @@ -768,7 +779,7 @@ export default class AddressPanel extends Component { - + { info === null ? From e323c72846c39461f0529e91711f451af12e814c Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 14 Mar 2025 20:33:36 +0100 Subject: [PATCH 11/19] [search] Distinguish between mainnet and testnet based on an env variable for address searches --- .env | 2 ++ src/Pages/Search.js | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..00c8bf3 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +REACT_APP_NETWORK=MAINNET +# REACT_APP_NETWORK=TESTNET diff --git a/src/Pages/Search.js b/src/Pages/Search.js index 007f743..906046e 100644 --- a/src/Pages/Search.js +++ b/src/Pages/Search.js @@ -75,12 +75,30 @@ export default class Search extends Component{ searchValue(value, page=1, loading="") { var simple = new URLSearchParams(window.location.search).get("simple") || false; - if (value.startsWith("wit1")) { + const network_type = process.env.REACT_APP_NETWORK; + + if (network_type === "MAINNET" && value.startsWith("wit1")) { + this.setState({ + search_value: value, + searching_address: true, + }); + } + else if (network_type === "TESTNET" && value.startsWith("twit1")) { this.setState({ search_value: value, searching_address: true, }); } + else if (network_type === "MAINNET" && value.startsWith("twit1")) { + this.setState({ + error_value: "Tried searching for a testnet address when the explorer is configured for mainnet", + }); + } + else if (network_type === "TESTNET" && value.startsWith("wit1")) { + this.setState({ + error_value: "Tried searching for a mainnet address when the explorer is configured for testnet", + }); + } else { if (loading === "history") { this.setState({ From c2d06b831cfee776472479c96f1e1a436b7d550a Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 14 Mar 2025 22:15:39 +0100 Subject: [PATCH 12/19] [search] Adapt layout of a value transfer to be more responsive to screen sizes --- src/Pages/SearchPages/ValueTransferPanel.js | 216 ++++++++++---------- 1 file changed, 105 insertions(+), 111 deletions(-) diff --git a/src/Pages/SearchPages/ValueTransferPanel.js b/src/Pages/SearchPages/ValueTransferPanel.js index 32bc2ec..2be4427 100644 --- a/src/Pages/SearchPages/ValueTransferPanel.js +++ b/src/Pages/SearchPages/ValueTransferPanel.js @@ -247,125 +247,117 @@ export default class ValueTransferPanel extends Component { let output_idx = 0; return ( -
- - - - + + + ); + }); + + return ([ + + + , + output_utxo_rows + ]); + }) + } + +
- - - { - Object.keys(grouped_inputs).map(function(address) { - var input_address_link = {address}; - - var input_utxo_rows = grouped_inputs[address].map(function(input){ - var input_value = showNanoWitValues - ? Formatter.formatValue(input.value) + " nWIT" - : Formatter.formatWitValue(input.value, 2); - var input_utxo_link = ( - - {input.utxo[1] + ":" + input.utxo[0]} - - ); - return ( - - - - - ); - }); +
- {input_utxo_link} - - {input_value} -
+ + + - - + + - - -
+ + + { + Object.keys(grouped_inputs).map(function(address) { + var input_address_link = {address}; - return ([ + var input_utxo_rows = grouped_inputs[address].map(function(input){ + var input_value = showNanoWitValues + ? Formatter.formatValue(input.value) + " nWIT" + : Formatter.formatWitValue(input.value, 2); + var input_utxo_link = ( + + {input.utxo[1] + ":" + input.utxo[0]} + + ); + return ( - - , - input_utxo_rows - ]); - }) - } - -
- {input_address_link} + + {input_utxo_link} - -
-
- - - - - { - Object.keys(grouped_outputs).map(function(address){ - var output_address_link = {address}; + + + ); + }); - var output_utxo_rows = grouped_outputs[address].map(function(output){ - var output_text = output_idx + ":" + data.hash; - var output_value = showNanoWitValues - ? Formatter.formatValue(output.value) + " nWIT" - : Formatter.formatWitValue(output.value, 2); - var output_timelock = output.timelocked - ? TimeConverter.convertUnixTimestamp(output.timelock, "full") - : ""; - var output_timelocked = output.timelocked - ? - : ; + return ([ + + + , + input_utxo_rows + ]); + }) + } + +
+ {input_value} +
+ {input_address_link} + + +
+
+ + + + + { + Object.keys(grouped_outputs).map(function(address){ + var output_address_link = {address}; - output_idx = output_idx + 1; + var output_utxo_rows = grouped_outputs[address].map(function(output){ + var output_text = output_idx + ":" + data.hash; + var output_value = showNanoWitValues + ? Formatter.formatValue(output.value) + " nWIT" + : Formatter.formatWitValue(output.value, 2); + var output_timelock = output.timelocked + ? TimeConverter.convertUnixTimestamp(output.timelock, "full") + : ""; + var output_timelocked = output.timelocked + ? + : ; - return ( - - - - - - ); - }); + output_idx = output_idx + 1; - return ([ + return ( - - , - output_utxo_rows - ]); - }) - } - -
- {output_text} - - {output_value} - - {output_timelocked} - {output_timelock} -
- {output_address_link} + + {output_text} - -
-
- +
+ {output_value} + + {output_timelocked} + {output_timelock} +
+ {output_address_link} + + +
+ + + + ); } @@ -407,7 +399,7 @@ export default class ValueTransferPanel extends Component { - + { this.generateDetailsCard(this.props.data) } @@ -416,6 +408,8 @@ export default class ValueTransferPanel extends Component { ? this.generateInputOutputUtxos(this.props.data, this.state.showNanoWitValues) : this.generateInputOutputAddresses(this.props.data, this.state.showNanoWitValues) } + + { this.generateUtxoCheck() } From 6d9c1bb995b296b7187a8f9cec8b371d996661d2 Mon Sep 17 00:00:00 2001 From: drcpu Date: Sun, 16 Mar 2025 21:57:12 +0100 Subject: [PATCH 13/19] [address] Add a stake and unstake tab --- src/Pages/SearchPages/AddressPanel.js | 241 ++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/src/Pages/SearchPages/AddressPanel.js b/src/Pages/SearchPages/AddressPanel.js index 1913de2..b03752d 100644 --- a/src/Pages/SearchPages/AddressPanel.js +++ b/src/Pages/SearchPages/AddressPanel.js @@ -1,4 +1,5 @@ import React, { Component } from "react"; +import { Link } from "react-router-dom"; import { Card, Col, Container, Row, Spinner, Tab, Table, Tabs } from "react-bootstrap"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -31,6 +32,10 @@ export default class AddressPanel extends Component { data_requests_solved: null, data_requests_created_pagination: null, data_requests_created: null, + stakes_pagination: null, + stakes: null, + unstakes_pagination: null, + unstakes: null, current_page: 1, current_tab: "value-transfers", error_value: "", @@ -64,6 +69,12 @@ export default class AddressPanel extends Component { else if (tab === "data-requests-created" && this.state.data_requests_created === null) { this.loadData(this.state.address, "data-requests-created", 1); } + else if (tab === "stakes" && this.state.stakes === null) { + this.loadData(this.state.address, "stakes", 1); + } + else if (tab === "unstakes" && this.state.unstakes === null) { + this.loadData(this.state.address, "unstakes", 1); + } } loadData(address, tab, page) { @@ -122,6 +133,18 @@ export default class AddressPanel extends Component { data_requests_created: response[1], }); } + else if (tab === "stakes") { + this.setState({ + stakes_pagination: JSON.parse(response[0].get("X-Pagination")), + stakes: response[1], + }); + } + else if (tab === "unstakes") { + this.setState({ + unstakes_pagination: JSON.parse(response[0].get("X-Pagination")), + unstakes: response[1], + }); + } }) .catch(e => { console.log(e); @@ -756,6 +779,206 @@ export default class AddressPanel extends Component { ); } + generateStakesCard() { + const { stakes_pagination, stakes } = this.state; + var total_stakes = stakes_pagination.total; + + return ( + + + + + + + + + + + + + + { + stakes.map(function(stake){ + const stake_link = "/search/" + stake.hash; + const validator_link = "/search/" + stake.validator; + const withdrawer_link = "/search/" + stake.withdrawer; + + let icon; + // Stake transaction to this validator from the validator address + if (stake.direction === "self") { + icon = + } + // Stake transaction to this validator from another set of addresses + else if (stake.direction === "in") { + icon = + } + // Stake transaction sent from this address to another validator + else if (stake.direction === "out") { + icon = + } + + return ( + + + + + + + + + ); + }) + } + +
+ {"Transaction"} + + {"Timestamp"} + + {"Validator"} + + {"Withdrawer"} + + {"Value"} + + {"Locked"} +
+ {icon}{stake.hash} + + {TimeConverter.convertUnixTimestamp(stake.timestamp, "full")} + + {stake.validator} + + {stake.withdrawer} + + {Formatter.formatWitValue(stake.stake_value)} + + { + stake.locked + ? + : + } +
+ +
+ ); + } + + generateUnstakesCard() { + const { unstakes_pagination, unstakes } = this.state; + var total_unstakes = unstakes_pagination.total; + + return ( + + + + + + + + + + + + + + { + unstakes.map(function(unstake){ + const unstake_link = "/search/" + unstake.hash; + const validator_link = "/search/" + unstake.validator; + const withdrawer_link = "/search/" + unstake.withdrawer; + + let icon; + // Unstake transaction from this validator to the validator address + if (unstake.direction === "self") { + icon = + } + // Unstake transaction from a validator to this address + else if (unstake.direction === "in") { + icon = + } + // Unstake transaction from this validator to another address + else if (unstake.direction === "out") { + icon = + } + + return ( + + + + + + + + + ); + }) + } + +
+ {"Transaction"} + + {"Timestamp"} + + {"Validator"} + + {"Withdrawer"} + + {"Value"} + + {"Locked"} +
+ {icon}{unstake.hash} + + {TimeConverter.convertUnixTimestamp(unstake.timestamp, "full")} + + {unstake.validator} + + {unstake.withdrawer} + + {Formatter.formatWitValue(unstake.unstake_value)} + + { + unstake.locked + ? + : + } +
+ +
+ ); + } + render() { const { details, info, error_value } = this.state; @@ -840,6 +1063,24 @@ export default class AddressPanel extends Component { }
+ + + { + this.state.stakes === null + ? + : this.generateStakesCard() + } + + + + + { + this.state.unstakes === null + ? + : this.generateUnstakesCard() + } + +
From 5e5fb9e5b22cb108b9d8845a6d7bbb34a9048b5b Mon Sep 17 00:00:00 2001 From: drcpu Date: Thu, 20 Mar 2025 22:20:27 +0100 Subject: [PATCH 14/19] [network] Remove staking tab from network page --- src/Pages/Network.js | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/Pages/Network.js b/src/Pages/Network.js index 1dcb13f..f7beab3 100644 --- a/src/Pages/Network.js +++ b/src/Pages/Network.js @@ -19,7 +19,6 @@ const tab_request_map = { "lie_rates": "histogram-data-request-lie-rate", "supply_burn_rate": "histogram-burn-rate", "value_transfers": "histogram-value-transfers", - "staking": "percentile-staking-balances", "miners": "top-100-miners", "solvers": "top-100-data-request-solvers", "rollbacks": "list-rollbacks", @@ -363,39 +362,6 @@ export default class Network extends Component { } } - generateStakingCard() { - const { data } = this.state; - - var named_data = data.staking.ars.map(function (balance, idx) { - return ( - { - "percentile": data.staking.percentiles[idx], - "balance": balance, - } - ); - }); - - return ( - - - - - - value + "%"}> - - { return Formatter.formatWitValue(value, 0) }} scale="log" domain={['auto', 'auto']}> - - Formatter.formatWitValue(value, 2)}/> - - - - - - ); - } - generateMinersCard(address_type, amount_type) { const { data } = this.state; @@ -590,9 +556,6 @@ export default class Network extends Component { else if (active_tab === "value_transfers") { data_card = this.generateValueTransfersCard(); } - else if (active_tab === "staking") { - data_card = this.generateStakingCard(); - } else if (active_tab === "miners") { data_card = this.generateMinersCard("Miner", "Blocks"); } @@ -648,9 +611,6 @@ export default class Network extends Component { {data_card} - - {data_card} - {data_card} From 702e8e8391912a88b25958deb9e916119188ede6 Mon Sep 17 00:00:00 2001 From: drcpu Date: Thu, 20 Mar 2025 22:25:13 +0100 Subject: [PATCH 15/19] [package] Update caniuse-lite version --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3028227..2706718 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5853,9 +5853,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001563", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", - "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", + "version": "1.0.30001706", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz", + "integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==", "dev": true, "funding": [ { @@ -22708,9 +22708,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001563", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", - "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", + "version": "1.0.30001706", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz", + "integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==", "dev": true }, "case-sensitive-paths-webpack-plugin": { From 9c75fca48ed112ca822657123a1735f6e67b3101 Mon Sep 17 00:00:00 2001 From: drcpu Date: Tue, 25 Mar 2025 20:39:09 +0100 Subject: [PATCH 16/19] [info] Update links on the info page --- src/Pages/Info.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Pages/Info.js b/src/Pages/Info.js index 394d706..be82501 100644 --- a/src/Pages/Info.js +++ b/src/Pages/Info.js @@ -15,7 +15,7 @@ export default class Info extends Component { - {"Official website and overview"} + {"Official website"} @@ -29,7 +29,7 @@ export default class Info extends Component { - {"https://witnet.io/about"} + {"https://docs.witnet.io"} @@ -40,7 +40,7 @@ export default class Info extends Component { - {"Official desktop wallet for Linux, Mac and Windows"} + {"Official wallets for Linux, Mac and Windows"} @@ -51,21 +51,10 @@ export default class Info extends Component { - - - - - - - - {"Community maintained documentation"} - - - - {"https://docs.witnet.io"} + {"https://mywitwallet.com"} From f68df4a7b6006de3f634719878831489d67699d5 Mon Sep 17 00:00:00 2001 From: drcpu Date: Thu, 27 Mar 2025 22:10:25 +0100 Subject: [PATCH 17/19] [staking] Add staking page showing validator performance and rewards --- src/App.js | 6 +- src/Pages/Reputation.js | 239 ---------------------------- src/Pages/Staking.js | 301 ++++++++++++++++++++++++++++++++++++ src/Services/DataService.js | 4 +- src/fontawesome.js | 6 + src/stylesheet.css | 8 + 6 files changed, 320 insertions(+), 244 deletions(-) delete mode 100644 src/Pages/Reputation.js create mode 100644 src/Pages/Staking.js diff --git a/src/App.js b/src/App.js index 83f9956..436c026 100644 --- a/src/App.js +++ b/src/App.js @@ -7,7 +7,7 @@ import Search from './Pages/Search'; import Blockchain from './Pages/Blockchain'; import Mempool from './Pages/Mempool'; import Network from './Pages/Network'; -import Reputation from './Pages/Reputation'; +import Staking from './Pages/Staking'; import Balances from './Pages/Balances'; import TAPI from './Pages/TAPI'; import Info from './Pages/Info'; @@ -31,7 +31,7 @@ class WitnetExplorer extends React.Component{ Blockchain Mempool Network - Reputation + Staking Balances TAPI Info @@ -46,7 +46,7 @@ class WitnetExplorer extends React.Component{ - + diff --git a/src/Pages/Reputation.js b/src/Pages/Reputation.js deleted file mode 100644 index 92dfa32..0000000 --- a/src/Pages/Reputation.js +++ /dev/null @@ -1,239 +0,0 @@ -import React, { Component } from "react"; -import { Link } from 'react-router-dom'; -import { Card, Col, Container, Row, Table } from "react-bootstrap"; -import { ResponsiveContainer, AreaChart, CartesianGrid, XAxis, YAxis, Label, Tooltip, Area } from 'recharts'; - -import ErrorCard from "../Components/ErrorCard"; -import Paginator from "../Components/Paginator"; -import SpinnerCard from "../Components/SpinnerCard"; - -import DataService from "../Services/DataService"; -import TimeConverter from "../Services/TimeConverter"; - -export default class Reputation extends Component{ - constructor(props) { - super(props); - this.state = { - loading : true, - error_value: "", - last_updated : "", - window_width: 0, - window_height: 0, - rows_per_page: 0, - current_page: 1, - total_pages: 0, - reputation_rows: [], - } - - this.getReputation = this.getReputation.bind(this); - this.onChangePage = this.onChangePage.bind(this); - this.updateWindowDimensions = this.updateWindowDimensions.bind(this); - } - - getReputation(epoch) { - DataService.getReputation(epoch) - .then(response => { - var new_reputation_rows = this.generateReputationRows(response.reputation, response.total_reputation); - this.reputation_chart = this.generateReputationChart(response.reputation); - this.eligibility_chart = this.generateEligibilityChart(response.reputation); - this.setState({ - reputation_rows: new_reputation_rows, - total_pages: Math.ceil(response.reputation.length / this.state.rows_per_page), - loading : false, - error_value : "", - last_updated : TimeConverter.convertUnixTimestamp(response.last_updated, "full") - }); - }) - .catch(e => { - console.log(e); - this.setState({ - error_value : "Could not fetch reputation!" - }); - }); - } - - generateReputationRows(reputations, total_reputation) { - var reputation_rows = reputations.map(function(reputation){ - var api_link = "/search/" + reputation.address; - var reputation_link = {reputation.address} - - return ( - - - {reputation_link} - - - {reputation.reputation} - - - {reputation.eligibility.toFixed(4) + "%"} - - - ); - }) - - return reputation_rows; - } - - onChangePage(paginator) { - this.setState({ - current_page: paginator.current_page - }); - } - - generateReputationCard(){ - var row_start = (this.state.current_page - 1) * this.state.rows_per_page; - var row_stop = this.state.current_page * this.state.rows_per_page; - - return ( - - - - - - - - - - - - - {this.state.reputation_rows.slice(row_start, row_stop)} - -
- {"Addresses " + (row_start + 1) + " to " + row_stop} - - {"Reputation"} - - {"Eligibility"} -
-
-
- - - Last updated: {this.state.last_updated} - - - -
- ); - } - - generateReputationChart(reputations) { - var plot_data = []; - reputations.forEach(function(reputation) { - plot_data.push({ - "Address": reputation.address, - "Reputation": reputation.reputation, - }); - }); - - return ( - - - - - - - - - - - - ); - } - - generateEligibilityChart(reputations) { - var plot_data = []; - reputations.forEach(function(reputation) { - plot_data.push({ - "Address": reputation.address, - "Eligibility": reputation.eligibility, - }); - }); - - return ( - - - - - tick + "%"}> - - - - - - - ); - } - - generateChartCard() { - return ( - - - {this.reputation_chart} - {this.eligibility_chart} - - - Last updated: {this.state.last_updated} - - - ); - } - - updateWindowDimensions() { - this.setState({ - window_width: window.innerWidth, - window_height: window.innerHeight, - rows_per_page: Math.floor(window.innerHeight * 0.7 / 20) - }); - } - - componentDidMount() { - this.getReputation(); - this.updateWindowDimensions(); - window.addEventListener('resize', this.updateWindowDimensions); - } - - componentWillUnmount() { - window.removeEventListener('resize', this.updateWindowDimensions); - } - - render() { - const { loading, error_value } = this.state; - - if (error_value === "") { - return( - - - - { - loading - ? - : this.generateReputationCard() - } - - - { - loading - ? - : this.generateChartCard() - } - - - - ); - } - else { - return ( - - ; - - ); - } - } -} diff --git a/src/Pages/Staking.js b/src/Pages/Staking.js new file mode 100644 index 0000000..3fc4f4c --- /dev/null +++ b/src/Pages/Staking.js @@ -0,0 +1,301 @@ +import React, { Component } from "react"; +import { Link } from "react-router-dom"; +import { Card, Container, OverlayTrigger, Table, Tooltip } from "react-bootstrap"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import ErrorCard from "../Components/ErrorCard"; +import SpinnerCard from "../Components/SpinnerCard"; + +import DataService from "../Services/DataService"; +import Formatter from "../Services/Formatter"; +import TimeConverter from "../Services/TimeConverter"; + +export default class Staking extends Component { + constructor(props) { + super(props); + + this.state = { + loading: true, + validator_data: [], + sort_field: "", + sort_order: "", + last_updated: "", + error_value: "", + table_style: {}, + }; + + this.addExtraData = this.addExtraData.bind(this); + this.getStakes = this.getStakes.bind(this); + this.sortTable = this.sortTable.bind(this); + this.updateTableDimensions = this.updateTableDimensions.bind(this); + } + + getStakes() { + DataService.getStakes() + .then((response) => { + this.setState({ + loading: false, + validator_data: this.addExtraData(response.stakes), + sort_field: "current_stake", + sort_order: "desc", + last_updated: TimeConverter.convertUnixTimestamp(response.last_updated, "full"), + error_value: "", + }); + }) + .catch((e) => { + console.log(e); + this.setState({ + error_value: "Could not fetch validator data!", + }); + }); + } + + addExtraData(stakes) { + var now = Math.floor(Date.now() / 1000); + var one_year = 365 * 24 * 60 * 60; + + return stakes.map(function (stake) { + var time_since_genesis = now - stake.genesis; + stake.score = (((stake.rewards / stake.time_weighted_stake) * one_year) / time_since_genesis) * 100; + + return stake; + }); + } + + generateValidatorCard() { + const sort_icon = ( + + ); + const sort_direction_icon = + this.state.sort_order === "asc" ? ( + + ) : ( + + ); + + return ( + + + + + + + + {"The time of the first stake transaction"} + } + > + + + + {"The time of the last proposed block or solved data request"} + + } + > + + + + + {"The sum of all block rewards and transaction fees received"} + + } + > + + + + + + { + "The amount of times a validator was slashed for revealing an out-of-consensus value" + } + + } + > + + + + { + "The rewards a validator received compared to its average stake over time" + } + + } + > + + + + + + {this.state.validator_data.map(function (data) { + var validator_link = {data.validator}; + + return ( + + + + + + + + + + + + ); + })} + +
this.sortTable("validator")} + > + {"Validator"} + {this.state.sort_field === "validator" ? sort_direction_icon : sort_icon} + this.sortTable("genesis")} + > + {"Genesis"} + {this.state.sort_field === "genesis" ? sort_direction_icon : sort_icon} + this.sortTable("last_active")} + > + {"Last active"} + {this.state.sort_field === "last_active" ? sort_direction_icon : sort_icon} + this.sortTable("current_stake")} + > + {"Current stake"} + {this.state.sort_field === "current_stake" ? sort_direction_icon : sort_icon} + this.sortTable("rewards")} + > + {"Rewards"} + {this.state.sort_field === "rewards" ? sort_direction_icon : sort_icon} + this.sortTable("blocks")} + > + {"Blocks"} + {this.state.sort_field === "blocks" ? sort_direction_icon : sort_icon} + this.sortTable("data_requests")} + > + {"Data requests"} + {this.state.sort_field === "data_requests" ? sort_direction_icon : sort_icon} + this.sortTable("lies")} + > + {"Slashes"} + {this.state.sort_field === "lies" ? sort_direction_icon : sort_icon} + this.sortTable("score")} + > + {"Score"} + {this.state.sort_field === "score" ? sort_direction_icon : sort_icon} +
+ {validator_link} + + {TimeConverter.convertUnixTimestamp(data.genesis, "full")} + + {data.last_active === 0 + ? "never" + : TimeConverter.convertUnixTimestamp(data.last_active, "full")} + + {Formatter.formatWitValue(data.current_stake, 0)} + + {Formatter.formatWitValue(data.rewards, 0)} + + {Formatter.formatValue(data.blocks)} + + {Formatter.formatValue(data.data_requests)} + + {Formatter.formatValue(data.lies)} + + {Formatter.formatValueRound(data.score, 2) + "%"} +
+
+
+ + Last updated: {this.state.last_updated} + +
+ ); + } + + updateTableDimensions() { + this.setState({ + table_style: { + display: "block", + height: window.innerHeight * 0.75 + "px", + overflow: "scroll", + margin: "0px", + }, + }); + } + + componentDidMount() { + this.updateTableDimensions(); + this.getStakes(); + window.addEventListener("resize", this.updateTableDimensions); + } + + componentWillUnmount() { + window.removeEventListener("resize", this.updateTableDimensions); + } + + sortTable(key) { + const order = this.state.sort_field === key && this.state.sort_order === "asc" ? "desc" : "asc"; + + this.setState({ + validator_data: [] + .concat(this.state.validator_data) + .sort((a, b) => (a[key] > b[key] ? 1 : -1) * (order === "asc" ? 1 : -1)), + sort_field: key, + sort_order: order, + }); + } + + render() { + const { loading, error_value } = this.state; + + if (error_value === "") { + return ( + + {loading ? : this.generateValidatorCard()} + + ); + } else { + return ( + + ; + + ); + } + } +} diff --git a/src/Services/DataService.js b/src/Services/DataService.js index 64cabf1..f393270 100644 --- a/src/Services/DataService.js +++ b/src/Services/DataService.js @@ -38,8 +38,8 @@ class DataService { }); } - getReputation() { - return fetch("/api/network/reputation").then(response => response.json()); + getStakes() { + return fetch("/api/network/stakes").then(response => response.json()); } getBalances(page = 1) { diff --git a/src/fontawesome.js b/src/fontawesome.js index 3b49586..cf830bf 100644 --- a/src/fontawesome.js +++ b/src/fontawesome.js @@ -42,6 +42,9 @@ import { faCirclePlus, faCircleMinus, faNetworkWired, + faSort, + faSortUp, + faSortDown } from '@fortawesome/free-solid-svg-icons'; import { @@ -114,4 +117,7 @@ library.add( faCirclePlus, faCircleMinus, faNetworkWired, + faSort, + faSortUp, + faSortDown ); diff --git a/src/stylesheet.css b/src/stylesheet.css index 4960183..f652312 100644 --- a/src/stylesheet.css +++ b/src/stylesheet.css @@ -151,6 +151,14 @@ body { white-space: nowrap; } +.tooltip-inner { + background-color: rgb(11, 177, 165); + color: white; +} +.tooltip.bs-tooltip-top .tooltip-arrow::before { + border-top-color: rgb(11, 177, 165); +} + .navbar-color { background-color: #12243a; } From 721606a3b8618e5bd4eb5674f32f29ebf1b3938c Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 6 Jun 2025 13:04:04 +0200 Subject: [PATCH 18/19] [formatter] Format WIT value with a suffix based on its absolute value --- src/Services/Formatter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Services/Formatter.js b/src/Services/Formatter.js index 4d1d379..207a1d3 100644 --- a/src/Services/Formatter.js +++ b/src/Services/Formatter.js @@ -68,19 +68,19 @@ class Formatter { } formatWitValue(value, decimals) { - if (value < 1000) { + if (Math.abs(value) < 1000) { return value.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 }) + " nWIT"; } - else if (value < 1000000){ + else if (Math.abs(value) < 1000000){ return (Math.floor(value / 10) / 100).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) + " uWIT"; } - else if (value < 1000000000){ + else if (Math.abs(value) < 1000000000){ return (Math.floor(value / 10000) / 100).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals From c7ed5d40ae50e9d6e5aacb7ea7e734aa017f3d27 Mon Sep 17 00:00:00 2001 From: drcpu Date: Fri, 6 Jun 2025 13:04:46 +0200 Subject: [PATCH 19/19] [home] Add some margin to the lower Y-axis value of the total staked graph --- src/Pages/Home.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Pages/Home.js b/src/Pages/Home.js index 7ae52a6..f2499f3 100644 --- a/src/Pages/Home.js +++ b/src/Pages/Home.js @@ -174,6 +174,8 @@ export default class Home extends Component { } }); + var minimum_staked = Math.min(...staked.map(({ staked }) => staked)); + return ( @@ -187,8 +189,8 @@ export default class Home extends Component { /> Formatter.formatValueSuffix(tick / 1e9, 2)} + domain={[minimum_staked * 0.95, "auto"]} + tickFormatter={(tick) => Formatter.formatValueSuffix(tick / 1e9, 0)} /> TimeConverter.convertUnixTimestamp(value, "full")}