From 41d02f6e2970023041a02ed14488c7cbae7b6be4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 06:37:04 +0000 Subject: [PATCH 01/45] Bump @types/react from 17.0.35 to 17.0.36 (#124) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index d3f7ea8..de5983b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.24", "@types/node": "^14.17.5", - "@types/react": "^17.0.35", + "@types/react": "^17.0.36", "@types/react-blockies": "^1.4.1", "@types/react-dom": "^17.0.11", "@types/react-highlight": "^0.12.5", @@ -3127,9 +3127,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.35.tgz", - "integrity": "sha512-r3C8/TJuri/SLZiiwwxQoLAoavaczARfT9up9b4Jr65+ErAUX3MIkU0oMOQnrpfgHme8zIqZLX7O5nnjm5Wayw==", + "version": "17.0.36", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.36.tgz", + "integrity": "sha512-CUFUp01OdfbpN/76v4koqgcpcRGT3sYOq3U3N6q0ZVGcyeP40NUdVU+EWe3hs34RNaTefiYyBzOpxBBidCc5zw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -21607,9 +21607,9 @@ "version": "1.5.4" }, "@types/react": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.35.tgz", - "integrity": "sha512-r3C8/TJuri/SLZiiwwxQoLAoavaczARfT9up9b4Jr65+ErAUX3MIkU0oMOQnrpfgHme8zIqZLX7O5nnjm5Wayw==", + "version": "17.0.36", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.36.tgz", + "integrity": "sha512-CUFUp01OdfbpN/76v4koqgcpcRGT3sYOq3U3N6q0ZVGcyeP40NUdVU+EWe3hs34RNaTefiYyBzOpxBBidCc5zw==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", diff --git a/package.json b/package.json index 386cded..ac5a0a3 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.24", "@types/node": "^14.17.5", - "@types/react": "^17.0.35", + "@types/react": "^17.0.36", "@types/react-blockies": "^1.4.1", "@types/react-dom": "^17.0.11", "@types/react-highlight": "^0.12.5", From 6128cbbb0efee422b48227486cbf58952ad1bed2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 06:37:20 +0000 Subject: [PATCH 02/45] Bump typescript from 4.4.4 to 4.5.2 (#122) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index de5983b..ef2742a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "react-scripts": "4.0.3", "react-syntax-highlighter": "^15.4.5", "serve": "^13.0.2", - "typescript": "^4.4.4", + "typescript": "^4.5.2", "use-keyboard-shortcut": "^1.0.6", "web-vitals": "^1.0.1" }, @@ -18154,9 +18154,9 @@ } }, "node_modules/typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -31794,9 +31794,9 @@ } }, "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==" + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==" }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4" diff --git a/package.json b/package.json index ac5a0a3..4858284 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "react-scripts": "4.0.3", "react-syntax-highlighter": "^15.4.5", "serve": "^13.0.2", - "typescript": "^4.4.4", + "typescript": "^4.5.2", "use-keyboard-shortcut": "^1.0.6", "web-vitals": "^1.0.1" }, From c7187118458fd9cd3b397bfa707019bb94cd8849 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 23:00:59 +0000 Subject: [PATCH 03/45] Bump @craco/craco from 6.4.0 to 6.4.1 (#125) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef2742a..90e0882 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@blackbox-vision/react-qr-reader": "^5.0.0", "@chainlink/contracts": "^0.2.2", - "@craco/craco": "^6.4.0", + "@craco/craco": "^6.4.1", "@fontsource/fira-code": "^4.5.2", "@fontsource/roboto": "^4.5.1", "@fontsource/roboto-mono": "^4.5.0", @@ -1249,9 +1249,9 @@ } }, "node_modules/@craco/craco": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.4.0.tgz", - "integrity": "sha512-puLp+pSL5B2tpoIPUYlWjKd0VDBPNF16BJIKEKrwg0x/9XC/4h8XPcVGNr6pd27pj8sahiH5QUdoBxB5AE9++g==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.4.1.tgz", + "integrity": "sha512-KZDbnHUwxoWrNPeSbfThnGFlG+K+0ZwnwYAj+JgtUCN5FRZ5QQOlc9hhMb44pN3CpWEOTyA0YE/rJeuAXFAErw==", "dependencies": { "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", "cosmiconfig": "^7.0.1", @@ -20448,9 +20448,9 @@ } }, "@craco/craco": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.4.0.tgz", - "integrity": "sha512-puLp+pSL5B2tpoIPUYlWjKd0VDBPNF16BJIKEKrwg0x/9XC/4h8XPcVGNr6pd27pj8sahiH5QUdoBxB5AE9++g==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.4.1.tgz", + "integrity": "sha512-KZDbnHUwxoWrNPeSbfThnGFlG+K+0ZwnwYAj+JgtUCN5FRZ5QQOlc9hhMb44pN3CpWEOTyA0YE/rJeuAXFAErw==", "requires": { "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", "cosmiconfig": "^7.0.1", diff --git a/package.json b/package.json index 4858284..3207165 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dependencies": { "@blackbox-vision/react-qr-reader": "^5.0.0", "@chainlink/contracts": "^0.2.2", - "@craco/craco": "^6.4.0", + "@craco/craco": "^6.4.1", "@fontsource/fira-code": "^4.5.2", "@fontsource/roboto": "^4.5.1", "@fontsource/roboto-mono": "^4.5.0", From 887ef5607f9e095f6e7d446dfbbf106a699ab697 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 23:02:00 +0000 Subject: [PATCH 04/45] Bump chart.js from 3.5.1 to 3.6.0 (#104) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90e0882..a673802 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "@types/react-highlight": "^0.12.5", "@types/react-router-dom": "^5.3.2", "@types/react-syntax-highlighter": "^13.5.2", - "chart.js": "^3.5.1", + "chart.js": "^3.6.0", "ethers": "^5.5.1", "highlightjs-solidity": "^2.0.2", "query-string": "^7.0.1", @@ -5602,9 +5602,9 @@ } }, "node_modules/chart.js": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz", - "integrity": "sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ==" + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.0.tgz", + "integrity": "sha512-iOzzDKePL+bj+ccIsVAgWQehCXv8xOKGbaU2fO/myivH736zcx535PGJzQGanvcSGVOqX6yuLZsN3ygcQ35UgQ==" }, "node_modules/check-types": { "version": "11.1.2", @@ -23331,9 +23331,9 @@ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" }, "chart.js": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz", - "integrity": "sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ==" + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.0.tgz", + "integrity": "sha512-iOzzDKePL+bj+ccIsVAgWQehCXv8xOKGbaU2fO/myivH736zcx535PGJzQGanvcSGVOqX6yuLZsN3ygcQ35UgQ==" }, "check-types": { "version": "11.1.2" diff --git a/package.json b/package.json index 3207165..45fea96 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@types/react-highlight": "^0.12.5", "@types/react-router-dom": "^5.3.2", "@types/react-syntax-highlighter": "^13.5.2", - "chart.js": "^3.5.1", + "chart.js": "^3.6.0", "ethers": "^5.5.1", "highlightjs-solidity": "^2.0.2", "query-string": "^7.0.1", From b17a1d4f2741ee51f89120c5388fae02f9856fb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 23:03:17 +0000 Subject: [PATCH 05/45] Bump @testing-library/jest-dom from 5.15.0 to 5.15.1 (#127) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index a673802..6ac2be9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.16", "@headlessui/react": "^1.4.2", - "@testing-library/jest-dom": "^5.15.0", + "@testing-library/jest-dom": "^5.15.1", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.24", @@ -2887,9 +2887,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.15.0.tgz", - "integrity": "sha512-lOMuQidnL1tWHLEWIhL6UvSZC1Qt3OkNe1khvi2h6xFiqpe5O8arYs46OU0qyUGq0cSTbroQyMktYNXu3a7sAA==", + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.15.1.tgz", + "integrity": "sha512-kmj8opVDRE1E4GXyLlESsQthCXK7An28dFWxhiMwD7ZUI7ZxA6sjdJRxLerD9Jd8cHX4BDc1jzXaaZKqzlUkvg==", "dependencies": { "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", @@ -21420,9 +21420,9 @@ } }, "@testing-library/jest-dom": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.15.0.tgz", - "integrity": "sha512-lOMuQidnL1tWHLEWIhL6UvSZC1Qt3OkNe1khvi2h6xFiqpe5O8arYs46OU0qyUGq0cSTbroQyMktYNXu3a7sAA==", + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.15.1.tgz", + "integrity": "sha512-kmj8opVDRE1E4GXyLlESsQthCXK7An28dFWxhiMwD7ZUI7ZxA6sjdJRxLerD9Jd8cHX4BDc1jzXaaZKqzlUkvg==", "requires": { "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", diff --git a/package.json b/package.json index 45fea96..3add342 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.16", "@headlessui/react": "^1.4.2", - "@testing-library/jest-dom": "^5.15.0", + "@testing-library/jest-dom": "^5.15.1", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.24", From 18b834c852ab6d723e06c7d36dd00bb2eb3d92b5 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Nov 2021 20:53:49 -0300 Subject: [PATCH 06/45] Upgrade react-chartjs-2 to v4; make code tree-shakeable --- package-lock.json | 14 +++++++------- package.json | 2 +- src/special/london/Blocks.tsx | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ac2be9..07a0037 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "query-string": "^7.0.1", "react": "^17.0.2", "react-blockies": "^1.4.1", - "react-chartjs-2": "^3.3.0", + "react-chartjs-2": "^4.0.0", "react-dom": "^17.0.2", "react-error-boundary": "^3.1.4", "react-helmet-async": "^1.1.2", @@ -14388,9 +14388,9 @@ } }, "node_modules/react-chartjs-2": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.3.0.tgz", - "integrity": "sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.0.0.tgz", + "integrity": "sha512-0kx41EVO6wIoeU6zvdwovX9kKcdrs7O62DGTSNmwAXZeLGJ3U+n4XijO1kxcMmAi4I6PQJWGD5oRwxVixHSp6g==", "peerDependencies": { "chart.js": "^3.5.0", "react": "^16.8.0 || ^17.0.0" @@ -29181,9 +29181,9 @@ } }, "react-chartjs-2": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.3.0.tgz", - "integrity": "sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.0.0.tgz", + "integrity": "sha512-0kx41EVO6wIoeU6zvdwovX9kKcdrs7O62DGTSNmwAXZeLGJ3U+n4XijO1kxcMmAi4I6PQJWGD5oRwxVixHSp6g==", "requires": {} }, "react-dev-utils": { diff --git a/package.json b/package.json index 3add342..4cdea99 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "query-string": "^7.0.1", "react": "^17.0.2", "react-blockies": "^1.4.1", - "react-chartjs-2": "^3.3.0", + "react-chartjs-2": "^4.0.0", "react-dom": "^17.0.2", "react-error-boundary": "^3.1.4", "react-helmet-async": "^1.1.2", diff --git a/src/special/london/Blocks.tsx b/src/special/london/Blocks.tsx index d100717..095bbc2 100644 --- a/src/special/london/Blocks.tsx +++ b/src/special/london/Blocks.tsx @@ -8,6 +8,15 @@ import React, { import { Block } from "@ethersproject/abstract-provider"; import { FixedNumber } from "@ethersproject/bignumber"; import { Line } from "react-chartjs-2"; +import { + Chart as ChartJS, + LinearScale, + CategoryScale, + PointElement, + LineElement, + Filler, + Tooltip, +} from "chart.js"; import { Transition } from "@headlessui/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn"; @@ -25,6 +34,15 @@ import { gasChartData, } from "./chart"; +ChartJS.register( + LinearScale, + CategoryScale, + PointElement, + LineElement, + Filler, + Tooltip +); + const MAX_BLOCK_HISTORY = 20; const PREV_BLOCK_COUNT = 15; From 33939054f2bc6435b19452da62f2deac8cac66e8 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Thu, 25 Nov 2021 06:28:45 -0300 Subject: [PATCH 07/45] Migrate to react-router v6 --- package-lock.json | 342 +++-------------------------------- package.json | 4 +- src/AddressTransactions.tsx | 225 +++++++++++------------ src/App.tsx | 67 +++---- src/Block.tsx | 17 +- src/BlockTransactions.tsx | 25 +-- src/Home.tsx | 14 +- src/Main.tsx | 26 +++ src/Search.tsx | 36 ---- src/Title.tsx | 19 +- src/Transaction.tsx | 83 +++++---- src/components/NavTab.tsx | 4 +- src/search/CameraScanner.tsx | 6 +- src/search/search.ts | 24 +++ 14 files changed, 305 insertions(+), 587 deletions(-) create mode 100644 src/Main.tsx delete mode 100644 src/Search.tsx diff --git a/package-lock.json b/package-lock.json index 07a0037..00aa601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,12 +31,10 @@ "@types/react-blockies": "^1.4.1", "@types/react-dom": "^17.0.11", "@types/react-highlight": "^0.12.5", - "@types/react-router-dom": "^5.3.2", "@types/react-syntax-highlighter": "^13.5.2", "chart.js": "^3.6.0", "ethers": "^5.5.1", "highlightjs-solidity": "^2.0.2", - "query-string": "^7.0.1", "react": "^17.0.2", "react-blockies": "^1.4.1", "react-chartjs-2": "^4.0.0", @@ -44,7 +42,7 @@ "react-error-boundary": "^3.1.4", "react-helmet-async": "^1.1.2", "react-image": "^4.0.3", - "react-router-dom": "^5.3.0", + "react-router-dom": "^6.0.2", "react-scripts": "4.0.3", "react-syntax-highlighter": "^15.4.5", "serve": "^13.0.2", @@ -3053,11 +3051,6 @@ "@types/unist": "*" } }, - "node_modules/@types/history": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", - "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==" - }, "node_modules/@types/html-minifier-terser": { "version": "5.1.1", "license": "MIT" @@ -3160,25 +3153,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-router": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.15.tgz", - "integrity": "sha512-z3UlMG/x91SFEVmmvykk9FLTliDvfdIUky4k2rCfXWQ0NKbrP8o9BTCaCTPuYsB8gDkUnUmkcA2vYlm2DR+HAA==", - "dependencies": { - "@types/history": "*", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.2.tgz", - "integrity": "sha512-ELEYRUie2czuJzaZ5+ziIp9Hhw+juEw8b7C11YNA4QdLCVbQ3qLi2l4aq8XnlqM7V31LZX8dxUuFUCrzHm6sqQ==", - "dependencies": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, "node_modules/@types/react-syntax-highlighter": { "version": "13.5.2", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-13.5.2.tgz", @@ -8559,14 +8533,6 @@ "node": ">=0.10.0" } }, - "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/finalhandler": { "version": "1.1.2", "license": "MIT", @@ -9288,16 +9254,11 @@ "integrity": "sha512-q0aYUKiZ9MPQg41qx/KpXKaCpqql50qTvmwGYyLFfcjt9AE/+C9CwjVIdJZc7EYj6NGgJuFJ4im1gfgrzUU1fQ==" }, "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.1.0.tgz", + "integrity": "sha512-zPuQgPacm2vH2xdORvGGz1wQMuHSIB56yNAy5FnLuwOwgSYyPKptJtcMm6Ev+hRGeS+GzhbmRacHzvlESbFwDg==", "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" + "@babel/runtime": "^7.7.6" } }, "node_modules/hmac-drbg": { @@ -9309,19 +9270,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/hoopy": { "version": "0.1.4", "license": "MIT", @@ -11674,19 +11622,6 @@ "node": ">=4" } }, - "node_modules/mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/mini-css-extract-plugin": { "version": "0.11.3", "license": "MIT", @@ -14223,23 +14158,6 @@ "node": ">=0.6" } }, - "node_modules/query-string": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz", - "integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==", - "dependencies": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/querystring": { "version": "0.2.0", "engines": { @@ -14594,60 +14512,29 @@ } }, "node_modules/react-router": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", - "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.0.2.tgz", + "integrity": "sha512-8/Wm3Ed8t7TuedXjAvV39+c8j0vwrI5qVsYqjFr5WkJjsJpEvNSoLRUbtqSEYzqaTUj1IV+sbPJxvO+accvU0Q==", "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "history": "^5.1.0" }, "peerDependencies": { - "react": ">=15" + "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz", - "integrity": "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.0.2.tgz", + "integrity": "sha512-cOpJ4B6raFutr0EG8O/M2fEoyQmwvZWomf1c6W2YXBZuFBx8oTk/zqjXghwScyhfrtnt0lANXV2182NQblRxFA==", "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.1", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "history": "^5.1.0", + "react-router": "6.0.2" }, "peerDependencies": { - "react": ">=15" + "react": ">=16.8", + "react-dom": ">=16.8" } }, - "node_modules/react-router/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/react-router/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/react-router/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/react-scripts": { "version": "4.0.3", "license": "MIT", @@ -15430,11 +15317,6 @@ "node": ">=4" } }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, "node_modules/resolve-url": { "version": "0.2.1", "license": "MIT" @@ -16724,14 +16606,6 @@ "node": ">= 6" } }, - "node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "engines": { - "node": ">=6" - } - }, "node_modules/split-string": { "version": "3.1.0", "license": "MIT", @@ -16869,14 +16743,6 @@ "version": "1.0.1", "license": "MIT" }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "engines": { - "node": ">=4" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "license": "MIT", @@ -17854,16 +17720,6 @@ "version": "0.3.0", "license": "MIT" }, - "node_modules/tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -18477,11 +18333,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", @@ -21545,11 +21396,6 @@ "@types/unist": "*" } }, - "@types/history": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", - "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==" - }, "@types/html-minifier-terser": { "version": "5.1.1" }, @@ -21640,25 +21486,6 @@ "@types/react": "*" } }, - "@types/react-router": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.15.tgz", - "integrity": "sha512-z3UlMG/x91SFEVmmvykk9FLTliDvfdIUky4k2rCfXWQ0NKbrP8o9BTCaCTPuYsB8gDkUnUmkcA2vYlm2DR+HAA==", - "requires": { - "@types/history": "*", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.2.tgz", - "integrity": "sha512-ELEYRUie2czuJzaZ5+ziIp9Hhw+juEw8b7C11YNA4QdLCVbQ3qLi2l4aq8XnlqM7V31LZX8dxUuFUCrzHm6sqQ==", - "requires": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, "@types/react-syntax-highlighter": { "version": "13.5.2", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-13.5.2.tgz", @@ -25321,11 +25148,6 @@ "to-regex-range": "^2.1.0" } }, - "filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=" - }, "finalhandler": { "version": "1.1.2", "requires": { @@ -25790,16 +25612,11 @@ "integrity": "sha512-q0aYUKiZ9MPQg41qx/KpXKaCpqql50qTvmwGYyLFfcjt9AE/+C9CwjVIdJZc7EYj6NGgJuFJ4im1gfgrzUU1fQ==" }, "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.1.0.tgz", + "integrity": "sha512-zPuQgPacm2vH2xdORvGGz1wQMuHSIB56yNAy5FnLuwOwgSYyPKptJtcMm6Ev+hRGeS+GzhbmRacHzvlESbFwDg==", "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" + "@babel/runtime": "^7.7.6" } }, "hmac-drbg": { @@ -25810,21 +25627,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, "hoopy": { "version": "0.1.4" }, @@ -27345,15 +27147,6 @@ "min-indent": { "version": "1.0.1" }, - "mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "requires": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - } - }, "mini-css-extract-plugin": { "version": "0.11.3", "requires": { @@ -29076,17 +28869,6 @@ "qs": { "version": "6.7.0" }, - "query-string": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz", - "integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==", - "requires": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, "querystring": { "version": "0.2.0" }, @@ -29320,54 +29102,20 @@ "version": "0.8.3" }, "react-router": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", - "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.0.2.tgz", + "integrity": "sha512-8/Wm3Ed8t7TuedXjAvV39+c8j0vwrI5qVsYqjFr5WkJjsJpEvNSoLRUbtqSEYzqaTUj1IV+sbPJxvO+accvU0Q==", "requires": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } + "history": "^5.1.0" } }, "react-router-dom": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz", - "integrity": "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.0.2.tgz", + "integrity": "sha512-cOpJ4B6raFutr0EG8O/M2fEoyQmwvZWomf1c6W2YXBZuFBx8oTk/zqjXghwScyhfrtnt0lANXV2182NQblRxFA==", "requires": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.1", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "history": "^5.1.0", + "react-router": "6.0.2" } }, "react-scripts": { @@ -29904,11 +29652,6 @@ "resolve-from": { "version": "3.0.0" }, - "resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, "resolve-url": { "version": "0.2.1" }, @@ -30824,11 +30567,6 @@ } } }, - "split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" - }, "split-string": { "version": "3.1.0", "requires": { @@ -30925,11 +30663,6 @@ "stream-shift": { "version": "1.0.1" }, - "strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" - }, "string_decoder": { "version": "1.3.0", "requires": { @@ -31597,16 +31330,6 @@ "timsort": { "version": "0.3.0" }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -32004,11 +31727,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, "vary": { "version": "1.1.2" }, diff --git a/package.json b/package.json index 4cdea99..95e2ed6 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,10 @@ "@types/react-blockies": "^1.4.1", "@types/react-dom": "^17.0.11", "@types/react-highlight": "^0.12.5", - "@types/react-router-dom": "^5.3.2", "@types/react-syntax-highlighter": "^13.5.2", "chart.js": "^3.6.0", "ethers": "^5.5.1", "highlightjs-solidity": "^2.0.2", - "query-string": "^7.0.1", "react": "^17.0.2", "react-blockies": "^1.4.1", "react-chartjs-2": "^4.0.0", @@ -39,7 +37,7 @@ "react-error-boundary": "^3.1.4", "react-helmet-async": "^1.1.2", "react-image": "^4.0.3", - "react-router-dom": "^5.3.0", + "react-router-dom": "^6.0.2", "react-scripts": "4.0.3", "react-syntax-highlighter": "^15.4.5", "serve": "^13.0.2", diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index a1013c8..52432fd 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -1,15 +1,14 @@ import React, { useState, useEffect, useMemo, useContext } from "react"; import { useParams, - useLocation, - useHistory, - Switch, + useNavigate, + Routes, Route, + useSearchParams, } from "react-router-dom"; import { BlockTag } from "@ethersproject/abstract-provider"; import { getAddress, isAddress } from "@ethersproject/address"; import { Tab } from "@headlessui/react"; -import queryString from "query-string"; import Blockies from "react-blockies"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch"; @@ -35,47 +34,50 @@ import { useMultipleMetadata } from "./useSourcify"; import { ChecksummedAddress } from "./types"; import SourcifyLogo from "./sourcify.svg"; -type BlockParams = { - addressOrName: string; - direction?: string; -}; - -type PageParams = { - p?: number; -}; - const AddressTransactions: React.FC = () => { const { provider } = useContext(RuntimeContext); - const params = useParams(); - const location = useLocation(); - const history = useHistory(); - const qs = queryString.parse(location.search); - let hash: string | undefined; - if (qs.h) { - hash = qs.h as string; + const { addressOrName, direction } = useParams(); + if (addressOrName === undefined) { + throw new Error("addressOrName couldn't be undefined here"); } - const [checksummedAddress, setChecksummedAddress] = useState(); + const navigate = useNavigate(); + + const [searchParams] = useSearchParams(); + const h = searchParams.get("h"); + let hash: string | undefined; + if (h) { + hash = h; + } + + const [checksummedAddress, setChecksummedAddress] = useState< + string | undefined + >(); const [isENS, setENS] = useState(); const [error, setError] = useState(); // If it looks like it is an ENS name, try to resolve it useEffect(() => { - if (isAddress(params.addressOrName)) { + // TODO: handle and offer fallback to bad checksummed addresses + if (isAddress(addressOrName)) { setENS(false); setError(false); // Normalize to checksummed address - const _checksummedAddress = getAddress(params.addressOrName); - if (_checksummedAddress !== params.addressOrName) { + const _checksummedAddress = getAddress(addressOrName); + if (_checksummedAddress !== addressOrName) { // Request came with a non-checksummed address; fix the URL - history.replace( + navigate( `/address/${_checksummedAddress}${ - params.direction ? "/" + params.direction : "" - }${location.search}` + direction ? "/" + direction : "" + }?${searchParams.toString()}`, + { replace: true } ); + return; } + setChecksummedAddress(_checksummedAddress); + document.title = `Address ${_checksummedAddress} | Otterscan`; return; } @@ -83,11 +85,12 @@ const AddressTransactions: React.FC = () => { return; } const resolveName = async () => { - const resolvedAddress = await provider.resolveName(params.addressOrName); + const resolvedAddress = await provider.resolveName(addressOrName); if (resolvedAddress !== null) { setENS(true); setError(false); setChecksummedAddress(resolvedAddress); + document.title = `Address ${addressOrName} | Otterscan`; } else { setENS(false); setError(true); @@ -95,13 +98,7 @@ const AddressTransactions: React.FC = () => { } }; resolveName(); - }, [ - provider, - params.addressOrName, - history, - params.direction, - location.search, - ]); + }, [provider, addressOrName, navigate, direction, searchParams]); const [controller, setController] = useState(); useEffect(() => { @@ -142,28 +139,28 @@ const AddressTransactions: React.FC = () => { }; // Page load from scratch - if (params.direction === "first" || params.direction === undefined) { + if (direction === "first" || direction === undefined) { if (!controller?.isFirst || controller.address !== checksummedAddress) { readFirstPage(); } - } else if (params.direction === "prev") { + } else if (direction === "prev") { if (controller && controller.address === checksummedAddress) { prevPage(); } else { readMiddlePage(false); } - } else if (params.direction === "next") { + } else if (direction === "next") { if (controller && controller.address === checksummedAddress) { nextPage(); } else { readMiddlePage(true); } - } else if (params.direction === "last") { + } else if (direction === "last") { if (!controller?.isLast || controller.address !== checksummedAddress) { readLastPage(); } } - }, [provider, checksummedAddress, params.direction, hash, controller]); + }, [provider, checksummedAddress, direction, hash, controller]); const page = useMemo(() => controller?.getPage(), [controller]); const addrCollector = useMemo(() => pageCollector(page), [page]); @@ -177,8 +174,6 @@ const AddressTransactions: React.FC = () => { }, [page]); const priceMap = useMultipleETHUSDOracle(provider, blockTags); - document.title = `Address ${params.addressOrName} | Otterscan`; - const [feeDisplay, feeDisplayToggler] = useFeeToggler(); const selectionCtx = useSelection(); @@ -212,7 +207,7 @@ const AddressTransactions: React.FC = () => { {error ? ( - "{params.addressOrName}" is not an ETH address or ENS name. + "{addressOrName}" is not an ETH address or ENS name. ) : ( checksummedAddress && ( @@ -231,7 +226,7 @@ const AddressTransactions: React.FC = () => { {isENS && ( - ENS: {params.addressOrName} + ENS: {addressOrName} )} @@ -274,73 +269,81 @@ const AddressTransactions: React.FC = () => { - - - -
-
- {page === undefined ? ( - <>Waiting for search results... - ) : ( - <>{page.length} transactions on this page - )} -
- -
- - {page ? ( - - {page.map((tx) => ( - - ))} -
-
- {page === undefined ? ( - <>Waiting for search results... - ) : ( - <>{page.length} transactions on this page - )} -
- + + +
+
+ {page === undefined ? ( + <>Waiting for search results... + ) : ( + <>{page.length} transactions on this page + )}
- - ) : ( - - )} - - - - - - + +
+ + {page ? ( + + {page.map((tx) => ( + + ))} +
+
+ {page === undefined ? ( + <>Waiting for search results... + ) : ( + <>{page.length} transactions on this page + )} +
+ +
+
+ ) : ( + + )} + + } + /> + + } + /> +
@@ -350,4 +353,4 @@ const AddressTransactions: React.FC = () => { ); }; -export default React.memo(AddressTransactions); +export default AddressTransactions; diff --git a/src/App.tsx b/src/App.tsx index 110d626..4ee2dca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,12 @@ -import React, { Suspense, useMemo, useState } from "react"; -import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; +import React, { Suspense } from "react"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import WarningHeader from "./WarningHeader"; import Home from "./Home"; -import Search from "./Search"; -import Title from "./Title"; +import Main from "./Main"; import ConnectionErrorPanel from "./ConnectionErrorPanel"; import Footer from "./Footer"; import { ConnectionStatus } from "./types"; import { RuntimeContext, useRuntime } from "./useRuntime"; -import { AppConfig, AppConfigContext } from "./useAppConfig"; -import { SourcifySource } from "./url"; const Block = React.lazy( () => import(/* webpackChunkName: "block", webpackPrefetch: true */ "./Block") @@ -39,15 +36,6 @@ const London = React.lazy( const App = () => { const runtime = useRuntime(); - const [sourcifySource, setSourcifySource] = useState( - SourcifySource.IPFS_IPNS - ); - const appConfig = useMemo((): AppConfig => { - return { - sourcifySource, - setSourcifySource, - }; - }, [sourcifySource, setSourcifySource]); return ( @@ -61,34 +49,27 @@ const App = () => {
- - - + + } /> + } /> + }> + } /> + } + /> + } /> + } + /> + } + /> + } /> - - - - - - - - - - <Route path="/block/:blockNumberOrHash" exact> - <Block /> - </Route> - <Route path="/block/:blockNumber/txs" exact> - <BlockTransactions /> - </Route> - <Route path="/tx/:txhash"> - <Transaction /> - </Route> - <Route path="/address/:addressOrName/:direction?"> - <AddressTransactions /> - </Route> - </AppConfigContext.Provider> - </Route> - </Switch> + </Routes> </Router> <Footer /> </div> @@ -98,4 +79,4 @@ const App = () => { ); }; -export default React.memo(App); +export default App; diff --git a/src/Block.tsx b/src/Block.tsx index df16643..473ef75 100644 --- a/src/Block.tsx +++ b/src/Block.tsx @@ -26,15 +26,14 @@ import { blockTxsURL } from "./url"; import { useBlockData } from "./useErigonHooks"; import { useETHUSDOracle } from "./usePriceOracle"; -type BlockParams = { - blockNumberOrHash: string; -}; - const Block: React.FC = () => { const { provider } = useContext(RuntimeContext); - const params = useParams<BlockParams>(); + const { blockNumberOrHash } = useParams(); + if (blockNumberOrHash === undefined) { + throw new Error("blockNumberOrHash couldn't be undefined here"); + } - const block = useBlockData(provider, params.blockNumberOrHash); + const block = useBlockData(provider, blockNumberOrHash); useEffect(() => { if (block) { document.title = `Block #${block.number} | Otterscan`; @@ -63,9 +62,7 @@ const Block: React.FC = () => { <StandardSubtitle> <div className="flex space-x-1 items-baseline"> <span>Block</span> - <span className="text-base text-gray-500"> - #{params.blockNumberOrHash} - </span> + <span className="text-base text-gray-500">#{blockNumberOrHash}</span> {block && ( <NavBlock blockNumber={block.number} @@ -192,4 +189,4 @@ const Block: React.FC = () => { ); }; -export default React.memo(Block); +export default Block; diff --git a/src/BlockTransactions.tsx b/src/BlockTransactions.tsx index ed9b2c7..b1fe5af 100644 --- a/src/BlockTransactions.tsx +++ b/src/BlockTransactions.tsx @@ -1,31 +1,24 @@ import React, { useMemo, useContext } from "react"; -import { useParams, useLocation } from "react-router"; +import { useParams } from "react-router"; import { BigNumber } from "@ethersproject/bignumber"; -import queryString from "query-string"; import StandardFrame from "./StandardFrame"; import BlockTransactionHeader from "./block/BlockTransactionHeader"; import BlockTransactionResults from "./block/BlockTransactionResults"; import { PAGE_SIZE } from "./params"; import { RuntimeContext } from "./useRuntime"; import { useBlockTransactions } from "./useErigonHooks"; - -type BlockParams = { - blockNumber: string; -}; - -type PageParams = { - p?: number; -}; +import { useSearchParams } from "react-router-dom"; const BlockTransactions: React.FC = () => { const { provider } = useContext(RuntimeContext); - const params = useParams<BlockParams>(); - const location = useLocation<PageParams>(); - const qs = queryString.parse(location.search); + const params = useParams(); + + const [searchParams] = useSearchParams(); let pageNumber = 1; - if (qs.p) { + const p = searchParams.get("p"); + if (p) { try { - pageNumber = parseInt(qs.p as string); + pageNumber = parseInt(p); } catch (err) {} } @@ -56,4 +49,4 @@ const BlockTransactions: React.FC = () => { ); }; -export default React.memo(BlockTransactions); +export default BlockTransactions; diff --git a/src/Home.tsx b/src/Home.tsx index 103639a..cdb47c9 100644 --- a/src/Home.tsx +++ b/src/Home.tsx @@ -1,5 +1,5 @@ import React, { useState, useContext } from "react"; -import { NavLink, useHistory } from "react-router-dom"; +import { NavLink, useNavigate } from "react-router-dom"; import { commify } from "@ethersproject/units"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn"; @@ -9,18 +9,20 @@ import Timestamp from "./components/Timestamp"; import { RuntimeContext } from "./useRuntime"; import { useLatestBlock } from "./useLatestBlock"; import { blockURL } from "./url"; +import { search } from "./search/search"; const CameraScanner = React.lazy(() => import("./search/CameraScanner")); const Home: React.FC = () => { const { provider } = useContext(RuntimeContext); - const [search, setSearch] = useState<string>(); + const [searchString, setSearchString] = useState<string>(""); const [canSubmit, setCanSubmit] = useState<boolean>(false); - const history = useHistory(); + const navigate = useNavigate(); const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => { - setCanSubmit(e.target.value.trim().length > 0); - setSearch(e.target.value.trim()); + const searchTerm = e.target.value.trim(); + setCanSubmit(searchTerm.length > 0); + setSearchString(searchTerm); }; const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => { @@ -29,7 +31,7 @@ const Home: React.FC = () => { return; } - history.push(`/search?q=${search}`); + search(searchString, navigate); }; const latestBlock = useLatestBlock(provider); diff --git a/src/Main.tsx b/src/Main.tsx new file mode 100644 index 0000000..4a8cbf4 --- /dev/null +++ b/src/Main.tsx @@ -0,0 +1,26 @@ +import React, { useMemo, useState } from "react"; +import { Outlet } from "react-router-dom"; +import Title from "./Title"; +import { AppConfig, AppConfigContext } from "./useAppConfig"; +import { SourcifySource } from "./url"; + +const Main: React.FC = () => { + const [sourcifySource, setSourcifySource] = useState<SourcifySource>( + SourcifySource.IPFS_IPNS + ); + const appConfig = useMemo((): AppConfig => { + return { + sourcifySource, + setSourcifySource, + }; + }, [sourcifySource, setSourcifySource]); + + return ( + <AppConfigContext.Provider value={appConfig}> + <Title /> + <Outlet /> + </AppConfigContext.Provider> + ); +}; + +export default Main; diff --git a/src/Search.tsx b/src/Search.tsx deleted file mode 100644 index 78dc724..0000000 --- a/src/Search.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useLocation, useHistory } from "react-router-dom"; -import { isAddress } from "@ethersproject/address"; -import { isHexString } from "@ethersproject/bytes"; -import queryString from "query-string"; - -type SearchParams = { - q: string; -}; - -const Search: React.FC = () => { - const location = useLocation<SearchParams>(); - const history = useHistory(); - - const qs = queryString.parse(location.search); - const q = (qs.q ?? "").toString(); - if (isAddress(q)) { - history.replace(`/address/${q}`); - return <></>; - } - if (isHexString(q, 32)) { - history.replace(`/tx/${q}`); - return <></>; - } - - const blockNumber = parseInt(q); - if (!isNaN(blockNumber)) { - history.replace(`/block/${blockNumber}`); - return <></>; - } - - // Assume it is an ENS name - history.replace(`/address/${q}`); - return <></>; -}; - -export default Search; diff --git a/src/Title.tsx b/src/Title.tsx index e1e9b8e..f23a92e 100644 --- a/src/Title.tsx +++ b/src/Title.tsx @@ -1,24 +1,26 @@ import React, { useState, useRef, useContext } from "react"; -import { Link, useHistory } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faQrcode } from "@fortawesome/free-solid-svg-icons/faQrcode"; import useKeyboardShortcut from "use-keyboard-shortcut"; import PriceBox from "./PriceBox"; import SourcifyMenu from "./SourcifyMenu"; import { RuntimeContext } from "./useRuntime"; +import { search } from "./search/search"; import Otter from "./otter.jpg"; const CameraScanner = React.lazy(() => import("./search/CameraScanner")); const Title: React.FC = () => { const { provider } = useContext(RuntimeContext); - const [search, setSearch] = useState<string>(); + const [searchString, setSearchString] = useState<string>(""); const [canSubmit, setCanSubmit] = useState<boolean>(false); - const history = useHistory(); + const navigate = useNavigate(); const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => { - setCanSubmit(e.target.value.trim().length > 0); - setSearch(e.target.value.trim()); + const searchTerm = e.target.value.trim(); + setCanSubmit(searchTerm.length > 0); + setSearchString(searchTerm); }; const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => { @@ -27,7 +29,10 @@ const Title: React.FC = () => { return; } - history.push(`/search?q=${search}`); + if (searchRef.current) { + searchRef.current.value = ""; + } + search(searchString, navigate); }; const searchRef = useRef<HTMLInputElement>(null); @@ -92,4 +97,4 @@ const Title: React.FC = () => { ); }; -export default React.memo(Title); +export default Title; diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 168c83f..4b8008c 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useContext } from "react"; -import { Route, Switch, useParams } from "react-router-dom"; +import { useParams, Routes, Route } from "react-router-dom"; import { Tab } from "@headlessui/react"; import StandardFrame from "./StandardFrame"; import StandardSubtitle from "./StandardSubtitle"; @@ -37,14 +37,12 @@ const Trace = React.lazy( ) ); -type TransactionParams = { - txhash: string; -}; - const Transaction: React.FC = () => { const { provider } = useContext(RuntimeContext); - const params = useParams<TransactionParams>(); - const { txhash } = params; + const { txhash } = useParams(); + if (txhash === undefined) { + throw new Error("txhash couldn't be undefined here"); + } const txData = useTxData(provider, txhash); const addrCollector = useMemo( @@ -97,44 +95,53 @@ const Transaction: React.FC = () => { <SelectionContext.Provider value={selectionCtx}> <Tab.Group> <Tab.List className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white"> - <NavTab href={`/tx/${txhash}`}>Overview</NavTab> + <NavTab href=".">Overview</NavTab> {txData.confirmedData?.blockNumber !== undefined && ( - <NavTab href={`/tx/${txhash}/logs`}> + <NavTab href="logs"> Logs {txData && ` (${txData.confirmedData?.logs?.length ?? 0})`} </NavTab> )} - <NavTab href={`/tx/${txhash}/trace`}>Trace</NavTab> + <NavTab href="trace">Trace</NavTab> </Tab.List> </Tab.Group> <React.Suspense fallback={null}> - <Switch> - <Route path="/tx/:txhash/" exact> - <Details - txData={txData} - txDesc={txDesc} - userDoc={metadata?.output.userdoc} - devDoc={metadata?.output.devdoc} - internalOps={internalOps} - sendsEthToMiner={sendsEthToMiner} - ethUSDPrice={blockETHUSDPrice} - resolvedAddresses={resolvedAddresses} - /> - </Route> - <Route path="/tx/:txhash/logs/" exact> - <Logs - txData={txData} - metadata={metadata} - resolvedAddresses={resolvedAddresses} - /> - </Route> - <Route path="/tx/:txhash/trace" exact> - <Trace - txData={txData} - resolvedAddresses={resolvedAddresses} - /> - </Route> - </Switch> + <Routes> + <Route + index + element={ + <Details + txData={txData} + txDesc={txDesc} + userDoc={metadata?.output.userdoc} + devDoc={metadata?.output.devdoc} + internalOps={internalOps} + sendsEthToMiner={sendsEthToMiner} + ethUSDPrice={blockETHUSDPrice} + resolvedAddresses={resolvedAddresses} + /> + } + /> + <Route + path="logs" + element={ + <Logs + txData={txData} + metadata={metadata} + resolvedAddresses={resolvedAddresses} + /> + } + /> + <Route + path="trace" + element={ + <Trace + txData={txData} + resolvedAddresses={resolvedAddresses} + /> + } + /> + </Routes> </React.Suspense> </SelectionContext.Provider> )} @@ -143,4 +150,4 @@ const Transaction: React.FC = () => { ); }; -export default React.memo(Transaction); +export default Transaction; diff --git a/src/components/NavTab.tsx b/src/components/NavTab.tsx index e16d193..6b3c66c 100644 --- a/src/components/NavTab.tsx +++ b/src/components/NavTab.tsx @@ -9,7 +9,7 @@ type NavTabProps = { const NavTab: React.FC<NavTabProps> = ({ href, children }) => ( <Tab as={Fragment}> <NavLink - className={(isActive) => + className={({ isActive }) => `${ isActive ? "text-link-blue border-link-blue" @@ -17,7 +17,7 @@ const NavTab: React.FC<NavTabProps> = ({ href, children }) => ( } hover:text-link-blue text-sm font-bold px-3 py-3 border-b-2` } to={href} - exact + end replace > {children} diff --git a/src/search/CameraScanner.tsx b/src/search/CameraScanner.tsx index b32d707..5766814 100644 --- a/src/search/CameraScanner.tsx +++ b/src/search/CameraScanner.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { isAddress } from "@ethersproject/address"; import { QrReader } from "@blackbox-vision/react-qr-reader"; import { OnResultFunction } from "@blackbox-vision/react-qr-reader/dist-types/types"; @@ -11,7 +11,7 @@ type CameraScannerProps = { }; const CameraScanner: React.FC<CameraScannerProps> = ({ turnOffScan }) => { - const history = useHistory(); + const navigate = useNavigate(); const evaluateScan: OnResultFunction = (result, error, codeReader) => { console.log("scan"); @@ -23,7 +23,7 @@ const CameraScanner: React.FC<CameraScannerProps> = ({ turnOffScan }) => { return; } - history.push(`/search?q=${text}`); + navigate(`/search?q=${text}`); turnOffScan(); } }; diff --git a/src/search/search.ts b/src/search/search.ts index 26af5e2..0453a44 100644 --- a/src/search/search.ts +++ b/src/search/search.ts @@ -1,4 +1,7 @@ import { JsonRpcProvider, TransactionResponse } from "@ethersproject/providers"; +import { isAddress } from "@ethersproject/address"; +import { isHexString } from "@ethersproject/bytes"; +import { NavigateFunction } from "react-router"; import { PAGE_SIZE } from "../params"; import { ProcessedTransaction, TransactionChunk } from "../types"; @@ -194,3 +197,24 @@ export class SearchController { return this; } } + +export const search = (q: string, navigate: NavigateFunction) => { + if (isAddress(q)) { + navigate(`/address/${q}`, { replace: true }); + return; + } + + if (isHexString(q, 32)) { + navigate(`/tx/${q}`, { replace: true }); + return; + } + + const blockNumber = parseInt(q); + if (!isNaN(blockNumber)) { + navigate(`/block/${blockNumber}`, { replace: true }); + return; + } + + // Assume it is an ENS name + navigate(`/address/${q}`); +}; From 0b43df6b7581cb13883ca84ed261c09919f65098 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 25 Nov 2021 06:44:25 -0300 Subject: [PATCH 08/45] Extract common search logic --- src/{Title.tsx => Header.tsx} | 39 +++++------------------------ src/Home.tsx | 24 +++--------------- src/Main.tsx | 4 +-- src/search/search.ts | 47 +++++++++++++++++++++++++++++++++-- 4 files changed, 57 insertions(+), 57 deletions(-) rename src/{Title.tsx => Header.tsx} (70%) diff --git a/src/Title.tsx b/src/Header.tsx similarity index 70% rename from src/Title.tsx rename to src/Header.tsx index f23a92e..251e584 100644 --- a/src/Title.tsx +++ b/src/Header.tsx @@ -1,45 +1,18 @@ -import React, { useState, useRef, useContext } from "react"; -import { Link, useNavigate } from "react-router-dom"; +import React, { useState, useContext } from "react"; +import { Link } from "react-router-dom"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faQrcode } from "@fortawesome/free-solid-svg-icons/faQrcode"; -import useKeyboardShortcut from "use-keyboard-shortcut"; import PriceBox from "./PriceBox"; import SourcifyMenu from "./SourcifyMenu"; import { RuntimeContext } from "./useRuntime"; -import { search } from "./search/search"; +import { useGenericSearch } from "./search/search"; import Otter from "./otter.jpg"; const CameraScanner = React.lazy(() => import("./search/CameraScanner")); -const Title: React.FC = () => { +const Header: React.FC = () => { const { provider } = useContext(RuntimeContext); - const [searchString, setSearchString] = useState<string>(""); - const [canSubmit, setCanSubmit] = useState<boolean>(false); - const navigate = useNavigate(); - - const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => { - const searchTerm = e.target.value.trim(); - setCanSubmit(searchTerm.length > 0); - setSearchString(searchTerm); - }; - - const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => { - e.preventDefault(); - if (!canSubmit) { - return; - } - - if (searchRef.current) { - searchRef.current.value = ""; - } - search(searchString, navigate); - }; - - const searchRef = useRef<HTMLInputElement>(null); - useKeyboardShortcut(["/"], () => { - searchRef.current?.focus(); - }); - + const [searchRef, handleChange, handleSubmit] = useGenericSearch(); const [isScanning, setScanning] = useState<boolean>(false); return ( @@ -97,4 +70,4 @@ const Title: React.FC = () => { ); }; -export default Title; +export default Header; diff --git a/src/Home.tsx b/src/Home.tsx index cdb47c9..d1d8b49 100644 --- a/src/Home.tsx +++ b/src/Home.tsx @@ -1,5 +1,5 @@ import React, { useState, useContext } from "react"; -import { NavLink, useNavigate } from "react-router-dom"; +import { NavLink } from "react-router-dom"; import { commify } from "@ethersproject/units"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn"; @@ -9,30 +9,13 @@ import Timestamp from "./components/Timestamp"; import { RuntimeContext } from "./useRuntime"; import { useLatestBlock } from "./useLatestBlock"; import { blockURL } from "./url"; -import { search } from "./search/search"; +import { useGenericSearch } from "./search/search"; const CameraScanner = React.lazy(() => import("./search/CameraScanner")); const Home: React.FC = () => { const { provider } = useContext(RuntimeContext); - const [searchString, setSearchString] = useState<string>(""); - const [canSubmit, setCanSubmit] = useState<boolean>(false); - const navigate = useNavigate(); - - const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => { - const searchTerm = e.target.value.trim(); - setCanSubmit(searchTerm.length > 0); - setSearchString(searchTerm); - }; - - const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => { - e.preventDefault(); - if (!canSubmit) { - return; - } - - search(searchString, navigate); - }; + const [searchRef, handleChange, handleSubmit] = useGenericSearch(); const latestBlock = useLatestBlock(provider); const [isScanning, setScanning] = useState<boolean>(false); @@ -58,6 +41,7 @@ const Home: React.FC = () => { size={50} placeholder="Search by address / txn hash / block number / ENS name" onChange={handleChange} + ref={searchRef} autoFocus /> <button diff --git a/src/Main.tsx b/src/Main.tsx index 4a8cbf4..1fe30e5 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from "react"; import { Outlet } from "react-router-dom"; -import Title from "./Title"; +import Header from "./Header"; import { AppConfig, AppConfigContext } from "./useAppConfig"; import { SourcifySource } from "./url"; @@ -17,7 +17,7 @@ const Main: React.FC = () => { return ( <AppConfigContext.Provider value={appConfig}> - <Title /> + <Header /> <Outlet /> </AppConfigContext.Provider> ); diff --git a/src/search/search.ts b/src/search/search.ts index 0453a44..25bda65 100644 --- a/src/search/search.ts +++ b/src/search/search.ts @@ -1,7 +1,15 @@ +import { + ChangeEventHandler, + FormEventHandler, + RefObject, + useRef, + useState, +} from "react"; +import { NavigateFunction, useNavigate } from "react-router"; import { JsonRpcProvider, TransactionResponse } from "@ethersproject/providers"; import { isAddress } from "@ethersproject/address"; import { isHexString } from "@ethersproject/bytes"; -import { NavigateFunction } from "react-router"; +import useKeyboardShortcut from "use-keyboard-shortcut"; import { PAGE_SIZE } from "../params"; import { ProcessedTransaction, TransactionChunk } from "../types"; @@ -198,7 +206,7 @@ export class SearchController { } } -export const search = (q: string, navigate: NavigateFunction) => { +const doSearch = (q: string, navigate: NavigateFunction) => { if (isAddress(q)) { navigate(`/address/${q}`, { replace: true }); return; @@ -218,3 +226,38 @@ export const search = (q: string, navigate: NavigateFunction) => { // Assume it is an ENS name navigate(`/address/${q}`); }; + +export const useGenericSearch = (): [ + RefObject<HTMLInputElement>, + ChangeEventHandler<HTMLInputElement>, + FormEventHandler<HTMLFormElement> +] => { + const [searchString, setSearchString] = useState<string>(""); + const [canSubmit, setCanSubmit] = useState<boolean>(false); + const navigate = useNavigate(); + + const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => { + const searchTerm = e.target.value.trim(); + setCanSubmit(searchTerm.length > 0); + setSearchString(searchTerm); + }; + + const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => { + e.preventDefault(); + if (!canSubmit) { + return; + } + + if (searchRef.current) { + searchRef.current.value = ""; + } + doSearch(searchString, navigate); + }; + + const searchRef = useRef<HTMLInputElement>(null); + useKeyboardShortcut(["/"], () => { + searchRef.current?.focus(); + }); + + return [searchRef, handleChange, handleSubmit]; +}; From 97611ca0a127f0b6326f7c7d5bf14e806484886d Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 25 Nov 2021 07:03:29 -0300 Subject: [PATCH 09/45] Extract address transaction results component --- src/AddressTransactions.tsx | 77 +++-------------- src/address/AddressTransactionResults.tsx | 100 ++++++++++++++++++++++ 2 files changed, 111 insertions(+), 66 deletions(-) create mode 100644 src/address/AddressTransactionResults.tsx diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index 52432fd..6e014f8 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -16,18 +16,13 @@ import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons/faQuestion import StandardFrame from "./StandardFrame"; import StandardSubtitle from "./StandardSubtitle"; import Copy from "./components/Copy"; -import ContentFrame from "./ContentFrame"; import NavTab from "./components/NavTab"; +import AddressTransactionResults from "./address/AddressTransactionResults"; import Contracts from "./address/Contracts"; -import UndefinedPageControl from "./search/UndefinedPageControl"; -import ResultHeader from "./search/ResultHeader"; -import PendingResults from "./search/PendingResults"; -import TransactionItem from "./search/TransactionItem"; import { SearchController } from "./search/search"; import { RuntimeContext } from "./useRuntime"; import { pageCollector, useResolvedAddresses } from "./useResolvedAddresses"; import { useFeeToggler } from "./search/useFeeToggler"; -import { SelectionContext, useSelection } from "./useSelection"; import { useMultipleETHUSDOracle } from "./usePriceOracle"; import { useAppConfigContext } from "./useAppConfig"; import { useMultipleMetadata } from "./useSourcify"; @@ -176,7 +171,6 @@ const AddressTransactions: React.FC = () => { const [feeDisplay, feeDisplayToggler] = useFeeToggler(); - const selectionCtx = useSelection(); const addresses = useMemo(() => { const _addresses: ChecksummedAddress[] = []; if (checksummedAddress) { @@ -273,65 +267,16 @@ const AddressTransactions: React.FC = () => { <Route path="*" element={ - <ContentFrame tabs> - <div className="flex justify-between items-baseline py-3"> - <div className="text-sm text-gray-500"> - {page === undefined ? ( - <>Waiting for search results...</> - ) : ( - <>{page.length} transactions on this page</> - )} - </div> - <UndefinedPageControl - address={checksummedAddress} - isFirst={controller?.isFirst} - isLast={controller?.isLast} - prevHash={page ? page[0].hash : ""} - nextHash={page ? page[page.length - 1].hash : ""} - disabled={controller === undefined} - /> - </div> - <ResultHeader - feeDisplay={feeDisplay} - feeDisplayToggler={feeDisplayToggler} - /> - {page ? ( - <SelectionContext.Provider value={selectionCtx}> - {page.map((tx) => ( - <TransactionItem - key={tx.hash} - tx={tx} - resolvedAddresses={resolvedAddresses} - selectedAddress={checksummedAddress} - feeDisplay={feeDisplay} - priceMap={priceMap} - metadatas={metadatas} - /> - ))} - <div className="flex justify-between items-baseline py-3"> - <div className="text-sm text-gray-500"> - {page === undefined ? ( - <>Waiting for search results...</> - ) : ( - <>{page.length} transactions on this page</> - )} - </div> - <UndefinedPageControl - address={checksummedAddress} - isFirst={controller?.isFirst} - isLast={controller?.isLast} - prevHash={page ? page[0].hash : ""} - nextHash={ - page ? page[page.length - 1].hash : "" - } - disabled={controller === undefined} - /> - </div> - </SelectionContext.Provider> - ) : ( - <PendingResults /> - )} - </ContentFrame> + <AddressTransactionResults + page={page} + checksummedAddress={checksummedAddress} + controller={controller} + feeDisplay={feeDisplay} + feeDisplayToggler={feeDisplayToggler} + resolvedAddresses={resolvedAddresses} + priceMap={priceMap} + metadatas={metadatas} + /> } /> <Route diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx new file mode 100644 index 0000000..eadd3d3 --- /dev/null +++ b/src/address/AddressTransactionResults.tsx @@ -0,0 +1,100 @@ +import React from "react"; +import { BlockTag } from "@ethersproject/providers"; +import { BigNumber } from "@ethersproject/bignumber"; +import { ResolvedAddresses } from "../api/address-resolver"; +import ContentFrame from "../ContentFrame"; +import PendingResults from "../search/PendingResults"; +import ResultHeader from "../search/ResultHeader"; +import { SearchController } from "../search/search"; +import TransactionItem from "../search/TransactionItem"; +import UndefinedPageControl from "../search/UndefinedPageControl"; +import { FeeDisplay } from "../search/useFeeToggler"; +import { ProcessedTransaction } from "../types"; +import { SelectionContext, useSelection } from "../useSelection"; +import { Metadata } from "../useSourcify"; + +type AddressTransactionResultsProps = { + page: ProcessedTransaction[] | undefined; + checksummedAddress: string; + controller: SearchController | undefined; + feeDisplay: FeeDisplay; + feeDisplayToggler: () => void; + resolvedAddresses: ResolvedAddresses | undefined; + priceMap: Record<BlockTag, BigNumber>; + metadatas: Record<string, Metadata | null | undefined>; +}; + +const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ + page, + checksummedAddress, + controller, + feeDisplay, + feeDisplayToggler, + resolvedAddresses, + priceMap, + metadatas, +}) => { + const selectionCtx = useSelection(); + + return ( + <ContentFrame tabs> + <div className="flex justify-between items-baseline py-3"> + <div className="text-sm text-gray-500"> + {page === undefined ? ( + <>Waiting for search results...</> + ) : ( + <>{page.length} transactions on this page</> + )} + </div> + <UndefinedPageControl + address={checksummedAddress} + isFirst={controller?.isFirst} + isLast={controller?.isLast} + prevHash={page ? page[0].hash : ""} + nextHash={page ? page[page.length - 1].hash : ""} + disabled={controller === undefined} + /> + </div> + <ResultHeader + feeDisplay={feeDisplay} + feeDisplayToggler={feeDisplayToggler} + /> + {page ? ( + <SelectionContext.Provider value={selectionCtx}> + {page.map((tx) => ( + <TransactionItem + key={tx.hash} + tx={tx} + resolvedAddresses={resolvedAddresses} + selectedAddress={checksummedAddress} + feeDisplay={feeDisplay} + priceMap={priceMap} + metadatas={metadatas} + /> + ))} + <div className="flex justify-between items-baseline py-3"> + <div className="text-sm text-gray-500"> + {page === undefined ? ( + <>Waiting for search results...</> + ) : ( + <>{page.length} transactions on this page</> + )} + </div> + <UndefinedPageControl + address={checksummedAddress} + isFirst={controller?.isFirst} + isLast={controller?.isLast} + prevHash={page ? page[0].hash : ""} + nextHash={page ? page[page.length - 1].hash : ""} + disabled={controller === undefined} + /> + </div> + </SelectionContext.Provider> + ) : ( + <PendingResults /> + )} + </ContentFrame> + ); +}; + +export default AddressTransactionResults; From 843781c8ba46508113ff9fe46b16b4d4bcccd4a5 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 25 Nov 2021 15:03:00 -0300 Subject: [PATCH 10/45] Push down address search-specific computations --- src/AddressTransactions.tsx | 115 +----------------- src/address/AddressTransactionResults.tsx | 141 ++++++++++++++++++---- src/block/BlockTransactionResults.tsx | 2 +- 3 files changed, 122 insertions(+), 136 deletions(-) diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index 6e014f8..0328c8a 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -6,7 +6,6 @@ import { Route, useSearchParams, } from "react-router-dom"; -import { BlockTag } from "@ethersproject/abstract-provider"; import { getAddress, isAddress } from "@ethersproject/address"; import { Tab } from "@headlessui/react"; import Blockies from "react-blockies"; @@ -19,14 +18,9 @@ import Copy from "./components/Copy"; import NavTab from "./components/NavTab"; import AddressTransactionResults from "./address/AddressTransactionResults"; import Contracts from "./address/Contracts"; -import { SearchController } from "./search/search"; import { RuntimeContext } from "./useRuntime"; -import { pageCollector, useResolvedAddresses } from "./useResolvedAddresses"; -import { useFeeToggler } from "./search/useFeeToggler"; -import { useMultipleETHUSDOracle } from "./usePriceOracle"; import { useAppConfigContext } from "./useAppConfig"; import { useMultipleMetadata } from "./useSourcify"; -import { ChecksummedAddress } from "./types"; import SourcifyLogo from "./sourcify.svg"; const AddressTransactions: React.FC = () => { @@ -37,13 +31,7 @@ const AddressTransactions: React.FC = () => { } const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - const h = searchParams.get("h"); - let hash: string | undefined; - if (h) { - hash = h; - } const [checksummedAddress, setChecksummedAddress] = useState< string | undefined @@ -95,96 +83,10 @@ const AddressTransactions: React.FC = () => { resolveName(); }, [provider, addressOrName, navigate, direction, searchParams]); - const [controller, setController] = useState<SearchController>(); - useEffect(() => { - if (!provider || !checksummedAddress) { - return; - } - - const readFirstPage = async () => { - const _controller = await SearchController.firstPage( - provider, - checksummedAddress - ); - setController(_controller); - }; - const readMiddlePage = async (next: boolean) => { - const _controller = await SearchController.middlePage( - provider, - checksummedAddress, - hash!, - next - ); - setController(_controller); - }; - const readLastPage = async () => { - const _controller = await SearchController.lastPage( - provider, - checksummedAddress - ); - setController(_controller); - }; - const prevPage = async () => { - const _controller = await controller!.prevPage(provider, hash!); - setController(_controller); - }; - const nextPage = async () => { - const _controller = await controller!.nextPage(provider, hash!); - setController(_controller); - }; - - // Page load from scratch - if (direction === "first" || direction === undefined) { - if (!controller?.isFirst || controller.address !== checksummedAddress) { - readFirstPage(); - } - } else if (direction === "prev") { - if (controller && controller.address === checksummedAddress) { - prevPage(); - } else { - readMiddlePage(false); - } - } else if (direction === "next") { - if (controller && controller.address === checksummedAddress) { - nextPage(); - } else { - readMiddlePage(true); - } - } else if (direction === "last") { - if (!controller?.isLast || controller.address !== checksummedAddress) { - readLastPage(); - } - } - }, [provider, checksummedAddress, direction, hash, controller]); - - const page = useMemo(() => controller?.getPage(), [controller]); - const addrCollector = useMemo(() => pageCollector(page), [page]); - const resolvedAddresses = useResolvedAddresses(provider, addrCollector); - - const blockTags: BlockTag[] = useMemo(() => { - if (!page) { - return []; - } - return page.map((p) => p.blockNumber); - }, [page]); - const priceMap = useMultipleETHUSDOracle(provider, blockTags); - - const [feeDisplay, feeDisplayToggler] = useFeeToggler(); - - const addresses = useMemo(() => { - const _addresses: ChecksummedAddress[] = []; - if (checksummedAddress) { - _addresses.push(checksummedAddress); - } - if (page) { - for (const t of page) { - if (t.to) { - _addresses.push(t.to); - } - } - } - return _addresses; - }, [checksummedAddress, page]); + const addresses = useMemo( + () => (checksummedAddress ? [checksummedAddress] : []), + [checksummedAddress] + ); const { sourcifySource } = useAppConfigContext(); const metadatas = useMultipleMetadata( undefined, @@ -268,14 +170,7 @@ const AddressTransactions: React.FC = () => { path="*" element={ <AddressTransactionResults - page={page} - checksummedAddress={checksummedAddress} - controller={controller} - feeDisplay={feeDisplay} - feeDisplayToggler={feeDisplayToggler} - resolvedAddresses={resolvedAddresses} - priceMap={priceMap} - metadatas={metadatas} + address={checksummedAddress} /> } /> diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx index eadd3d3..3a8528d 100644 --- a/src/address/AddressTransactionResults.tsx +++ b/src/address/AddressTransactionResults.tsx @@ -1,40 +1,131 @@ -import React from "react"; +import React, { useContext, useEffect, useMemo, useState } from "react"; import { BlockTag } from "@ethersproject/providers"; -import { BigNumber } from "@ethersproject/bignumber"; -import { ResolvedAddresses } from "../api/address-resolver"; import ContentFrame from "../ContentFrame"; import PendingResults from "../search/PendingResults"; import ResultHeader from "../search/ResultHeader"; import { SearchController } from "../search/search"; import TransactionItem from "../search/TransactionItem"; import UndefinedPageControl from "../search/UndefinedPageControl"; -import { FeeDisplay } from "../search/useFeeToggler"; -import { ProcessedTransaction } from "../types"; +import { useFeeToggler } from "../search/useFeeToggler"; import { SelectionContext, useSelection } from "../useSelection"; -import { Metadata } from "../useSourcify"; +import { useMultipleMetadata } from "../useSourcify"; +import { useMultipleETHUSDOracle } from "../usePriceOracle"; +import { RuntimeContext } from "../useRuntime"; +import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses"; +import { useAppConfigContext } from "../useAppConfig"; +import { useParams, useSearchParams } from "react-router-dom"; +import { ChecksummedAddress } from "../types"; type AddressTransactionResultsProps = { - page: ProcessedTransaction[] | undefined; - checksummedAddress: string; - controller: SearchController | undefined; - feeDisplay: FeeDisplay; - feeDisplayToggler: () => void; - resolvedAddresses: ResolvedAddresses | undefined; - priceMap: Record<BlockTag, BigNumber>; - metadatas: Record<string, Metadata | null | undefined>; + address: ChecksummedAddress; }; const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ - page, - checksummedAddress, - controller, - feeDisplay, - feeDisplayToggler, - resolvedAddresses, - priceMap, - metadatas, + address, }) => { + const { provider } = useContext(RuntimeContext); const selectionCtx = useSelection(); + const [feeDisplay, feeDisplayToggler] = useFeeToggler(); + + const { addressOrName, direction } = useParams(); + if (addressOrName === undefined) { + throw new Error("addressOrName couldn't be undefined here"); + } + + const [searchParams] = useSearchParams(); + const hash = searchParams.get("h"); + + const [controller, setController] = useState<SearchController>(); + useEffect(() => { + if (!provider || !address) { + return; + } + + const readFirstPage = async () => { + const _controller = await SearchController.firstPage(provider, address); + setController(_controller); + }; + const readMiddlePage = async (next: boolean) => { + const _controller = await SearchController.middlePage( + provider, + address, + hash!, + next + ); + setController(_controller); + }; + const readLastPage = async () => { + const _controller = await SearchController.lastPage(provider, address); + setController(_controller); + }; + const prevPage = async () => { + const _controller = await controller!.prevPage(provider, hash!); + setController(_controller); + }; + const nextPage = async () => { + const _controller = await controller!.nextPage(provider, hash!); + setController(_controller); + }; + + // Page load from scratch + if (direction === "first" || direction === undefined) { + if (!controller?.isFirst || controller.address !== address) { + readFirstPage(); + } + } else if (direction === "prev") { + if (controller && controller.address === address) { + prevPage(); + } else { + readMiddlePage(false); + } + } else if (direction === "next") { + if (controller && controller.address === address) { + nextPage(); + } else { + readMiddlePage(true); + } + } else if (direction === "last") { + if (!controller?.isLast || controller.address !== address) { + readLastPage(); + } + } + }, [provider, address, direction, hash, controller]); + + const page = useMemo(() => controller?.getPage(), [controller]); + + // Extract block number from all txs on current page + // TODO: dedup blockTags + const blockTags: BlockTag[] = useMemo(() => { + if (!page) { + return []; + } + return page.map((t) => t.blockNumber); + }, [page]); + const priceMap = useMultipleETHUSDOracle(provider, blockTags); + + // Resolve all addresses that appear on this page results + const addrCollector = useMemo(() => pageCollector(page), [page]); + const resolvedAddresses = useResolvedAddresses(provider, addrCollector); + + // Calculate Sourcify metadata for all addresses that appear on this page results + const addresses = useMemo(() => { + const _addresses = [address]; + if (page) { + for (const t of page) { + if (t.to) { + _addresses.push(t.to); + } + } + } + return _addresses; + }, [address, page]); + const { sourcifySource } = useAppConfigContext(); + const metadatas = useMultipleMetadata( + undefined, + addresses, + provider?.network.chainId, + sourcifySource + ); return ( <ContentFrame tabs> @@ -47,7 +138,7 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ )} </div> <UndefinedPageControl - address={checksummedAddress} + address={address} isFirst={controller?.isFirst} isLast={controller?.isLast} prevHash={page ? page[0].hash : ""} @@ -66,7 +157,7 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ key={tx.hash} tx={tx} resolvedAddresses={resolvedAddresses} - selectedAddress={checksummedAddress} + selectedAddress={address} feeDisplay={feeDisplay} priceMap={priceMap} metadatas={metadatas} @@ -81,7 +172,7 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ )} </div> <UndefinedPageControl - address={checksummedAddress} + address={address} isFirst={controller?.isFirst} isLast={controller?.isLast} prevHash={page ? page[0].hash : ""} diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index 969e4a4..aa8e688 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -28,9 +28,9 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({ total, pageNumber, }) => { + const { provider } = useContext(RuntimeContext); const selectionCtx = useSelection(); const [feeDisplay, feeDisplayToggler] = useFeeToggler(); - const { provider } = useContext(RuntimeContext); const addrCollector = useMemo(() => pageCollector(page), [page]); const resolvedAddresses = useResolvedAddresses(provider, addrCollector); const blockTags = useMemo(() => [blockTag], [blockTag]); From 379ffda83ed0352d28e4e570752e1ce7dcd3aa13 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 25 Nov 2021 15:41:40 -0300 Subject: [PATCH 11/45] Extract single Sourcify metadata helper function --- src/AddressTransactions.tsx | 23 ++++++----------------- src/useSourcify.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index 0328c8a..ea2baa3 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo, useContext } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { useParams, useNavigate, @@ -20,7 +20,7 @@ import AddressTransactionResults from "./address/AddressTransactionResults"; import Contracts from "./address/Contracts"; import { RuntimeContext } from "./useRuntime"; import { useAppConfigContext } from "./useAppConfig"; -import { useMultipleMetadata } from "./useSourcify"; +import { useSingleMetadata } from "./useSourcify"; import SourcifyLogo from "./sourcify.svg"; const AddressTransactions: React.FC = () => { @@ -83,21 +83,12 @@ const AddressTransactions: React.FC = () => { resolveName(); }, [provider, addressOrName, navigate, direction, searchParams]); - const addresses = useMemo( - () => (checksummedAddress ? [checksummedAddress] : []), - [checksummedAddress] - ); const { sourcifySource } = useAppConfigContext(); - const metadatas = useMultipleMetadata( - undefined, - addresses, + const addressMetadata = useSingleMetadata( + checksummedAddress, provider?.network.chainId, sourcifySource ); - const addressMetadata = - checksummedAddress !== undefined - ? metadatas[checksummedAddress] - : undefined; return ( <StandardFrame> @@ -151,7 +142,7 @@ const AddressTransactions: React.FC = () => { <FontAwesomeIcon icon={faQuestionCircle} /> </span> ) : ( - <span className="self-center text-green-500"> + <span className="self-center"> <img src={SourcifyLogo} alt="Sourcify logo" @@ -169,9 +160,7 @@ const AddressTransactions: React.FC = () => { <Route path="*" element={ - <AddressTransactionResults - address={checksummedAddress} - /> + <AddressTransactionResults address={checksummedAddress} /> } /> <Route diff --git a/src/useSourcify.ts b/src/useSourcify.ts index 10f0c68..d7878db 100644 --- a/src/useSourcify.ts +++ b/src/useSourcify.ts @@ -121,6 +121,16 @@ export const useSourcify = ( return rawMetadata; }; +export const useSingleMetadata = ( + address: ChecksummedAddress | undefined, + chainId: number | undefined, + source: SourcifySource +) => { + const addresses = useMemo(() => (address ? [address] : []), [address]); + const metadatas = useMultipleMetadata(undefined, addresses, chainId, source); + return address !== undefined ? metadatas[address] : undefined; +}; + export const useMultipleMetadata = ( baseMetadatas: Record<string, Metadata | null> | undefined, addresses: (ChecksummedAddress | undefined)[], From 79461886334b29dcb944df00b0a9b60318739334 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 25 Nov 2021 15:48:17 -0300 Subject: [PATCH 12/45] Extract Sourcify logo component --- src/AddressTransactions.tsx | 10 ++-------- src/components/DecoratedAddressLink.tsx | 10 ++-------- src/sourcify/SourcifyLogo.tsx | 14 ++++++++++++++ src/{ => sourcify}/sourcify.svg | 0 4 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 src/sourcify/SourcifyLogo.tsx rename src/{ => sourcify}/sourcify.svg (100%) diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index ea2baa3..1850479 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -16,12 +16,12 @@ import StandardFrame from "./StandardFrame"; import StandardSubtitle from "./StandardSubtitle"; import Copy from "./components/Copy"; import NavTab from "./components/NavTab"; +import SourcifyLogo from "./sourcify/SourcifyLogo"; import AddressTransactionResults from "./address/AddressTransactionResults"; import Contracts from "./address/Contracts"; import { RuntimeContext } from "./useRuntime"; import { useAppConfigContext } from "./useAppConfig"; import { useSingleMetadata } from "./useSourcify"; -import SourcifyLogo from "./sourcify.svg"; const AddressTransactions: React.FC = () => { const { provider } = useContext(RuntimeContext); @@ -143,13 +143,7 @@ const AddressTransactions: React.FC = () => { </span> ) : ( <span className="self-center"> - <img - src={SourcifyLogo} - alt="Sourcify logo" - title="Verified by Sourcify" - width={16} - height={16} - /> + <SourcifyLogo /> </span> )} </span> diff --git a/src/components/DecoratedAddressLink.tsx b/src/components/DecoratedAddressLink.tsx index 333e1ff..c92ed98 100644 --- a/src/components/DecoratedAddressLink.tsx +++ b/src/components/DecoratedAddressLink.tsx @@ -7,10 +7,10 @@ import { faMoneyBillAlt } from "@fortawesome/free-solid-svg-icons/faMoneyBillAlt import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn"; import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins"; import AddressOrENSName from "./AddressOrENSName"; +import SourcifyLogo from "../sourcify/SourcifyLogo"; import { AddressContext, ZERO_ADDRESS } from "../types"; import { ResolvedAddresses } from "../api/address-resolver"; import { Metadata } from "../useSourcify"; -import SourcifyLogo from "../sourcify.svg"; type DecoratedAddressLinkProps = { address: string; @@ -80,13 +80,7 @@ const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({ className="self-center flex-shrink-0 flex items-center" to={`/address/${address}/contract`} > - <img - src={SourcifyLogo} - alt="Sourcify logo" - title="Verified by Sourcify" - width={16} - height={16} - /> + <SourcifyLogo /> </NavLink> )} <AddressOrENSName diff --git a/src/sourcify/SourcifyLogo.tsx b/src/sourcify/SourcifyLogo.tsx new file mode 100644 index 0000000..0324e76 --- /dev/null +++ b/src/sourcify/SourcifyLogo.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import SourcifyIcon from "./sourcify.svg"; + +const SourcifyLogo: React.FC = () => ( + <img + src={SourcifyIcon} + alt="Sourcify logo" + title="Verified by Sourcify" + width={16} + height={16} + /> +); + +export default SourcifyLogo; diff --git a/src/sourcify.svg b/src/sourcify/sourcify.svg similarity index 100% rename from src/sourcify.svg rename to src/sourcify/sourcify.svg From d9592fe1962ded39ac8eaa0da9e81ddbe293f4b1 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 25 Nov 2021 15:50:59 -0300 Subject: [PATCH 13/45] Move file --- src/AddressTransactions.tsx | 2 +- src/TokenTransferItem.tsx | 2 +- src/Transaction.tsx | 2 +- src/address/AddressTransactionResults.tsx | 2 +- src/address/Contract.tsx | 2 +- src/address/Contracts.tsx | 2 +- src/block/BlockTransactionResults.tsx | 2 +- src/components/DecoratedAddressLink.tsx | 2 +- src/components/TransactionAddress.tsx | 2 +- src/search/TransactionItem.tsx | 2 +- src/{ => sourcify}/useSourcify.ts | 4 ++-- src/transaction/Details.tsx | 2 +- src/transaction/LogEntry.tsx | 2 +- src/transaction/Logs.tsx | 2 +- src/transaction/decoder/DecodedParamsTable.tsx | 2 +- src/transaction/decoder/InputDecoder.tsx | 2 +- 16 files changed, 17 insertions(+), 17 deletions(-) rename src/{ => sourcify}/useSourcify.ts (98%) diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index 1850479..a4eb24f 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -21,7 +21,7 @@ import AddressTransactionResults from "./address/AddressTransactionResults"; import Contracts from "./address/Contracts"; import { RuntimeContext } from "./useRuntime"; import { useAppConfigContext } from "./useAppConfig"; -import { useSingleMetadata } from "./useSourcify"; +import { useSingleMetadata } from "./sourcify/useSourcify"; const AddressTransactions: React.FC = () => { const { provider } = useContext(RuntimeContext); diff --git a/src/TokenTransferItem.tsx b/src/TokenTransferItem.tsx index c56bc26..59fffbb 100644 --- a/src/TokenTransferItem.tsx +++ b/src/TokenTransferItem.tsx @@ -11,7 +11,7 @@ import { TokenTransfer, } from "./types"; import { ResolvedAddresses } from "./api/address-resolver"; -import { Metadata } from "./useSourcify"; +import { Metadata } from "./sourcify/useSourcify"; type TokenTransferItemProps = { t: TokenTransfer; diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 4b8008c..9f9fbc6 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -10,7 +10,7 @@ import { SelectionContext, useSelection } from "./useSelection"; import { useInternalOperations, useTxData } from "./useErigonHooks"; import { useETHUSDOracle } from "./usePriceOracle"; import { useAppConfigContext } from "./useAppConfig"; -import { useSourcify, useTransactionDescription } from "./useSourcify"; +import { useSourcify, useTransactionDescription } from "./sourcify/useSourcify"; import { transactionDataCollector, useResolvedAddresses, diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx index 3a8528d..b13bbd2 100644 --- a/src/address/AddressTransactionResults.tsx +++ b/src/address/AddressTransactionResults.tsx @@ -8,7 +8,7 @@ import TransactionItem from "../search/TransactionItem"; import UndefinedPageControl from "../search/UndefinedPageControl"; import { useFeeToggler } from "../search/useFeeToggler"; import { SelectionContext, useSelection } from "../useSelection"; -import { useMultipleMetadata } from "../useSourcify"; +import { useMultipleMetadata } from "../sourcify/useSourcify"; import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { RuntimeContext } from "../useRuntime"; import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses"; diff --git a/src/address/Contract.tsx b/src/address/Contract.tsx index 5432f51..360e8bc 100644 --- a/src/address/Contract.tsx +++ b/src/address/Contract.tsx @@ -1,6 +1,6 @@ import React from "react"; import { SyntaxHighlighter, docco } from "../highlight-init"; -import { useContract } from "../useSourcify"; +import { useContract } from "../sourcify/useSourcify"; import { useAppConfigContext } from "../useAppConfig"; type ContractProps = { diff --git a/src/address/Contracts.tsx b/src/address/Contracts.tsx index dbaaf83..c537063 100644 --- a/src/address/Contracts.tsx +++ b/src/address/Contracts.tsx @@ -7,7 +7,7 @@ import ContentFrame from "../ContentFrame"; import InfoRow from "../components/InfoRow"; import Contract from "./Contract"; import { RuntimeContext } from "../useRuntime"; -import { Metadata } from "../useSourcify"; +import { Metadata } from "../sourcify/useSourcify"; import ExternalLink from "../components/ExternalLink"; import { openInRemixURL } from "../url"; import ContractABI from "./ContractABI"; diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index aa8e688..f2d8d08 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -13,7 +13,7 @@ import { ChecksummedAddress, ProcessedTransaction } from "../types"; import { PAGE_SIZE } from "../params"; import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { useAppConfigContext } from "../useAppConfig"; -import { useMultipleMetadata } from "../useSourcify"; +import { useMultipleMetadata } from "../sourcify/useSourcify"; type BlockTransactionResultsProps = { blockTag: BlockTag; diff --git a/src/components/DecoratedAddressLink.tsx b/src/components/DecoratedAddressLink.tsx index c92ed98..0f13f2c 100644 --- a/src/components/DecoratedAddressLink.tsx +++ b/src/components/DecoratedAddressLink.tsx @@ -10,7 +10,7 @@ import AddressOrENSName from "./AddressOrENSName"; import SourcifyLogo from "../sourcify/SourcifyLogo"; import { AddressContext, ZERO_ADDRESS } from "../types"; import { ResolvedAddresses } from "../api/address-resolver"; -import { Metadata } from "../useSourcify"; +import { Metadata } from "../sourcify/useSourcify"; type DecoratedAddressLinkProps = { address: string; diff --git a/src/components/TransactionAddress.tsx b/src/components/TransactionAddress.tsx index 28540dd..cb1edc9 100644 --- a/src/components/TransactionAddress.tsx +++ b/src/components/TransactionAddress.tsx @@ -4,7 +4,7 @@ import DecoratedAddressLink from "./DecoratedAddressLink"; import { ResolvedAddresses } from "../api/address-resolver"; import { useSelectedTransaction } from "../useSelectedTransaction"; import { AddressContext } from "../types"; -import { Metadata } from "../useSourcify"; +import { Metadata } from "../sourcify/useSourcify"; type TransactionAddressProps = { address: string; diff --git a/src/search/TransactionItem.tsx b/src/search/TransactionItem.tsx index 446daf4..929d2cd 100644 --- a/src/search/TransactionItem.tsx +++ b/src/search/TransactionItem.tsx @@ -19,7 +19,7 @@ import { FeeDisplay } from "./useFeeToggler"; import { formatValue } from "../components/formatter"; import ETH2USDValue from "../components/ETH2USDValue"; import { ResolvedAddresses } from "../api/address-resolver"; -import { Metadata } from "../useSourcify"; +import { Metadata } from "../sourcify/useSourcify"; type TransactionItemProps = { tx: ProcessedTransaction; diff --git a/src/useSourcify.ts b/src/sourcify/useSourcify.ts similarity index 98% rename from src/useSourcify.ts rename to src/sourcify/useSourcify.ts index d7878db..e4d1ab1 100644 --- a/src/useSourcify.ts +++ b/src/sourcify/useSourcify.ts @@ -1,7 +1,7 @@ import { useState, useEffect, useMemo } from "react"; import { Interface } from "@ethersproject/abi"; -import { ChecksummedAddress, TransactionData } from "./types"; -import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "./url"; +import { ChecksummedAddress, TransactionData } from "../types"; +import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "../url"; export type UserMethod = { notice?: string | undefined; diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index e2613be..994ab51 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -37,7 +37,7 @@ import { use4Bytes, useTransactionDescription, } from "../use4Bytes"; -import { DevDoc, useMultipleMetadata, UserDoc } from "../useSourcify"; +import { DevDoc, useMultipleMetadata, UserDoc } from "../sourcify/useSourcify"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; import { useAppConfigContext } from "../useAppConfig"; diff --git a/src/transaction/LogEntry.tsx b/src/transaction/LogEntry.tsx index 109ec1e..07be33f 100644 --- a/src/transaction/LogEntry.tsx +++ b/src/transaction/LogEntry.tsx @@ -10,7 +10,7 @@ import DecodedLogSignature from "./decoder/DecodedLogSignature"; import { useTopic0 } from "../useTopic0"; import { ResolvedAddresses } from "../api/address-resolver"; import { ChecksummedAddress } from "../types"; -import { Metadata } from "../useSourcify"; +import { Metadata } from "../sourcify/useSourcify"; type LogEntryProps = { log: Log; diff --git a/src/transaction/Logs.tsx b/src/transaction/Logs.tsx index bbf0475..4a9cc93 100644 --- a/src/transaction/Logs.tsx +++ b/src/transaction/Logs.tsx @@ -4,7 +4,7 @@ import ContentFrame from "../ContentFrame"; import LogEntry from "./LogEntry"; import { TransactionData } from "../types"; import { useAppConfigContext } from "../useAppConfig"; -import { Metadata, useMultipleMetadata } from "../useSourcify"; +import { Metadata, useMultipleMetadata } from "../sourcify/useSourcify"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; diff --git a/src/transaction/decoder/DecodedParamsTable.tsx b/src/transaction/decoder/DecodedParamsTable.tsx index 376eee2..78d94e6 100644 --- a/src/transaction/decoder/DecodedParamsTable.tsx +++ b/src/transaction/decoder/DecodedParamsTable.tsx @@ -1,7 +1,7 @@ import React from "react"; import { ParamType, Result } from "@ethersproject/abi"; import DecodedParamRow from "./DecodedParamRow"; -import { DevMethod, UserMethod } from "../../useSourcify"; +import { DevMethod, UserMethod } from "../../sourcify/useSourcify"; import { ResolvedAddresses } from "../../api/address-resolver"; type DecodedParamsTableProps = { diff --git a/src/transaction/decoder/InputDecoder.tsx b/src/transaction/decoder/InputDecoder.tsx index e3d041c..0bd9835 100644 --- a/src/transaction/decoder/InputDecoder.tsx +++ b/src/transaction/decoder/InputDecoder.tsx @@ -4,7 +4,7 @@ import { toUtf8String } from "@ethersproject/strings"; import { Tab } from "@headlessui/react"; import ModeTab from "../../components/ModeTab"; import DecodedParamsTable from "./DecodedParamsTable"; -import { DevMethod, UserMethod } from "../../useSourcify"; +import { DevMethod, UserMethod } from "../../sourcify/useSourcify"; import { ResolvedAddresses } from "../../api/address-resolver"; type InputDecoderProps = { From 3bfe0d0055df3c3a8e68974207ff59d74e461af6 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 25 Nov 2021 16:20:44 -0300 Subject: [PATCH 14/45] Extract and generalize ETH address fixing logic --- src/AddressTransactions.tsx | 72 ++++++++++++------------------------- src/useResolvedAddresses.ts | 64 +++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 51 deletions(-) diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index a4eb24f..6225ea1 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext } from "react"; +import React, { useEffect, useContext, useCallback } from "react"; import { useParams, useNavigate, @@ -6,7 +6,6 @@ import { Route, useSearchParams, } from "react-router-dom"; -import { getAddress, isAddress } from "@ethersproject/address"; import { Tab } from "@headlessui/react"; import Blockies from "react-blockies"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -21,7 +20,9 @@ import AddressTransactionResults from "./address/AddressTransactionResults"; import Contracts from "./address/Contracts"; import { RuntimeContext } from "./useRuntime"; import { useAppConfigContext } from "./useAppConfig"; +import { useAddressOrENSFromURL } from "./useResolvedAddresses"; import { useSingleMetadata } from "./sourcify/useSourcify"; +import { ChecksummedAddress } from "./types"; const AddressTransactions: React.FC = () => { const { provider } = useContext(RuntimeContext); @@ -32,56 +33,29 @@ const AddressTransactions: React.FC = () => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); + const urlFixer = useCallback( + (address: ChecksummedAddress) => { + navigate( + `/address/${address}${ + direction ? "/" + direction : "" + }?${searchParams.toString()}`, + { replace: true } + ); + }, + [navigate, direction, searchParams] + ); + const [checksummedAddress, isENS, error] = useAddressOrENSFromURL( + addressOrName, + urlFixer + ); - const [checksummedAddress, setChecksummedAddress] = useState< - string | undefined - >(); - const [isENS, setENS] = useState<boolean>(); - const [error, setError] = useState<boolean>(); - - // If it looks like it is an ENS name, try to resolve it useEffect(() => { - // TODO: handle and offer fallback to bad checksummed addresses - if (isAddress(addressOrName)) { - setENS(false); - setError(false); - - // Normalize to checksummed address - const _checksummedAddress = getAddress(addressOrName); - if (_checksummedAddress !== addressOrName) { - // Request came with a non-checksummed address; fix the URL - navigate( - `/address/${_checksummedAddress}${ - direction ? "/" + direction : "" - }?${searchParams.toString()}`, - { replace: true } - ); - return; - } - - setChecksummedAddress(_checksummedAddress); - document.title = `Address ${_checksummedAddress} | Otterscan`; - return; + if (isENS || checksummedAddress === undefined) { + document.title = `Address ${addressOrName} | Otterscan`; + } else { + document.title = `Address ${checksummedAddress} | Otterscan`; } - - if (!provider) { - return; - } - const resolveName = async () => { - const resolvedAddress = await provider.resolveName(addressOrName); - if (resolvedAddress !== null) { - setENS(true); - setError(false); - setChecksummedAddress(resolvedAddress); - document.title = `Address ${addressOrName} | Otterscan`; - } else { - setENS(false); - setError(true); - setChecksummedAddress(undefined); - } - }; - resolveName(); - }, [provider, addressOrName, navigate, direction, searchParams]); + }, [addressOrName, checksummedAddress, isENS]); const { sourcifySource } = useAppConfigContext(); const addressMetadata = useSingleMetadata( diff --git a/src/useResolvedAddresses.ts b/src/useResolvedAddresses.ts index 9eadfde..dc0df3a 100644 --- a/src/useResolvedAddresses.ts +++ b/src/useResolvedAddresses.ts @@ -1,8 +1,68 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useContext } from "react"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { ProcessedTransaction, TransactionData } from "./types"; +import { getAddress, isAddress } from "@ethersproject/address"; import { batchPopulate, ResolvedAddresses } from "./api/address-resolver"; import { TraceGroup } from "./useErigonHooks"; +import { RuntimeContext } from "./useRuntime"; +import { + ChecksummedAddress, + ProcessedTransaction, + TransactionData, +} from "./types"; + +export const useAddressOrENSFromURL = ( + addressOrName: string, + urlFixer: (address: ChecksummedAddress) => void +): [ + ChecksummedAddress | undefined, + boolean | undefined, + boolean | undefined +] => { + const { provider } = useContext(RuntimeContext); + const [checksummedAddress, setChecksummedAddress] = useState< + ChecksummedAddress | undefined + >(); + const [isENS, setENS] = useState<boolean>(); + const [error, setError] = useState<boolean>(); + + // If it looks like it is an ENS name, try to resolve it + useEffect(() => { + // TODO: handle and offer fallback to bad checksummed addresses + if (isAddress(addressOrName)) { + // Normalize to checksummed address + const _checksummedAddress = getAddress(addressOrName); + if (_checksummedAddress !== addressOrName) { + // Request came with a non-checksummed address; fix the URL + urlFixer(_checksummedAddress); + return; + } + + setENS(false); + setError(false); + setChecksummedAddress(_checksummedAddress); + return; + } + + if (!provider) { + return; + } + const resolveName = async () => { + const resolvedAddress = await provider.resolveName(addressOrName); + if (resolvedAddress !== null) { + setENS(true); + setError(false); + setChecksummedAddress(resolvedAddress); + } else { + setENS(false); + setError(true); + setChecksummedAddress(undefined); + } + }; + resolveName(); + }, [provider, addressOrName, urlFixer]); + + return [checksummedAddress, isENS, error]; +}; export type AddressCollector = () => string[]; From 83ce3ab734c31ec217ec498bf0eb351bb2947e1d Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 26 Nov 2021 03:38:24 -0300 Subject: [PATCH 15/45] Fix contract tab --- src/AddressTransactions.tsx | 8 +++++++- src/App.tsx | 6 +----- src/search/UndefinedPageButton.tsx | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index 6225ea1..17c8888 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -126,7 +126,13 @@ const AddressTransactions: React.FC = () => { <Tab.Panels> <Routes> <Route - path="*" + index + element={ + <AddressTransactionResults address={checksummedAddress} /> + } + /> + <Route + path="txs/:direction" element={ <AddressTransactionResults address={checksummedAddress} /> } diff --git a/src/App.tsx b/src/App.tsx index 4ee2dca..f31fff6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -60,11 +60,7 @@ const App = () => { /> <Route path="tx/:txhash/*" element={<Transaction />} /> <Route - path="address/:addressOrName" - element={<AddressTransactions />} - /> - <Route - path="address/:addressOrName/:direction" + path="address/:addressOrName/*" element={<AddressTransactions />} /> <Route path="*" element={<Home />} /> diff --git a/src/search/UndefinedPageButton.tsx b/src/search/UndefinedPageButton.tsx index 9b8f644..102fc6d 100644 --- a/src/search/UndefinedPageButton.tsx +++ b/src/search/UndefinedPageButton.tsx @@ -26,7 +26,7 @@ const UndefinedPageButton: React.FC<UndefinedPageButtonProps> = ({ return ( <NavLink className="transition-colors bg-link-blue bg-opacity-10 text-link-blue hover:bg-opacity-100 hover:text-white disabled:bg-link-blue disabled:text-gray-400 disabled:cursor-default rounded-lg px-3 py-2 text-xs" - to={`/address/${address}/${direction}${ + to={`/address/${address}/txs/${direction}${ direction === "prev" || direction === "next" ? `?h=${hash}` : "" }`} > From 8506009917067cdf3253e38fc26a5836166d8c3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Nov 2021 06:47:57 +0000 Subject: [PATCH 16/45] Bump @types/react from 17.0.36 to 17.0.37 (#128) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00aa601..aee27c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.24", "@types/node": "^14.17.5", - "@types/react": "^17.0.36", + "@types/react": "^17.0.37", "@types/react-blockies": "^1.4.1", "@types/react-dom": "^17.0.11", "@types/react-highlight": "^0.12.5", @@ -3120,9 +3120,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "17.0.36", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.36.tgz", - "integrity": "sha512-CUFUp01OdfbpN/76v4koqgcpcRGT3sYOq3U3N6q0ZVGcyeP40NUdVU+EWe3hs34RNaTefiYyBzOpxBBidCc5zw==", + "version": "17.0.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", + "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -21453,9 +21453,9 @@ "version": "1.5.4" }, "@types/react": { - "version": "17.0.36", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.36.tgz", - "integrity": "sha512-CUFUp01OdfbpN/76v4koqgcpcRGT3sYOq3U3N6q0ZVGcyeP40NUdVU+EWe3hs34RNaTefiYyBzOpxBBidCc5zw==", + "version": "17.0.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", + "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", diff --git a/package.json b/package.json index 95e2ed6..5176a95 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.24", "@types/node": "^14.17.5", - "@types/react": "^17.0.36", + "@types/react": "^17.0.37", "@types/react-blockies": "^1.4.1", "@types/react-dom": "^17.0.11", "@types/react-highlight": "^0.12.5", From 541c88fca595ccb072c127030b0fe1d995f1fe3d Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 26 Nov 2021 03:50:06 -0300 Subject: [PATCH 17/45] Ignore vscode settings --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4d29575..349cafa 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +/.vscode From 43171c444e086529a0bbaf4d1628580f1f6e2528 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Nov 2021 06:50:44 +0000 Subject: [PATCH 18/45] Bump @craco/craco from 6.4.1 to 6.4.2 (#129) --- package-lock.json | 94 ++++++++++++++++++++--------------------------- package.json | 2 +- 2 files changed, 40 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index aee27c0..a4e34a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@blackbox-vision/react-qr-reader": "^5.0.0", "@chainlink/contracts": "^0.2.2", - "@craco/craco": "^6.4.1", + "@craco/craco": "^6.4.2", "@fontsource/fira-code": "^4.5.2", "@fontsource/roboto": "^4.5.1", "@fontsource/roboto-mono": "^4.5.0", @@ -1247,11 +1247,10 @@ } }, "node_modules/@craco/craco": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.4.1.tgz", - "integrity": "sha512-KZDbnHUwxoWrNPeSbfThnGFlG+K+0ZwnwYAj+JgtUCN5FRZ5QQOlc9hhMb44pN3CpWEOTyA0YE/rJeuAXFAErw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.4.2.tgz", + "integrity": "sha512-egIooyvuzKM5dsvWe/U5ISyFpZwLnG9uuTF1fU4s/6b/hE8MvoxyaxKymQKgbtpfOZeH0ebtEP4cbH7xZ4XRbw==", "dependencies": { - "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", "cosmiconfig": "^7.0.1", "cross-spawn": "^7.0.0", "lodash": "^4.17.15", @@ -1268,23 +1267,6 @@ "react-scripts": "^4.0.0" } }, - "node_modules/@craco/craco/node_modules/@endemolshinegroup/cosmiconfig-typescript-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", - "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", - "dependencies": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "cosmiconfig": ">=6" - } - }, "node_modules/@craco/craco/node_modules/cosmiconfig": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", @@ -6181,7 +6163,9 @@ "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -6919,6 +6903,8 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "optional": true, + "peer": true, "engines": { "node": ">=0.3.1" } @@ -11309,11 +11295,6 @@ "version": "3.0.0", "license": "MIT" }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "license": "MIT" @@ -11441,7 +11422,9 @@ "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "optional": true, + "peer": true }, "node_modules/makeerror": { "version": "1.0.11", @@ -17872,6 +17855,8 @@ "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "optional": true, + "peer": true, "dependencies": { "arg": "^4.1.0", "create-require": "^1.1.0", @@ -17896,7 +17881,9 @@ "node_modules/ts-node/node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "optional": true, + "peer": true }, "node_modules/ts-pnp": { "version": "1.2.0", @@ -19454,6 +19441,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -20299,11 +20288,10 @@ } }, "@craco/craco": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.4.1.tgz", - "integrity": "sha512-KZDbnHUwxoWrNPeSbfThnGFlG+K+0ZwnwYAj+JgtUCN5FRZ5QQOlc9hhMb44pN3CpWEOTyA0YE/rJeuAXFAErw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.4.2.tgz", + "integrity": "sha512-egIooyvuzKM5dsvWe/U5ISyFpZwLnG9uuTF1fU4s/6b/hE8MvoxyaxKymQKgbtpfOZeH0ebtEP4cbH7xZ4XRbw==", "requires": { - "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", "cosmiconfig": "^7.0.1", "cross-spawn": "^7.0.0", "lodash": "^4.17.15", @@ -20311,17 +20299,6 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "@endemolshinegroup/cosmiconfig-typescript-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", - "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", - "requires": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - } - }, "cosmiconfig": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", @@ -23590,7 +23567,9 @@ "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "optional": true, + "peer": true }, "cross-spawn": { "version": "7.0.3", @@ -24074,7 +24053,9 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "optional": true, + "peer": true }, "diff-sequences": { "version": "26.6.2" @@ -26945,11 +26926,6 @@ "lodash._reinterpolate": { "version": "3.0.0" }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, "lodash.memoize": { "version": "4.1.2" }, @@ -27037,7 +27013,9 @@ "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "optional": true, + "peer": true }, "makeerror": { "version": "1.0.11", @@ -31431,6 +31409,8 @@ "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "optional": true, + "peer": true, "requires": { "arg": "^4.1.0", "create-require": "^1.1.0", @@ -31443,7 +31423,9 @@ "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "optional": true, + "peer": true } } }, @@ -32518,7 +32500,9 @@ "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "optional": true, + "peer": true }, "yocto-queue": { "version": "0.1.0" diff --git a/package.json b/package.json index 5176a95..5bce984 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dependencies": { "@blackbox-vision/react-qr-reader": "^5.0.0", "@chainlink/contracts": "^0.2.2", - "@craco/craco": "^6.4.1", + "@craco/craco": "^6.4.2", "@fontsource/fira-code": "^4.5.2", "@fontsource/roboto": "^4.5.1", "@fontsource/roboto-mono": "^4.5.0", From 1431b449cc2f7beed24e5b7bfda881862c324185 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 26 Nov 2021 03:53:38 -0300 Subject: [PATCH 19/45] Update trustwallet assets --- trustwallet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trustwallet b/trustwallet index 30e4ffa..e779c7b 160000 --- a/trustwallet +++ b/trustwallet @@ -1 +1 @@ -Subproject commit 30e4ffa0153594b11421cf383b1192e4414d2f66 +Subproject commit e779c7b400fc479f8442066f13565555be5bfcf3 From 380ef96225a099defb3d2390e3e558d8f7c660d4 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 26 Nov 2021 05:13:23 -0300 Subject: [PATCH 20/45] Change default token icon --- public/eth-diamond-black.png | Bin 16034 -> 0 bytes src/components/TokenLogo.tsx | 25 ++++++++++++------------- 2 files changed, 12 insertions(+), 13 deletions(-) delete mode 100644 public/eth-diamond-black.png diff --git a/public/eth-diamond-black.png b/public/eth-diamond-black.png deleted file mode 100644 index 51498780c204121f6f4e04b7d22ceffd2ab597b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16034 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 ZR~uKGJdLV*EBST#K}R=-XLf-}{{u>`1OWg5 diff --git a/src/components/TokenLogo.tsx b/src/components/TokenLogo.tsx index 8568a0b..7eaf2bd 100644 --- a/src/components/TokenLogo.tsx +++ b/src/components/TokenLogo.tsx @@ -1,32 +1,31 @@ -import React, { Suspense, useContext } from "react"; +import React, { useContext } from "react"; import { useImage } from "react-image"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins"; import { tokenLogoURL } from "../url"; import { RuntimeContext } from "../useRuntime"; +import { ChecksummedAddress } from "../types"; type TokenLogoProps = { - address: string; + address: ChecksummedAddress; name: string; }; -const TokenLogo: React.FC<TokenLogoProps> = (props) => ( - <Suspense fallback={null}> - <InternalTokenLogo {...props} /> - </Suspense> -); - -const InternalTokenLogo: React.FC<TokenLogoProps> = ({ address, name }) => { +const TokenLogo: React.FC<TokenLogoProps> = ({ address, name }) => { const { config } = useContext(RuntimeContext); const srcList: string[] = []; if (config) { srcList.push(tokenLogoURL(config.assetsURLPrefix ?? "", address)); } - srcList.push("/eth-diamond-black.png"); - const { src } = useImage({ srcList }); + const { src, isLoading } = useImage({ srcList, useSuspense: false }); return ( - <div className="flex items-center justify-center w-5 h-5"> - <img className="max-w-full max-h-full" src={src} alt={`${name} logo`} /> + <div className="flex items-center justify-center text-gray-400 w-5 h-5"> + {src && ( + <img className="max-w-full max-h-full" src={src} alt={`${name} logo`} /> + )} + {!src && !isLoading && <FontAwesomeIcon icon={faCoins} size="1x" />} </div> ); }; From 030594ed35f5caa2a6a65f05e65c5d7462914f34 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 26 Nov 2021 05:18:10 -0300 Subject: [PATCH 21/45] Update topic0 db --- topic0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topic0 b/topic0 index 5026a20..63794c4 160000 --- a/topic0 +++ b/topic0 @@ -1 +1 @@ -Subproject commit 5026a20b712c1cad66878821c38e1f070e4a3799 +Subproject commit 63794c46467dea47fd99ec47db745c482887367e From 44eec1d2e78793a2866ad5d7a21d57e6270187cf Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 26 Nov 2021 05:22:08 -0300 Subject: [PATCH 22/45] Undo hash coloring --- src/components/HexValue.tsx | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/components/HexValue.tsx b/src/components/HexValue.tsx index b07b473..88d6e0b 100644 --- a/src/components/HexValue.tsx +++ b/src/components/HexValue.tsx @@ -4,26 +4,8 @@ type HexValueProps = { value: string; }; -const HexValue: React.FC<HexValueProps> = ({ value }) => { - const shards: string[] = [value.slice(0, 10)]; - for (let i = 10; i < value.length; i += 8) { - shards.push(value.slice(i, i + 8)); - } +const HexValue: React.FC<HexValueProps> = ({ value }) => ( + <span className="font-hash text-black">{value}</span> +); - return ( - <> - {shards.map((s, i) => ( - <span - key={i} - className={`font-hash ${ - i % 2 === 0 ? "text-black" : "text-gray-400" - }`} - > - {s} - </span> - ))} - </> - ); -}; - -export default React.memo(HexValue); +export default HexValue; From 8092f7699d8bda93db5d7c94029d0536f61d648b Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 26 Nov 2021 09:11:35 -0300 Subject: [PATCH 23/45] Add tornado.cash pool addresses --- .../hardcoded-addresses/1.json | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/api/address-resolver/hardcoded-addresses/1.json b/src/api/address-resolver/hardcoded-addresses/1.json index 072a2c0..35575d9 100644 --- a/src/api/address-resolver/hardcoded-addresses/1.json +++ b/src/api/address-resolver/hardcoded-addresses/1.json @@ -4,10 +4,29 @@ "0x3342E3737732D879743f2682A3953a730ae4F47C": "Gitcoin: GR9 Matching Payout", "0x3ebAFfe01513164e638480404c651E885cCA0AA4": "Gitcoin: GR10 Matching Payout", "0x0EbD2E2130b73107d0C45fF2E16c93E7e2e10e3a": "Gitcoin: GR11 Matching Payout", - "0x722122dF12D4e14e13Ac3b6895a86e84145b6967": "Tornado Cash: Proxy", "0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95": "Uniswap V1: Factory", "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f": "Uniswap V2: Factory", "0xf164fC0Ec4E93095b804a4795bBe1e041497b92a": "Uniswap V2: Router 1", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D": "Uniswap V2: Router 2", - "0x1F98431c8aD98523631AE4a59f267346ea31F984": "Uniswap V3: Router" + "0x1F98431c8aD98523631AE4a59f267346ea31F984": "Uniswap V3: Router", + "0x722122dF12D4e14e13Ac3b6895a86e84145b6967": "Tornado Cash: Proxy", + "0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc": "Tornado Cash: 0.1 ETH", + "0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936": "Tornado Cash: 1 ETH", + "0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF": "Tornado Cash: 10 ETH", + "0xA160cdAB225685dA1d56aa342Ad8841c3b53f291": "Tornado Cash: 100 ETH", + "0xD4B88Df4D29F5CedD6857912842cff3b20C8Cfa3": "Tornado Cash: 100 DAI", + "0xFD8610d20aA15b7B2E3Be39B396a1bC3516c7144": "Tornado Cash: 1K DAI", + "0x07687e702b410Fa43f4cB4Af7FA097918ffD2730": "Tornado Cash: 10K DAI", + "0x23773E65ed146A459791799d01336DB287f25334": "Tornado Cash: 100K DAI", + "0x22aaA7720ddd5388A3c0A3333430953C68f1849b": "Tornado Cash: 5K cDAI", + "0x03893a7c7463AE47D46bc7f091665f1893656003": "Tornado Cash: 50K cDAI", + "0x2717c5e28cf931547B621a5dddb772Ab6A35B701": "Tornado Cash: 500K cDAI", + "0xD21be7248e0197Ee08E0c20D4a96DEBdaC3D20Af": "Tornado Cash: 5M cDAI", + "0xd96f2B1c14Db8458374d9Aca76E26c3D18364307": "Tornado Cash: 100 USDC", + "0x4736dCf1b7A3d580672CcE6E7c65cd5cc9cFBa9D": "Tornado Cash: 1K USDC", + "0x169AD27A470D064DEDE56a2D3ff727986b15D52B": "Tornado Cash: 100 USDT", + "0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f": "Tornado Cash: 1K USDT", + "0x178169B423a011fff22B9e3F3abeA13414dDD0F1": "Tornado Cash: 0.1 WBTC", + "0x610B717796ad172B316836AC95a2ffad065CeaB4": "Tornado Cash: 1 WBTC", + "0xbB93e510BbCD0B7beb5A853875f9eC60275CF498": "Tornado Cash: 10 WBTC" } \ No newline at end of file From 9d56f981b32e587042f335a39ded1d467c5a9fa6 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Sun, 28 Nov 2021 13:06:20 -0300 Subject: [PATCH 24/45] Add known binance addresses --- src/api/address-resolver/hardcoded-addresses/1.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/address-resolver/hardcoded-addresses/1.json b/src/api/address-resolver/hardcoded-addresses/1.json index 35575d9..a33327e 100644 --- a/src/api/address-resolver/hardcoded-addresses/1.json +++ b/src/api/address-resolver/hardcoded-addresses/1.json @@ -28,5 +28,7 @@ "0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f": "Tornado Cash: 1K USDT", "0x178169B423a011fff22B9e3F3abeA13414dDD0F1": "Tornado Cash: 0.1 WBTC", "0x610B717796ad172B316836AC95a2ffad065CeaB4": "Tornado Cash: 1 WBTC", - "0xbB93e510BbCD0B7beb5A853875f9eC60275CF498": "Tornado Cash: 10 WBTC" + "0xbB93e510BbCD0B7beb5A853875f9eC60275CF498": "Tornado Cash: 10 WBTC", + "0x56Eddb7aa87536c09CCc2793473599fD21A8b17F": "Binance", + "0x9696f59E4d72E237BE84fFD425DCaD154Bf96976": "Binance" } \ No newline at end of file From fcc05de66df5be087d512aa13f1169273b882673 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Sun, 28 Nov 2021 13:58:53 -0300 Subject: [PATCH 25/45] Add old tornado proxy address --- src/api/address-resolver/hardcoded-addresses/1.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/address-resolver/hardcoded-addresses/1.json b/src/api/address-resolver/hardcoded-addresses/1.json index a33327e..2b7b997 100644 --- a/src/api/address-resolver/hardcoded-addresses/1.json +++ b/src/api/address-resolver/hardcoded-addresses/1.json @@ -29,6 +29,7 @@ "0x178169B423a011fff22B9e3F3abeA13414dDD0F1": "Tornado Cash: 0.1 WBTC", "0x610B717796ad172B316836AC95a2ffad065CeaB4": "Tornado Cash: 1 WBTC", "0xbB93e510BbCD0B7beb5A853875f9eC60275CF498": "Tornado Cash: 10 WBTC", + "0x94A1B5CdB22c43faab4AbEb5c74999895464Ddaf": "Tornado Cash: Old Proxy", "0x56Eddb7aa87536c09CCc2793473599fD21A8b17F": "Binance", "0x9696f59E4d72E237BE84fFD425DCaD154Bf96976": "Binance" } \ No newline at end of file From 53630f43a431ed09341cc5a4e5c6ceaf1f37e51d Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Mon, 29 Nov 2021 14:28:35 -0300 Subject: [PATCH 26/45] Silence catch error on purpose --- src/usePriceOracle.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/usePriceOracle.ts b/src/usePriceOracle.ts index f41a1be..2e49afd 100644 --- a/src/usePriceOracle.ts +++ b/src/usePriceOracle.ts @@ -50,7 +50,8 @@ export const useMultipleETHUSDOracle = ( const priceData = await ethFeed.latestRoundData({ blockTag }); return BigNumber.from(priceData.answer); } catch (err) { - console.error(err); + // Silently ignore on purpose; it means the network or block number does + // not contain the chainlink feed contract return undefined; } })() From 832f5f435f00d0276c27f9dba8c5e0dab18f4f5b Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Mon, 29 Nov 2021 14:45:14 -0300 Subject: [PATCH 27/45] Reduce the amount of network calls by probing first only name() and on success probe other erc20 methods --- src/api/address-resolver/ERCTokenResolver.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/api/address-resolver/ERCTokenResolver.ts b/src/api/address-resolver/ERCTokenResolver.ts index f087ec6..49301bd 100644 --- a/src/api/address-resolver/ERCTokenResolver.ts +++ b/src/api/address-resolver/ERCTokenResolver.ts @@ -1,24 +1,31 @@ import { BaseProvider } from "@ethersproject/providers"; import { Contract } from "@ethersproject/contracts"; +import { Interface } from "@ethersproject/abi"; import { IAddressResolver } from "./address-resolver"; import erc20 from "../../erc20.json"; import { TokenMeta } from "../../types"; +const erc20Interface = new Interface(erc20); + export class ERCTokenResolver implements IAddressResolver<TokenMeta> { async resolveAddress( provider: BaseProvider, address: string ): Promise<TokenMeta | undefined> { - const erc20Contract = new Contract(address, erc20, provider); + const erc20Contract = new Contract(address, erc20Interface, provider); try { - const [name, symbol, decimals] = (await Promise.all([ - erc20Contract.name(), + const name = (await erc20Contract.name()) as string; + if (!name.trim()) { + return undefined; + } + + const [symbol, decimals] = (await Promise.all([ erc20Contract.symbol(), erc20Contract.decimals(), - ])) as [string, string, number]; + ])) as [string, number]; // Prevent faulty tokens with empty name/symbol - if (!name.trim() || !symbol.trim()) { + if (!symbol.trim()) { return undefined; } From e37a5b6c0239aadde4d96af8213277239cebd77d Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Mon, 29 Nov 2021 15:03:09 -0300 Subject: [PATCH 28/45] Add ENS mainnet contracts --- src/api/address-resolver/hardcoded-addresses/1.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/api/address-resolver/hardcoded-addresses/1.json b/src/api/address-resolver/hardcoded-addresses/1.json index 2b7b997..53f7122 100644 --- a/src/api/address-resolver/hardcoded-addresses/1.json +++ b/src/api/address-resolver/hardcoded-addresses/1.json @@ -1,4 +1,9 @@ { + "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e": "ENS: Registry", + "0xFaC7BEA255a6990f749363002136aF6556b31e04": "ENS: Old .eth Registrar", + "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85": "ENS: .eth Registrar", + "0x6109DD117AA5486605FC85e040ab00163a75c662": "ENS: Migration Contract", + "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41": "ENS: Public Resolver", "0x7d655c57f71464B6f83811C55D84009Cd9f5221C": "Gitcoin: Bulk Checkout", "0xf2354570bE2fB420832Fb7Ff6ff0AE0dF80CF2c6": "Gitcoin: GR8 Matching Payout", "0x3342E3737732D879743f2682A3953a730ae4F47C": "Gitcoin: GR9 Matching Payout", From 5216198b0612c157da03c046ed4d812e023b872f Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Mon, 29 Nov 2021 15:32:07 -0300 Subject: [PATCH 29/45] Add selection highlighting support to decoded param table values --- src/components/SelectionHighlighter.tsx | 1 + src/transaction/decoder/DecodedParamRow.tsx | 45 ++++++++++++--------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/components/SelectionHighlighter.tsx b/src/components/SelectionHighlighter.tsx index 40a3431..bebd606 100644 --- a/src/components/SelectionHighlighter.tsx +++ b/src/components/SelectionHighlighter.tsx @@ -18,6 +18,7 @@ export const genericSelector = selection.content === content; export const addressSelector: ContentSelector = genericSelector("address"); +export const valueSelector: ContentSelector = genericSelector("value"); export const functionSigSelector: ContentSelector = genericSelector("functionSig"); diff --git a/src/transaction/decoder/DecodedParamRow.tsx b/src/transaction/decoder/DecodedParamRow.tsx index 78467d3..3ad47cc 100644 --- a/src/transaction/decoder/DecodedParamRow.tsx +++ b/src/transaction/decoder/DecodedParamRow.tsx @@ -9,6 +9,9 @@ import AddressDecoder from "./AddressDecoder"; import BooleanDecoder from "./BooleanDecoder"; import BytesDecoder from "./BytesDecoder"; import { ResolvedAddresses } from "../../api/address-resolver"; +import SelectionHighlighter, { + valueSelector, +} from "../../components/SelectionHighlighter"; type DecodedParamRowProps = { prefix?: ReactNode; @@ -68,24 +71,30 @@ const DecodedParamRow: React.FC<DecodedParamRowProps> = ({ {help && showHelp && <div className="mt-2 text-gray-400">{help}</div>} </td> <td className="col-span-1 text-gray-500">{paramType.type}</td> - <td className="col-span-8 pr-1 font-code break-all"> - {paramType.baseType === "uint256" ? ( - <Uint256Decoder r={r} /> - ) : paramType.baseType === "address" ? ( - <AddressDecoder - r={r.toString()} - resolvedAddresses={resolvedAddresses} - /> - ) : paramType.baseType === "bool" ? ( - <BooleanDecoder r={r} /> - ) : paramType.baseType === "bytes" ? ( - <BytesDecoder r={r} /> - ) : paramType.baseType === "tuple" || - paramType.baseType === "array" ? ( - <></> - ) : ( - r.toString() - )} + <td className="col-span-8 pr-1 font-code break-all flex"> + <SelectionHighlighter + myType="value" + myContent={r.toString()} + selector={valueSelector} + > + {paramType.baseType === "uint256" ? ( + <Uint256Decoder r={r} /> + ) : paramType.baseType === "address" ? ( + <AddressDecoder + r={r.toString()} + resolvedAddresses={resolvedAddresses} + /> + ) : paramType.baseType === "bool" ? ( + <BooleanDecoder r={r} /> + ) : paramType.baseType === "bytes" ? ( + <BytesDecoder r={r} /> + ) : paramType.baseType === "tuple" || + paramType.baseType === "array" ? ( + <></> + ) : ( + r.toString() + )} + </SelectionHighlighter> </td> </tr> {paramType.baseType === "tuple" && From 10debf1395dfee8d2568ce232d60a772306ec43a Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 2 Dec 2021 15:21:23 -0300 Subject: [PATCH 30/45] Add eth2 deposit contract --- src/api/address-resolver/hardcoded-addresses/1.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/address-resolver/hardcoded-addresses/1.json b/src/api/address-resolver/hardcoded-addresses/1.json index 53f7122..c05f60a 100644 --- a/src/api/address-resolver/hardcoded-addresses/1.json +++ b/src/api/address-resolver/hardcoded-addresses/1.json @@ -4,6 +4,7 @@ "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85": "ENS: .eth Registrar", "0x6109DD117AA5486605FC85e040ab00163a75c662": "ENS: Migration Contract", "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41": "ENS: Public Resolver", + "0x00000000219ab540356cBB839Cbe05303d7705Fa": "ETH2: Deposit Contract", "0x7d655c57f71464B6f83811C55D84009Cd9f5221C": "Gitcoin: Bulk Checkout", "0xf2354570bE2fB420832Fb7Ff6ff0AE0dF80CF2c6": "Gitcoin: GR8 Matching Payout", "0x3342E3737732D879743f2682A3953a730ae4F47C": "Gitcoin: GR9 Matching Payout", From c3dfcfeee385f49ec36bd330d09cb4dff7b42eb6 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Tue, 30 Nov 2021 16:31:39 -0300 Subject: [PATCH 31/45] Add still unoptimized check for addresses with code --- src/address/AddressTransactionResults.tsx | 10 ++++-- src/block/BlockTransactionResults.tsx | 10 ++++-- src/sourcify/useSourcify.ts | 44 +++++++++++++---------- src/transaction/Details.tsx | 12 +++++-- src/transaction/Logs.tsx | 11 ++++-- src/useErigonHooks.ts | 40 +++++++++++++++++++++ 6 files changed, 100 insertions(+), 27 deletions(-) diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx index b13bbd2..4c85c6c 100644 --- a/src/address/AddressTransactionResults.tsx +++ b/src/address/AddressTransactionResults.tsx @@ -8,7 +8,11 @@ import TransactionItem from "../search/TransactionItem"; import UndefinedPageControl from "../search/UndefinedPageControl"; import { useFeeToggler } from "../search/useFeeToggler"; import { SelectionContext, useSelection } from "../useSelection"; -import { useMultipleMetadata } from "../sourcify/useSourcify"; +import { + useDedupedAddresses, + useMultipleMetadata, +} from "../sourcify/useSourcify"; +import { useAddressCodes } from "../useErigonHooks"; import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { RuntimeContext } from "../useRuntime"; import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses"; @@ -120,9 +124,11 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ return _addresses; }, [address, page]); const { sourcifySource } = useAppConfigContext(); + const deduped = useDedupedAddresses(addresses); + const checked = useAddressCodes(provider, deduped); const metadatas = useMultipleMetadata( undefined, - addresses, + checked, provider?.network.chainId, sourcifySource ); diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index f2d8d08..891637b 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -13,7 +13,11 @@ import { ChecksummedAddress, ProcessedTransaction } from "../types"; import { PAGE_SIZE } from "../params"; import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { useAppConfigContext } from "../useAppConfig"; -import { useMultipleMetadata } from "../sourcify/useSourcify"; +import { + useDedupedAddresses, + useMultipleMetadata, +} from "../sourcify/useSourcify"; +import { useAddressCodes } from "../useErigonHooks"; type BlockTransactionResultsProps = { blockTag: BlockTag; @@ -44,9 +48,11 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({ return page.map((t) => t.to).filter((to): to is string => to !== undefined); }, [page]); const { sourcifySource } = useAppConfigContext(); + const deduped = useDedupedAddresses(addresses); + const checked = useAddressCodes(provider, deduped); const metadatas = useMultipleMetadata( undefined, - addresses, + checked, provider?.network.chainId, sourcifySource ); diff --git a/src/sourcify/useSourcify.ts b/src/sourcify/useSourcify.ts index e4d1ab1..6b1e5d6 100644 --- a/src/sourcify/useSourcify.ts +++ b/src/sourcify/useSourcify.ts @@ -131,51 +131,57 @@ export const useSingleMetadata = ( return address !== undefined ? metadatas[address] : undefined; }; +export const useDedupedAddresses = ( + addresses: (ChecksummedAddress | undefined)[] +): ChecksummedAddress[] => { + return useMemo(() => { + const deduped = new Set( + addresses.filter((a): a is ChecksummedAddress => a !== undefined) + ); + return [...deduped]; + }, [addresses]); +}; + export const useMultipleMetadata = ( baseMetadatas: Record<string, Metadata | null> | undefined, - addresses: (ChecksummedAddress | undefined)[], + addresses: ChecksummedAddress[] | undefined, chainId: number | undefined, source: SourcifySource ): Record<ChecksummedAddress, Metadata | null | undefined> => { const [rawMetadata, setRawMetadata] = useState< Record<string, Metadata | null | undefined> >({}); - useEffect(() => { - if (!addresses || chainId === undefined) { + if (addresses === undefined || chainId === undefined) { return; } setRawMetadata({}); const abortController = new AbortController(); - const fetchMetadata = async (dedupedAddresses: string[]) => { - const promises: Promise<Metadata | null>[] = []; - for (const address of dedupedAddresses) { - promises.push( + const fetchMetadata = async (_addresses: string[]) => { + const fetchers: Promise<Metadata | null>[] = []; + for (const address of _addresses) { + fetchers.push( fetchSourcifyMetadata(address, chainId, source, abortController) ); } - const results = await Promise.all(promises); + const results = await Promise.all(fetchers); if (abortController.signal.aborted) { return; } - const metadatas: Record<string, Metadata | null> = baseMetadatas - ? { ...baseMetadatas } - : {}; + let metadatas: Record<string, Metadata | null> = {}; + if (baseMetadatas) { + metadatas = { ...baseMetadatas }; + } for (let i = 0; i < results.length; i++) { - metadatas[dedupedAddresses[i]] = results[i]; + metadatas[_addresses[i]] = results[i]; } setRawMetadata(metadatas); }; - const deduped = new Set( - addresses.filter( - (a): a is ChecksummedAddress => - a !== undefined && baseMetadatas?.[a] === undefined - ) - ); - fetchMetadata(Array.from(deduped)); + const filtered = addresses.filter((a) => baseMetadatas?.[a] === undefined); + fetchMetadata(filtered); return () => { abortController.abort(); diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 994ab51..8799987 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -37,7 +37,13 @@ import { use4Bytes, useTransactionDescription, } from "../use4Bytes"; -import { DevDoc, useMultipleMetadata, UserDoc } from "../sourcify/useSourcify"; +import { + DevDoc, + useDedupedAddresses, + useMultipleMetadata, + UserDoc, +} from "../sourcify/useSourcify"; +import { useAddressCodes } from "../useErigonHooks"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; import { useAppConfigContext } from "../useAppConfig"; @@ -96,9 +102,11 @@ const Details: React.FC<DetailsProps> = ({ return _addresses; }, [txData]); const { sourcifySource } = useAppConfigContext(); + const deduped = useDedupedAddresses(addresses); + const checked = useAddressCodes(provider, deduped); const metadatas = useMultipleMetadata( undefined, - addresses, + checked, provider?.network.chainId, sourcifySource ); diff --git a/src/transaction/Logs.tsx b/src/transaction/Logs.tsx index 4a9cc93..d907a37 100644 --- a/src/transaction/Logs.tsx +++ b/src/transaction/Logs.tsx @@ -4,7 +4,12 @@ import ContentFrame from "../ContentFrame"; import LogEntry from "./LogEntry"; import { TransactionData } from "../types"; import { useAppConfigContext } from "../useAppConfig"; -import { Metadata, useMultipleMetadata } from "../sourcify/useSourcify"; +import { + Metadata, + useDedupedAddresses, + useMultipleMetadata, +} from "../sourcify/useSourcify"; +import { useAddressCodes } from "../useErigonHooks"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; @@ -31,9 +36,11 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata, resolvedAddresses }) => { ); const { provider } = useContext(RuntimeContext); const { sourcifySource } = useAppConfigContext(); + const deduped = useDedupedAddresses(logAddresses); + const checked = useAddressCodes(provider, deduped); const metadatas = useMultipleMetadata( baseMetadatas, - logAddresses, + checked, provider?.network.chainId, sourcifySource ); diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 17d629f..adbfce2 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -14,6 +14,7 @@ import { InternalOperation, ProcessedTransaction, OperationType, + ChecksummedAddress, } from "./types"; import erc20 from "./erc20.json"; @@ -440,3 +441,42 @@ export const useUniqueSignatures = (traces: TraceGroup[] | undefined) => { return uniqueSignatures; }; + +const checkCode = async ( + provider: JsonRpcProvider, + address: ChecksummedAddress +): Promise<boolean> => { + const code = await provider.getCode(address); + if (code !== "0x") { + console.log(`Has code: ${address}`); + } + return code === "0x"; +}; + +export const useAddressCodes = ( + provider: JsonRpcProvider | undefined, + addresses: ChecksummedAddress[] +): ChecksummedAddress[] | undefined => { + const [hasCode, setCode] = useState<ChecksummedAddress[] | undefined>(); + + useEffect(() => { + if (provider === undefined) { + setCode(undefined); + return; + } + + const readCodes = async () => { + const checkers: Promise<boolean>[] = []; + for (const a of addresses) { + checkers.push(checkCode(provider, a)); + } + + const result = await Promise.all(checkers); + const filtered = addresses.filter((_, i) => result[i]); + setCode(filtered); + }; + readCodes(); + }, [provider, addresses]); + + return hasCode; +}; From 2e04c726d736f0fdc4fea7b96ecdc81ebb149b22 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 2 Dec 2021 15:34:06 -0300 Subject: [PATCH 32/45] Use new custom API to server-side check if address has code/is contract --- src/address/AddressTransactionResults.tsx | 4 ++-- src/block/BlockTransactionResults.tsx | 4 ++-- src/params.ts | 2 +- src/transaction/Details.tsx | 4 ++-- src/transaction/Logs.tsx | 4 ++-- src/useErigonHooks.ts | 21 +++++++++------------ 6 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx index 4c85c6c..e565427 100644 --- a/src/address/AddressTransactionResults.tsx +++ b/src/address/AddressTransactionResults.tsx @@ -12,7 +12,7 @@ import { useDedupedAddresses, useMultipleMetadata, } from "../sourcify/useSourcify"; -import { useAddressCodes } from "../useErigonHooks"; +import { useAddressesWithCode } from "../useErigonHooks"; import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { RuntimeContext } from "../useRuntime"; import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses"; @@ -125,7 +125,7 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ }, [address, page]); const { sourcifySource } = useAppConfigContext(); const deduped = useDedupedAddresses(addresses); - const checked = useAddressCodes(provider, deduped); + const checked = useAddressesWithCode(provider, deduped); const metadatas = useMultipleMetadata( undefined, checked, diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index 891637b..59d1ba6 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -17,7 +17,7 @@ import { useDedupedAddresses, useMultipleMetadata, } from "../sourcify/useSourcify"; -import { useAddressCodes } from "../useErigonHooks"; +import { useAddressesWithCode } from "../useErigonHooks"; type BlockTransactionResultsProps = { blockTag: BlockTag; @@ -49,7 +49,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({ }, [page]); const { sourcifySource } = useAppConfigContext(); const deduped = useDedupedAddresses(addresses); - const checked = useAddressCodes(provider, deduped); + const checked = useAddressesWithCode(provider, deduped); const metadatas = useMultipleMetadata( undefined, checked, diff --git a/src/params.ts b/src/params.ts index 5c95cfa..7ac916a 100644 --- a/src/params.ts +++ b/src/params.ts @@ -1,3 +1,3 @@ -export const MIN_API_LEVEL = 3; +export const MIN_API_LEVEL = 4; export const PAGE_SIZE = 25; diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 8799987..06e70d5 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -43,7 +43,7 @@ import { useMultipleMetadata, UserDoc, } from "../sourcify/useSourcify"; -import { useAddressCodes } from "../useErigonHooks"; +import { useAddressesWithCode } from "../useErigonHooks"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; import { useAppConfigContext } from "../useAppConfig"; @@ -103,7 +103,7 @@ const Details: React.FC<DetailsProps> = ({ }, [txData]); const { sourcifySource } = useAppConfigContext(); const deduped = useDedupedAddresses(addresses); - const checked = useAddressCodes(provider, deduped); + const checked = useAddressesWithCode(provider, deduped); const metadatas = useMultipleMetadata( undefined, checked, diff --git a/src/transaction/Logs.tsx b/src/transaction/Logs.tsx index d907a37..acc2baa 100644 --- a/src/transaction/Logs.tsx +++ b/src/transaction/Logs.tsx @@ -9,7 +9,7 @@ import { useDedupedAddresses, useMultipleMetadata, } from "../sourcify/useSourcify"; -import { useAddressCodes } from "../useErigonHooks"; +import { useAddressesWithCode } from "../useErigonHooks"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; @@ -37,7 +37,7 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata, resolvedAddresses }) => { const { provider } = useContext(RuntimeContext); const { sourcifySource } = useAppConfigContext(); const deduped = useDedupedAddresses(logAddresses); - const checked = useAddressCodes(provider, deduped); + const checked = useAddressesWithCode(provider, deduped); const metadatas = useMultipleMetadata( baseMetadatas, checked, diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index adbfce2..f722f03 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -442,41 +442,38 @@ export const useUniqueSignatures = (traces: TraceGroup[] | undefined) => { return uniqueSignatures; }; -const checkCode = async ( +const hasCode = async ( provider: JsonRpcProvider, address: ChecksummedAddress ): Promise<boolean> => { - const code = await provider.getCode(address); - if (code !== "0x") { - console.log(`Has code: ${address}`); - } - return code === "0x"; + const result = await provider.send("ots_hasCode", [address, "latest"]); + return result as boolean; }; -export const useAddressCodes = ( +export const useAddressesWithCode = ( provider: JsonRpcProvider | undefined, addresses: ChecksummedAddress[] ): ChecksummedAddress[] | undefined => { - const [hasCode, setCode] = useState<ChecksummedAddress[] | undefined>(); + const [results, setResults] = useState<ChecksummedAddress[] | undefined>(); useEffect(() => { if (provider === undefined) { - setCode(undefined); + setResults(undefined); return; } const readCodes = async () => { const checkers: Promise<boolean>[] = []; for (const a of addresses) { - checkers.push(checkCode(provider, a)); + checkers.push(hasCode(provider, a)); } const result = await Promise.all(checkers); const filtered = addresses.filter((_, i) => result[i]); - setCode(filtered); + setResults(filtered); }; readCodes(); }, [provider, addresses]); - return hasCode; + return results; }; From c40e653bef0e164a8177b69a453d8d411003ffcd Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 2 Dec 2021 15:42:59 -0300 Subject: [PATCH 33/45] Small refactorings --- src/address/AddressTransactionResults.tsx | 6 +++--- src/block/BlockTransactionResults.tsx | 6 +++--- src/sourcify/useSourcify.ts | 6 ++---- src/transaction/Details.tsx | 6 +++--- src/transaction/Logs.tsx | 6 +++--- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx index e565427..ce7cf45 100644 --- a/src/address/AddressTransactionResults.tsx +++ b/src/address/AddressTransactionResults.tsx @@ -123,12 +123,12 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ } return _addresses; }, [address, page]); - const { sourcifySource } = useAppConfigContext(); const deduped = useDedupedAddresses(addresses); - const checked = useAddressesWithCode(provider, deduped); + const contracts = useAddressesWithCode(provider, deduped); + const { sourcifySource } = useAppConfigContext(); const metadatas = useMultipleMetadata( undefined, - checked, + contracts, provider?.network.chainId, sourcifySource ); diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index 59d1ba6..44caeb1 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -47,12 +47,12 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({ return page.map((t) => t.to).filter((to): to is string => to !== undefined); }, [page]); - const { sourcifySource } = useAppConfigContext(); const deduped = useDedupedAddresses(addresses); - const checked = useAddressesWithCode(provider, deduped); + const contracts = useAddressesWithCode(provider, deduped); + const { sourcifySource } = useAppConfigContext(); const metadatas = useMultipleMetadata( undefined, - checked, + contracts, provider?.network.chainId, sourcifySource ); diff --git a/src/sourcify/useSourcify.ts b/src/sourcify/useSourcify.ts index 6b1e5d6..85baaaa 100644 --- a/src/sourcify/useSourcify.ts +++ b/src/sourcify/useSourcify.ts @@ -132,12 +132,10 @@ export const useSingleMetadata = ( }; export const useDedupedAddresses = ( - addresses: (ChecksummedAddress | undefined)[] + addresses: ChecksummedAddress[] ): ChecksummedAddress[] => { return useMemo(() => { - const deduped = new Set( - addresses.filter((a): a is ChecksummedAddress => a !== undefined) - ); + const deduped = new Set(addresses); return [...deduped]; }, [addresses]); }; diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 06e70d5..aa7d393 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -101,12 +101,12 @@ const Details: React.FC<DetailsProps> = ({ } return _addresses; }, [txData]); - const { sourcifySource } = useAppConfigContext(); const deduped = useDedupedAddresses(addresses); - const checked = useAddressesWithCode(provider, deduped); + const contracts = useAddressesWithCode(provider, deduped); + const { sourcifySource } = useAppConfigContext(); const metadatas = useMultipleMetadata( undefined, - checked, + contracts, provider?.network.chainId, sourcifySource ); diff --git a/src/transaction/Logs.tsx b/src/transaction/Logs.tsx index acc2baa..dedb178 100644 --- a/src/transaction/Logs.tsx +++ b/src/transaction/Logs.tsx @@ -35,12 +35,12 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata, resolvedAddresses }) => { [txData] ); const { provider } = useContext(RuntimeContext); - const { sourcifySource } = useAppConfigContext(); const deduped = useDedupedAddresses(logAddresses); - const checked = useAddressesWithCode(provider, deduped); + const contracts = useAddressesWithCode(provider, deduped); + const { sourcifySource } = useAppConfigContext(); const metadatas = useMultipleMetadata( baseMetadatas, - checked, + contracts, provider?.network.chainId, sourcifySource ); From 751ef0c9af900669c8c762c48f2955fedab5e195 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Thu, 2 Dec 2021 16:19:03 -0300 Subject: [PATCH 34/45] Extract higher level hook --- src/address/AddressTransactionResults.tsx | 17 ++------------ src/block/BlockTransactionResults.tsx | 17 ++------------ src/hooks.ts | 27 +++++++++++++++++++++++ src/transaction/Details.tsx | 20 +++-------------- src/transaction/Logs.tsx | 20 ++++------------- 5 files changed, 38 insertions(+), 63 deletions(-) create mode 100644 src/hooks.ts diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx index ce7cf45..07f8e4f 100644 --- a/src/address/AddressTransactionResults.tsx +++ b/src/address/AddressTransactionResults.tsx @@ -8,17 +8,12 @@ import TransactionItem from "../search/TransactionItem"; import UndefinedPageControl from "../search/UndefinedPageControl"; import { useFeeToggler } from "../search/useFeeToggler"; import { SelectionContext, useSelection } from "../useSelection"; -import { - useDedupedAddresses, - useMultipleMetadata, -} from "../sourcify/useSourcify"; -import { useAddressesWithCode } from "../useErigonHooks"; import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { RuntimeContext } from "../useRuntime"; import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses"; -import { useAppConfigContext } from "../useAppConfig"; import { useParams, useSearchParams } from "react-router-dom"; import { ChecksummedAddress } from "../types"; +import { useContractsMetadata } from "../hooks"; type AddressTransactionResultsProps = { address: ChecksummedAddress; @@ -123,15 +118,7 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ } return _addresses; }, [address, page]); - const deduped = useDedupedAddresses(addresses); - const contracts = useAddressesWithCode(provider, deduped); - const { sourcifySource } = useAppConfigContext(); - const metadatas = useMultipleMetadata( - undefined, - contracts, - provider?.network.chainId, - sourcifySource - ); + const metadatas = useContractsMetadata(addresses, provider); return ( <ContentFrame tabs> diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index 44caeb1..fc5bf9f 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -12,12 +12,7 @@ import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses"; import { ChecksummedAddress, ProcessedTransaction } from "../types"; import { PAGE_SIZE } from "../params"; import { useMultipleETHUSDOracle } from "../usePriceOracle"; -import { useAppConfigContext } from "../useAppConfig"; -import { - useDedupedAddresses, - useMultipleMetadata, -} from "../sourcify/useSourcify"; -import { useAddressesWithCode } from "../useErigonHooks"; +import { useContractsMetadata } from "../hooks"; type BlockTransactionResultsProps = { blockTag: BlockTag; @@ -47,15 +42,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({ return page.map((t) => t.to).filter((to): to is string => to !== undefined); }, [page]); - const deduped = useDedupedAddresses(addresses); - const contracts = useAddressesWithCode(provider, deduped); - const { sourcifySource } = useAppConfigContext(); - const metadatas = useMultipleMetadata( - undefined, - contracts, - provider?.network.chainId, - sourcifySource - ); + const metadatas = useContractsMetadata(addresses, provider); return ( <ContentFrame> diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..a81f10b --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,27 @@ +import { JsonRpcProvider } from "@ethersproject/providers"; +import { ChecksummedAddress } from "./types"; +import { + Metadata, + useDedupedAddresses, + useMultipleMetadata, +} from "./sourcify/useSourcify"; +import { useAppConfigContext } from "./useAppConfig"; +import { useAddressesWithCode } from "./useErigonHooks"; + +export const useContractsMetadata = ( + addresses: ChecksummedAddress[], + provider: JsonRpcProvider | undefined, + baseMetadatas?: Record<string, Metadata | null> +) => { + const deduped = useDedupedAddresses(addresses); + const contracts = useAddressesWithCode(provider, deduped); + const { sourcifySource } = useAppConfigContext(); + const metadatas = useMultipleMetadata( + baseMetadatas, + contracts, + provider?.network.chainId, + sourcifySource + ); + + return metadatas; +}; diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index aa7d393..e4daf2f 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -37,16 +37,10 @@ import { use4Bytes, useTransactionDescription, } from "../use4Bytes"; -import { - DevDoc, - useDedupedAddresses, - useMultipleMetadata, - UserDoc, -} from "../sourcify/useSourcify"; -import { useAddressesWithCode } from "../useErigonHooks"; +import { DevDoc, UserDoc } from "../sourcify/useSourcify"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; -import { useAppConfigContext } from "../useAppConfig"; +import { useContractsMetadata } from "../hooks"; type DetailsProps = { txData: TransactionData; @@ -101,15 +95,7 @@ const Details: React.FC<DetailsProps> = ({ } return _addresses; }, [txData]); - const deduped = useDedupedAddresses(addresses); - const contracts = useAddressesWithCode(provider, deduped); - const { sourcifySource } = useAppConfigContext(); - const metadatas = useMultipleMetadata( - undefined, - contracts, - provider?.network.chainId, - sourcifySource - ); + const metadatas = useContractsMetadata(addresses, provider); return ( <ContentFrame tabs> diff --git a/src/transaction/Logs.tsx b/src/transaction/Logs.tsx index dedb178..eba82e1 100644 --- a/src/transaction/Logs.tsx +++ b/src/transaction/Logs.tsx @@ -3,15 +3,10 @@ import { Interface } from "@ethersproject/abi"; import ContentFrame from "../ContentFrame"; import LogEntry from "./LogEntry"; import { TransactionData } from "../types"; -import { useAppConfigContext } from "../useAppConfig"; -import { - Metadata, - useDedupedAddresses, - useMultipleMetadata, -} from "../sourcify/useSourcify"; -import { useAddressesWithCode } from "../useErigonHooks"; +import { Metadata } from "../sourcify/useSourcify"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; +import { useContractsMetadata } from "../hooks"; type LogsProps = { txData: TransactionData; @@ -35,15 +30,8 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata, resolvedAddresses }) => { [txData] ); const { provider } = useContext(RuntimeContext); - const deduped = useDedupedAddresses(logAddresses); - const contracts = useAddressesWithCode(provider, deduped); - const { sourcifySource } = useAppConfigContext(); - const metadatas = useMultipleMetadata( - baseMetadatas, - contracts, - provider?.network.chainId, - sourcifySource - ); + const metadatas = useContractsMetadata(logAddresses, provider, baseMetadatas); + const logDescs = useMemo(() => { if (!txData) { return undefined; From c58d9afb38a713bb5b7d8ca2d56f232811e5e990 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 3 Dec 2021 08:37:09 -0300 Subject: [PATCH 35/45] First working iteration of uniswap V2 LP address resolver --- src/TokenTransferItem.tsx | 6 +- src/api/address-resolver/UniswapV2Resolver.ts | 83 +++++++++++++ src/api/address-resolver/index.ts | 5 + src/components/UniswapV2PairName.tsx | 110 ++++++++++++++++++ 4 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 src/api/address-resolver/UniswapV2Resolver.ts create mode 100644 src/components/UniswapV2PairName.tsx diff --git a/src/TokenTransferItem.tsx b/src/TokenTransferItem.tsx index 59fffbb..9e49ee4 100644 --- a/src/TokenTransferItem.tsx +++ b/src/TokenTransferItem.tsx @@ -31,8 +31,8 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({ <span className="text-gray-500"> <FontAwesomeIcon icon={faCaretRight} size="1x" /> </span> - <div className="grid grid-cols-5 gap-x-1"> - <div className="flex space-x-1"> + <div className="grid grid-cols-7 gap-x-1 w-full"> + <div className="col-span-2 flex space-x-1"> <span className="font-bold">From</span> <TransactionAddress address={t.from} @@ -41,7 +41,7 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({ metadata={metadatas[t.from]} /> </div> - <div className="flex space-x-1"> + <div className="col-span-2 flex space-x-1"> <span className="font-bold">To</span> <TransactionAddress address={t.to} diff --git a/src/api/address-resolver/UniswapV2Resolver.ts b/src/api/address-resolver/UniswapV2Resolver.ts new file mode 100644 index 0000000..7c594bc --- /dev/null +++ b/src/api/address-resolver/UniswapV2Resolver.ts @@ -0,0 +1,83 @@ +import { BaseProvider } from "@ethersproject/providers"; +import { Contract } from "@ethersproject/contracts"; +import { IAddressResolver } from "./address-resolver"; +import { ChecksummedAddress, TokenMeta } from "../../types"; +import { ERCTokenResolver } from "./ERCTokenResolver"; + +const UNISWAP_V2_FACTORY = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"; + +const UNISWAP_V2_FACTORY_ABI = [ + "function getPair(address tokenA, address tokenB) external view returns (address pair)", +]; + +const UNISWAP_V2_PAIR_ABI = [ + "function factory() external view returns (address)", + "function token0() external view returns (address)", + "function token1() external view returns (address)", +]; + +export type UniswapV2TokenMeta = { + address: ChecksummedAddress; +} & TokenMeta; + +export type UniswapV2PairMeta = { + pair: ChecksummedAddress; + token0: UniswapV2TokenMeta; + token1: UniswapV2TokenMeta; +}; + +const ercResolver = new ERCTokenResolver(); + +export class UniswapV2Resolver implements IAddressResolver<UniswapV2PairMeta> { + async resolveAddress( + provider: BaseProvider, + address: string + ): Promise<UniswapV2PairMeta | undefined> { + const pairContract = new Contract(address, UNISWAP_V2_PAIR_ABI, provider); + const factoryContract = new Contract( + UNISWAP_V2_FACTORY, + UNISWAP_V2_FACTORY_ABI, + provider + ); + + try { + // First, probe the factory() function; if it responds with UniswapV2 factory + // address, it may be a pair + const factoryAddress = (await pairContract.factory()) as string; + if (factoryAddress !== UNISWAP_V2_FACTORY) { + return undefined; + } + + // Probe the token0/token1 + const [token0, token1] = await Promise.all([ + pairContract.token0() as string, + pairContract.token1() as string, + ]); + + // Probe the factory to ensure it is a legit pair + const expectedPairAddress = await factoryContract.getPair(token0, token1); + if (expectedPairAddress !== address) { + return undefined; + } + + console.log(`Found pair: ${token0}/${token1}`); + const [meta0, meta1] = await Promise.all([ + ercResolver.resolveAddress(provider, token0), + ercResolver.resolveAddress(provider, token1), + ]); + if (meta0 === undefined || meta1 === undefined) { + return undefined; + } + + return { + pair: address, + token0: { address: token0, ...meta0 }, + token1: { address: token1, ...meta1 }, + }; + } catch (err) { + // Ignore on purpose; this indicates the probe failed and the address + // is not a token + } + return undefined; + } +} diff --git a/src/api/address-resolver/index.ts b/src/api/address-resolver/index.ts index f1514be..ba96106 100644 --- a/src/api/address-resolver/index.ts +++ b/src/api/address-resolver/index.ts @@ -2,12 +2,14 @@ import { BaseProvider } from "@ethersproject/providers"; import { ensRenderer } from "../../components/ENSName"; import { plainStringRenderer } from "../../components/PlainString"; import { tokenRenderer } from "../../components/TokenName"; +import { uniswapV2PairRenderer } from "../../components/UniswapV2PairName"; import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver"; import { CompositeAddressResolver, SelectedResolvedName, } from "./CompositeAddressResolver"; import { ENSAddressResolver } from "./ENSAddressResolver"; +import { UniswapV2Resolver } from "./UniswapV2Resolver"; import { ERCTokenResolver } from "./ERCTokenResolver"; import { HardcodedAddressResolver } from "./HardcodedAddressResolver"; @@ -15,11 +17,13 @@ export type ResolvedAddresses = Record<string, SelectedResolvedName<any>>; // Create and configure the main resolver export const ensResolver = new ENSAddressResolver(); +export const uniswapV2Resolver = new UniswapV2Resolver(); export const ercTokenResolver = new ERCTokenResolver(); export const hardcodedResolver = new HardcodedAddressResolver(); const _mainResolver = new CompositeAddressResolver(); _mainResolver.addResolver(ensResolver); +_mainResolver.addResolver(uniswapV2Resolver); _mainResolver.addResolver(ercTokenResolver); _mainResolver.addResolver(hardcodedResolver); @@ -31,6 +35,7 @@ export const resolverRendererRegistry = new Map< ResolvedAddressRenderer<any> >(); resolverRendererRegistry.set(ensResolver, ensRenderer); +resolverRendererRegistry.set(uniswapV2Resolver, uniswapV2PairRenderer); resolverRendererRegistry.set(ercTokenResolver, tokenRenderer); resolverRendererRegistry.set(hardcodedResolver, plainStringRenderer); diff --git a/src/components/UniswapV2PairName.tsx b/src/components/UniswapV2PairName.tsx new file mode 100644 index 0000000..f4520b7 --- /dev/null +++ b/src/components/UniswapV2PairName.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import TokenLogo from "./TokenLogo"; +import { ResolvedAddressRenderer } from "../api/address-resolver/address-resolver"; +import { + UniswapV2PairMeta, + UniswapV2TokenMeta, +} from "../api/address-resolver/UniswapV2Resolver"; +import { ChecksummedAddress } from "../types"; + +type UniswapV2PairNameProps = { + address: string; + token0: UniswapV2TokenMeta; + token1: UniswapV2TokenMeta; + linkable: boolean; + dontOverrideColors?: boolean; +}; + +const UniswapV2PairName: React.FC<UniswapV2PairNameProps> = ({ + address, + token0, + token1, + linkable, + dontOverrideColors, +}) => { + if (linkable) { + return ( + <NavLink + className={`flex items-baseline space-x-1 font-sans ${ + dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover" + } truncate`} + to={`/address/${address}`} + title={`Uniswap V2 LP (${token0.symbol}/${token1.symbol}): ${address}`} + > + <span>Uniswap V2 LP:</span> + <Content + linkable={true} + address={token0.address} + name={token0.name} + symbol={token0.symbol} + /> + <span>/</span> + <Content + linkable={true} + address={token1.address} + name={token1.name} + symbol={token1.symbol} + /> + </NavLink> + ); + } + + return ( + <div + className="flex items-baseline space-x-1 font-sans text-gray-700 truncate" + title={`Uniswap V2 LP (${token0.symbol}/${token1.symbol}): ${address}`} + > + <span>Uniswap V2 LP:</span> + <Content + linkable={false} + address={token0.address} + name={token0.name} + symbol={token0.symbol} + /> + <span>/</span> + <Content + linkable={false} + address={token1.address} + name={token1.name} + symbol={token1.symbol} + /> + </div> + ); +}; + +type ContentProps = { + linkable: boolean; + address: ChecksummedAddress; + name: string; + symbol: string; +}; + +const Content: React.FC<ContentProps> = ({ + address, + name, + symbol, + linkable, +}) => ( + <> + <div + className={`self-center w-5 h-5 ${linkable ? "" : "filter grayscale"}`} + > + <TokenLogo address={address} name={name} /> + </div> + <span>{symbol}</span> + </> +); + +export const uniswapV2PairRenderer: ResolvedAddressRenderer<UniswapV2PairMeta> = + (address, tokenMeta, linkable, dontOverrideColors) => ( + <UniswapV2PairName + address={address} + token0={tokenMeta.token0} + token1={tokenMeta.token1} + linkable={linkable} + dontOverrideColors={dontOverrideColors} + /> + ); + +export default UniswapV2PairName; From 0233879011e4a4be0454a9e8ae6a4c2d12fdf9ca Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 3 Dec 2021 10:04:15 -0300 Subject: [PATCH 36/45] Remove console.log --- src/api/address-resolver/UniswapV2Resolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/address-resolver/UniswapV2Resolver.ts b/src/api/address-resolver/UniswapV2Resolver.ts index 7c594bc..761237c 100644 --- a/src/api/address-resolver/UniswapV2Resolver.ts +++ b/src/api/address-resolver/UniswapV2Resolver.ts @@ -60,7 +60,6 @@ export class UniswapV2Resolver implements IAddressResolver<UniswapV2PairMeta> { return undefined; } - console.log(`Found pair: ${token0}/${token1}`); const [meta0, meta1] = await Promise.all([ ercResolver.resolveAddress(provider, token0), ercResolver.resolveAddress(provider, token1), From 6a15285a48844c3603caafff5d17c5f0d0f2ae00 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 3 Dec 2021 10:04:42 -0300 Subject: [PATCH 37/45] Add uniswap V1 support --- src/api/address-resolver/UniswapV1Resolver.ts | 60 ++++++++++++ src/api/address-resolver/index.ts | 5 + src/components/UniswapV1ExchangeName.tsx | 93 +++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 src/api/address-resolver/UniswapV1Resolver.ts create mode 100644 src/components/UniswapV1ExchangeName.tsx diff --git a/src/api/address-resolver/UniswapV1Resolver.ts b/src/api/address-resolver/UniswapV1Resolver.ts new file mode 100644 index 0000000..a8cb2e0 --- /dev/null +++ b/src/api/address-resolver/UniswapV1Resolver.ts @@ -0,0 +1,60 @@ +import { BaseProvider } from "@ethersproject/providers"; +import { Contract } from "@ethersproject/contracts"; +import { IAddressResolver } from "./address-resolver"; +import { ChecksummedAddress, TokenMeta } from "../../types"; +import { ERCTokenResolver } from "./ERCTokenResolver"; + +const UNISWAP_V1_FACTORY = "0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95"; + +const UNISWAP_V1_FACTORY_ABI = [ + "function getToken(address exchange) external view returns (address token)", +]; + +const NULL_ADDRESS = "0x0000000000000000000000000000000000000000"; + +export type UniswapV1TokenMeta = { + address: ChecksummedAddress; +} & TokenMeta; + +export type UniswapV1PairMeta = { + exchange: ChecksummedAddress; + token: UniswapV1TokenMeta; +}; + +const ercResolver = new ERCTokenResolver(); + +export class UniswapV1Resolver implements IAddressResolver<UniswapV1PairMeta> { + async resolveAddress( + provider: BaseProvider, + address: string + ): Promise<UniswapV1PairMeta | undefined> { + const factoryContract = new Contract( + UNISWAP_V1_FACTORY, + UNISWAP_V1_FACTORY_ABI, + provider + ); + + try { + // First, probe the getToken() function; if it responds with an UniswapV1 exchange + // address, it is a LP + const token = (await factoryContract.getToken(address)) as string; + if (token === NULL_ADDRESS) { + return undefined; + } + + const metadata = await ercResolver.resolveAddress(provider, token); + if (metadata === undefined) { + return undefined; + } + + return { + exchange: address, + token: { address: token, ...metadata }, + }; + } catch (err) { + // Ignore on purpose; this indicates the probe failed and the address + // is not a token + } + return undefined; + } +} diff --git a/src/api/address-resolver/index.ts b/src/api/address-resolver/index.ts index ba96106..5899231 100644 --- a/src/api/address-resolver/index.ts +++ b/src/api/address-resolver/index.ts @@ -2,6 +2,7 @@ import { BaseProvider } from "@ethersproject/providers"; import { ensRenderer } from "../../components/ENSName"; import { plainStringRenderer } from "../../components/PlainString"; import { tokenRenderer } from "../../components/TokenName"; +import { uniswapV1PairRenderer } from "../../components/UniswapV1ExchangeName"; import { uniswapV2PairRenderer } from "../../components/UniswapV2PairName"; import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver"; import { @@ -9,6 +10,7 @@ import { SelectedResolvedName, } from "./CompositeAddressResolver"; import { ENSAddressResolver } from "./ENSAddressResolver"; +import { UniswapV1Resolver } from "./UniswapV1Resolver"; import { UniswapV2Resolver } from "./UniswapV2Resolver"; import { ERCTokenResolver } from "./ERCTokenResolver"; import { HardcodedAddressResolver } from "./HardcodedAddressResolver"; @@ -18,12 +20,14 @@ export type ResolvedAddresses = Record<string, SelectedResolvedName<any>>; // Create and configure the main resolver export const ensResolver = new ENSAddressResolver(); export const uniswapV2Resolver = new UniswapV2Resolver(); +export const uniswapV1Resolver = new UniswapV1Resolver(); export const ercTokenResolver = new ERCTokenResolver(); export const hardcodedResolver = new HardcodedAddressResolver(); const _mainResolver = new CompositeAddressResolver(); _mainResolver.addResolver(ensResolver); _mainResolver.addResolver(uniswapV2Resolver); +_mainResolver.addResolver(uniswapV1Resolver); _mainResolver.addResolver(ercTokenResolver); _mainResolver.addResolver(hardcodedResolver); @@ -36,6 +40,7 @@ export const resolverRendererRegistry = new Map< >(); resolverRendererRegistry.set(ensResolver, ensRenderer); resolverRendererRegistry.set(uniswapV2Resolver, uniswapV2PairRenderer); +resolverRendererRegistry.set(uniswapV1Resolver, uniswapV1PairRenderer); resolverRendererRegistry.set(ercTokenResolver, tokenRenderer); resolverRendererRegistry.set(hardcodedResolver, plainStringRenderer); diff --git a/src/components/UniswapV1ExchangeName.tsx b/src/components/UniswapV1ExchangeName.tsx new file mode 100644 index 0000000..710c543 --- /dev/null +++ b/src/components/UniswapV1ExchangeName.tsx @@ -0,0 +1,93 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import TokenLogo from "./TokenLogo"; +import { ResolvedAddressRenderer } from "../api/address-resolver/address-resolver"; +import { ChecksummedAddress } from "../types"; +import { + UniswapV1PairMeta, + UniswapV1TokenMeta, +} from "../api/address-resolver/UniswapV1Resolver"; + +type UniswapV1ExchangeNameProps = { + address: string; + token: UniswapV1TokenMeta; + linkable: boolean; + dontOverrideColors?: boolean; +}; + +const UniswapV1ExchangeName: React.FC<UniswapV1ExchangeNameProps> = ({ + address, + token, + linkable, + dontOverrideColors, +}) => { + if (linkable) { + return ( + <NavLink + className={`flex items-baseline space-x-1 font-sans ${ + dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover" + } truncate`} + to={`/address/${address}`} + title={`Uniswap V1 LP (${token.symbol}): ${address}`} + > + <span>Uniswap V1 LP:</span> + <Content + linkable={true} + address={token.address} + name={token.name} + symbol={token.symbol} + /> + </NavLink> + ); + } + + return ( + <div + className="flex items-baseline space-x-1 font-sans text-gray-700 truncate" + title={`Uniswap V1 LP (${token.symbol}): ${address}`} + > + <span>Uniswap V1 LP:</span> + <Content + linkable={false} + address={token.address} + name={token.name} + symbol={token.symbol} + /> + </div> + ); +}; + +type ContentProps = { + linkable: boolean; + address: ChecksummedAddress; + name: string; + symbol: string; +}; + +const Content: React.FC<ContentProps> = ({ + address, + name, + symbol, + linkable, +}) => ( + <> + <div + className={`self-center w-5 h-5 ${linkable ? "" : "filter grayscale"}`} + > + <TokenLogo address={address} name={name} /> + </div> + <span>{symbol}</span> + </> +); + +export const uniswapV1PairRenderer: ResolvedAddressRenderer<UniswapV1PairMeta> = + (address, tokenMeta, linkable, dontOverrideColors) => ( + <UniswapV1ExchangeName + address={address} + token={tokenMeta.token} + linkable={linkable} + dontOverrideColors={dontOverrideColors} + /> + ); + +export default UniswapV1ExchangeName; From e6ec78daee2c7c40ed23606e5e3a7bd148949725 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 3 Dec 2021 15:46:50 -0300 Subject: [PATCH 38/45] Add all Uniswap V3 known deployments from https://github.com/Uniswap/v3-periphery/blob/main/deploys.md --- src/api/address-resolver/hardcoded-addresses/1.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/api/address-resolver/hardcoded-addresses/1.json b/src/api/address-resolver/hardcoded-addresses/1.json index c05f60a..0164374 100644 --- a/src/api/address-resolver/hardcoded-addresses/1.json +++ b/src/api/address-resolver/hardcoded-addresses/1.json @@ -14,7 +14,17 @@ "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f": "Uniswap V2: Factory", "0xf164fC0Ec4E93095b804a4795bBe1e041497b92a": "Uniswap V2: Router 1", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D": "Uniswap V2: Router 2", - "0x1F98431c8aD98523631AE4a59f267346ea31F984": "Uniswap V3: Router", + "0x1F98431c8aD98523631AE4a59f267346ea31F984": "Uniswap V3: Factory", + "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696": "Uniswap V3: Multicall2", + "0xB753548F6E010e7e680BA186F9Ca1BdAB2E90cf2": "Uniswap V3: ProxyAdmin", + "0xbfd8137f7d1516D3ea5cA83523914859ec47F573": "Uniswap V3: TickLens", + "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6": "Uniswap V3: Quoter", + "0xE592427A0AEce92De3Edee1F18E0157C05861564": "Uniswap V3: Router", + "0x42B24A95702b9986e82d421cC3568932790A48Ec": "Uniswap V3: NFT Descriptor", + "0x91ae842A5Ffd8d12023116943e72A606179294f3": "Uniswap V3: NFT Position Descriptor", + "0xEe6A57eC80ea46401049E92587E52f5Ec1c24785": "Uniswap V3: Transparent Upgradeable Proxy", + "0xC36442b4a4522E871399CD717aBDD847Ab11FE88": "Uniswap V3: Nonfungible Position Manager", + "0xA5644E29708357803b5A882D272c41cC0dF92B34": "Uniswap V3: V3 Migrator", "0x722122dF12D4e14e13Ac3b6895a86e84145b6967": "Tornado Cash: Proxy", "0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc": "Tornado Cash: 0.1 ETH", "0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936": "Tornado Cash: 1 ETH", From 14186ad151d86a0dd929c2a4e225aa2f95e33e2a Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Fri, 3 Dec 2021 16:14:09 -0300 Subject: [PATCH 39/45] Add uniswap V3 resolver --- src/api/address-resolver/UniswapV3Resolver.ts | 90 +++++++++++++ src/api/address-resolver/index.ts | 9 +- src/components/UniswapV3PoolName.tsx | 119 ++++++++++++++++++ 3 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 src/api/address-resolver/UniswapV3Resolver.ts create mode 100644 src/components/UniswapV3PoolName.tsx diff --git a/src/api/address-resolver/UniswapV3Resolver.ts b/src/api/address-resolver/UniswapV3Resolver.ts new file mode 100644 index 0000000..835e823 --- /dev/null +++ b/src/api/address-resolver/UniswapV3Resolver.ts @@ -0,0 +1,90 @@ +import { BaseProvider } from "@ethersproject/providers"; +import { Contract } from "@ethersproject/contracts"; +import { IAddressResolver } from "./address-resolver"; +import { ChecksummedAddress, TokenMeta } from "../../types"; +import { ERCTokenResolver } from "./ERCTokenResolver"; + +const UNISWAP_V3_FACTORY = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; + +const UNISWAP_V3_FACTORY_ABI = [ + "function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)", +]; + +const UNISWAP_V3_PAIR_ABI = [ + "function factory() external view returns (address)", + "function token0() external view returns (address)", + "function token1() external view returns (address)", + "function fee() external view returns (uint24)", +]; + +export type UniswapV3TokenMeta = { + address: ChecksummedAddress; +} & TokenMeta; + +export type UniswapV3PairMeta = { + pair: ChecksummedAddress; + token0: UniswapV3TokenMeta; + token1: UniswapV3TokenMeta; + fee: number; +}; + +const ercResolver = new ERCTokenResolver(); + +export class UniswapV3Resolver implements IAddressResolver<UniswapV3PairMeta> { + async resolveAddress( + provider: BaseProvider, + address: string + ): Promise<UniswapV3PairMeta | undefined> { + const poolContract = new Contract(address, UNISWAP_V3_PAIR_ABI, provider); + const factoryContract = new Contract( + UNISWAP_V3_FACTORY, + UNISWAP_V3_FACTORY_ABI, + provider + ); + + try { + // First, probe the factory() function; if it responds with UniswapV2 factory + // address, it may be a pair + const factoryAddress = (await poolContract.factory()) as string; + if (factoryAddress !== UNISWAP_V3_FACTORY) { + return undefined; + } + + // Probe the token0/token1/fee + const [token0, token1, fee] = await Promise.all([ + poolContract.token0() as string, + poolContract.token1() as string, + poolContract.fee() as number, + ]); + + // Probe the factory to ensure it is a legit pair + const expectedPoolAddress = await factoryContract.getPool( + token0, + token1, + fee + ); + if (expectedPoolAddress !== address) { + return undefined; + } + + const [meta0, meta1] = await Promise.all([ + ercResolver.resolveAddress(provider, token0), + ercResolver.resolveAddress(provider, token1), + ]); + if (meta0 === undefined || meta1 === undefined) { + return undefined; + } + + return { + pair: address, + token0: { address: token0, ...meta0 }, + token1: { address: token1, ...meta1 }, + fee, + }; + } catch (err) { + // Ignore on purpose; this indicates the probe failed and the address + // is not a token + } + return undefined; + } +} diff --git a/src/api/address-resolver/index.ts b/src/api/address-resolver/index.ts index 5899231..c6a1687 100644 --- a/src/api/address-resolver/index.ts +++ b/src/api/address-resolver/index.ts @@ -4,6 +4,7 @@ import { plainStringRenderer } from "../../components/PlainString"; import { tokenRenderer } from "../../components/TokenName"; import { uniswapV1PairRenderer } from "../../components/UniswapV1ExchangeName"; import { uniswapV2PairRenderer } from "../../components/UniswapV2PairName"; +import { uniswapV3PairRenderer } from "../../components/UniswapV3PoolName"; import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver"; import { CompositeAddressResolver, @@ -12,6 +13,7 @@ import { import { ENSAddressResolver } from "./ENSAddressResolver"; import { UniswapV1Resolver } from "./UniswapV1Resolver"; import { UniswapV2Resolver } from "./UniswapV2Resolver"; +import { UniswapV3Resolver } from "./UniswapV3Resolver"; import { ERCTokenResolver } from "./ERCTokenResolver"; import { HardcodedAddressResolver } from "./HardcodedAddressResolver"; @@ -19,13 +21,15 @@ export type ResolvedAddresses = Record<string, SelectedResolvedName<any>>; // Create and configure the main resolver export const ensResolver = new ENSAddressResolver(); -export const uniswapV2Resolver = new UniswapV2Resolver(); export const uniswapV1Resolver = new UniswapV1Resolver(); +export const uniswapV2Resolver = new UniswapV2Resolver(); +export const uniswapV3Resolver = new UniswapV3Resolver(); export const ercTokenResolver = new ERCTokenResolver(); export const hardcodedResolver = new HardcodedAddressResolver(); const _mainResolver = new CompositeAddressResolver(); _mainResolver.addResolver(ensResolver); +_mainResolver.addResolver(uniswapV3Resolver); _mainResolver.addResolver(uniswapV2Resolver); _mainResolver.addResolver(uniswapV1Resolver); _mainResolver.addResolver(ercTokenResolver); @@ -39,8 +43,9 @@ export const resolverRendererRegistry = new Map< ResolvedAddressRenderer<any> >(); resolverRendererRegistry.set(ensResolver, ensRenderer); -resolverRendererRegistry.set(uniswapV2Resolver, uniswapV2PairRenderer); resolverRendererRegistry.set(uniswapV1Resolver, uniswapV1PairRenderer); +resolverRendererRegistry.set(uniswapV2Resolver, uniswapV2PairRenderer); +resolverRendererRegistry.set(uniswapV3Resolver, uniswapV3PairRenderer); resolverRendererRegistry.set(ercTokenResolver, tokenRenderer); resolverRendererRegistry.set(hardcodedResolver, plainStringRenderer); diff --git a/src/components/UniswapV3PoolName.tsx b/src/components/UniswapV3PoolName.tsx new file mode 100644 index 0000000..a21b186 --- /dev/null +++ b/src/components/UniswapV3PoolName.tsx @@ -0,0 +1,119 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import TokenLogo from "./TokenLogo"; +import { ResolvedAddressRenderer } from "../api/address-resolver/address-resolver"; +import { + UniswapV3PairMeta, + UniswapV3TokenMeta, +} from "../api/address-resolver/UniswapV3Resolver"; +import { ChecksummedAddress } from "../types"; + +type UniswapV3PoolNameProps = { + address: string; + token0: UniswapV3TokenMeta; + token1: UniswapV3TokenMeta; + fee: number; + linkable: boolean; + dontOverrideColors?: boolean; +}; + +const UniswapV3PairName: React.FC<UniswapV3PoolNameProps> = ({ + address, + token0, + token1, + fee, + linkable, + dontOverrideColors, +}) => { + if (linkable) { + return ( + <NavLink + className={`flex items-baseline space-x-1 font-sans ${ + dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover" + } truncate`} + to={`/address/${address}`} + title={`Uniswap V3 LP (${token0.symbol}/${token1.symbol}/${ + fee / 10000 + }%): ${address}`} + > + <span>Uniswap V3 LP:</span> + <Content + linkable={true} + address={token0.address} + name={token0.name} + symbol={token0.symbol} + /> + <span>/</span> + <Content + linkable={true} + address={token1.address} + name={token1.name} + symbol={token1.symbol} + /> + <span>/ {fee / 10000}%</span> + </NavLink> + ); + } + + return ( + <div + className="flex items-baseline space-x-1 font-sans text-gray-700 truncate" + title={`Uniswap V3 LP (${token0.symbol}/${token1.symbol}/${ + fee / 10000 + }%): ${address}`} + > + <span>Uniswap V3 LP:</span> + <Content + linkable={false} + address={token0.address} + name={token0.name} + symbol={token0.symbol} + /> + <span>/</span> + <Content + linkable={false} + address={token1.address} + name={token1.name} + symbol={token1.symbol} + /> + <span>/ {fee / 10000}%</span> + </div> + ); +}; + +type ContentProps = { + linkable: boolean; + address: ChecksummedAddress; + name: string; + symbol: string; +}; + +const Content: React.FC<ContentProps> = ({ + address, + name, + symbol, + linkable, +}) => ( + <> + <div + className={`self-center w-5 h-5 ${linkable ? "" : "filter grayscale"}`} + > + <TokenLogo address={address} name={name} /> + </div> + <span>{symbol}</span> + </> +); + +export const uniswapV3PairRenderer: ResolvedAddressRenderer<UniswapV3PairMeta> = + (address, tokenMeta, linkable, dontOverrideColors) => ( + <UniswapV3PairName + address={address} + token0={tokenMeta.token0} + token1={tokenMeta.token1} + fee={tokenMeta.fee} + linkable={linkable} + dontOverrideColors={dontOverrideColors} + /> + ); + +export default UniswapV3PairName; From 3a6f050061492f7fa09c1672786de1c0e978006c Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Sat, 4 Dec 2021 16:28:40 -0300 Subject: [PATCH 40/45] Dont display the contract tab if we know the address is an EOA --- src/AddressTransactions.tsx | 81 +++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index 17c8888..9adcd86 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useContext, useCallback } from "react"; +import React, { useEffect, useContext, useCallback, useMemo } from "react"; import { useParams, useNavigate, @@ -21,8 +21,9 @@ import Contracts from "./address/Contracts"; import { RuntimeContext } from "./useRuntime"; import { useAppConfigContext } from "./useAppConfig"; import { useAddressOrENSFromURL } from "./useResolvedAddresses"; -import { useSingleMetadata } from "./sourcify/useSourcify"; +import { useMultipleMetadata } from "./sourcify/useSourcify"; import { ChecksummedAddress } from "./types"; +import { useAddressesWithCode } from "./useErigonHooks"; const AddressTransactions: React.FC = () => { const { provider } = useContext(RuntimeContext); @@ -58,11 +59,24 @@ const AddressTransactions: React.FC = () => { }, [addressOrName, checksummedAddress, isENS]); const { sourcifySource } = useAppConfigContext(); - const addressMetadata = useSingleMetadata( - checksummedAddress, + const checksummedAddressAsArray = useMemo( + () => (checksummedAddress !== undefined ? [checksummedAddress] : []), + [checksummedAddress] + ); + const contractAddresses = useAddressesWithCode( + provider, + checksummedAddressAsArray + ); + const metadatas = useMultipleMetadata( + undefined, + contractAddresses, provider?.network.chainId, sourcifySource ); + const addressMetadata = + checksummedAddress !== undefined + ? metadatas[checksummedAddress] + : undefined; return ( <StandardFrame> @@ -97,31 +111,33 @@ const AddressTransactions: React.FC = () => { <NavTab href={`/address/${checksummedAddress}`}> Overview </NavTab> - <NavTab href={`/address/${checksummedAddress}/contract`}> - <span - className={`flex items-baseline space-x-2 ${ - addressMetadata === undefined ? "italic opacity-50" : "" - }`} - > - <span>Contract</span> - {addressMetadata === undefined ? ( - <span className="self-center"> - <FontAwesomeIcon - className="animate-spin" - icon={faCircleNotch} - /> - </span> - ) : addressMetadata === null ? ( - <span className="self-center text-red-500"> - <FontAwesomeIcon icon={faQuestionCircle} /> - </span> - ) : ( - <span className="self-center"> - <SourcifyLogo /> - </span> - )} - </span> - </NavTab> + {(contractAddresses?.length ?? 0) > 0 && ( + <NavTab href={`/address/${checksummedAddress}/contract`}> + <span + className={`flex items-baseline space-x-2 ${ + addressMetadata === undefined ? "italic opacity-50" : "" + }`} + > + <span>Contract</span> + {addressMetadata === undefined ? ( + <span className="self-center"> + <FontAwesomeIcon + className="animate-spin" + icon={faCircleNotch} + /> + </span> + ) : addressMetadata === null ? ( + <span className="self-center text-red-500"> + <FontAwesomeIcon icon={faQuestionCircle} /> + </span> + ) : ( + <span className="self-center"> + <SourcifyLogo /> + </span> + )} + </span> + </NavTab> + )} </Tab.List> <Tab.Panels> <Routes> @@ -142,7 +158,12 @@ const AddressTransactions: React.FC = () => { element={ <Contracts checksummedAddress={checksummedAddress} - rawMetadata={addressMetadata} + rawMetadata={ + contractAddresses !== undefined && + contractAddresses.length === 0 + ? null + : addressMetadata + } /> } /> From 6f6dd1877a8fd483da846c65d9d6418de7d06db3 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Sat, 4 Dec 2021 16:29:40 -0300 Subject: [PATCH 41/45] Remove dead code --- src/sourcify/useSourcify.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/sourcify/useSourcify.ts b/src/sourcify/useSourcify.ts index 85baaaa..e210ea0 100644 --- a/src/sourcify/useSourcify.ts +++ b/src/sourcify/useSourcify.ts @@ -121,16 +121,6 @@ export const useSourcify = ( return rawMetadata; }; -export const useSingleMetadata = ( - address: ChecksummedAddress | undefined, - chainId: number | undefined, - source: SourcifySource -) => { - const addresses = useMemo(() => (address ? [address] : []), [address]); - const metadatas = useMultipleMetadata(undefined, addresses, chainId, source); - return address !== undefined ? metadatas[address] : undefined; -}; - export const useDedupedAddresses = ( addresses: ChecksummedAddress[] ): ChecksummedAddress[] => { From 7a9dd3c519d472be033a6ab5b6f68faeabd6100b Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Sat, 4 Dec 2021 16:33:07 -0300 Subject: [PATCH 42/45] Move hook --- src/hooks.ts | 16 +++++++++++----- src/sourcify/useSourcify.ts | 9 --------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/hooks.ts b/src/hooks.ts index a81f10b..e924314 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,13 +1,19 @@ +import { useMemo } from "react"; import { JsonRpcProvider } from "@ethersproject/providers"; import { ChecksummedAddress } from "./types"; -import { - Metadata, - useDedupedAddresses, - useMultipleMetadata, -} from "./sourcify/useSourcify"; +import { Metadata, useMultipleMetadata } from "./sourcify/useSourcify"; import { useAppConfigContext } from "./useAppConfig"; import { useAddressesWithCode } from "./useErigonHooks"; +export const useDedupedAddresses = ( + addresses: ChecksummedAddress[] +): ChecksummedAddress[] => { + return useMemo(() => { + const deduped = new Set(addresses); + return [...deduped]; + }, [addresses]); +}; + export const useContractsMetadata = ( addresses: ChecksummedAddress[], provider: JsonRpcProvider | undefined, diff --git a/src/sourcify/useSourcify.ts b/src/sourcify/useSourcify.ts index e210ea0..f5fae67 100644 --- a/src/sourcify/useSourcify.ts +++ b/src/sourcify/useSourcify.ts @@ -121,15 +121,6 @@ export const useSourcify = ( return rawMetadata; }; -export const useDedupedAddresses = ( - addresses: ChecksummedAddress[] -): ChecksummedAddress[] => { - return useMemo(() => { - const deduped = new Set(addresses); - return [...deduped]; - }, [addresses]); -}; - export const useMultipleMetadata = ( baseMetadatas: Record<string, Metadata | null> | undefined, addresses: ChecksummedAddress[] | undefined, From 658e530ed14260c3f4be820d52a716c44e8e86cc Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Sat, 4 Dec 2021 16:54:27 -0300 Subject: [PATCH 43/45] Fix tabs when search by ens name; fix contract creation on block txs results --- src/AddressTransactions.tsx | 6 ++---- src/block/BlockTransactionResults.tsx | 2 +- src/search/TransactionItem.tsx | 5 ++++- src/search/search.ts | 2 +- src/types.ts | 2 +- src/useErigonHooks.ts | 6 ++++-- src/useResolvedAddresses.ts | 5 +++++ 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index 9adcd86..d7222c0 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -108,11 +108,9 @@ const AddressTransactions: React.FC = () => { </StandardSubtitle> <Tab.Group> <Tab.List className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white"> - <NavTab href={`/address/${checksummedAddress}`}> - Overview - </NavTab> + <NavTab href={`/address/${addressOrName}`}>Overview</NavTab> {(contractAddresses?.length ?? 0) > 0 && ( - <NavTab href={`/address/${checksummedAddress}/contract`}> + <NavTab href={`/address/${addressOrName}/contract`}> <span className={`flex items-baseline space-x-2 ${ addressMetadata === undefined ? "italic opacity-50" : "" diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index fc5bf9f..3931c46 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -40,7 +40,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({ return []; } - return page.map((t) => t.to).filter((to): to is string => to !== undefined); + return page.map((t) => t.to).filter((to): to is string => to !== null); }, [page]); const metadatas = useContractsMetadata(addresses, provider); diff --git a/src/search/TransactionItem.tsx b/src/search/TransactionItem.tsx index 929d2cd..b6cfe1e 100644 --- a/src/search/TransactionItem.tsx +++ b/src/search/TransactionItem.tsx @@ -99,7 +99,10 @@ const TransactionItem: React.FC<TransactionItemProps> = ({ /> </span> </span> - <span className="col-span-2 flex items-baseline" title={tx.to}> + <span + className="col-span-2 flex items-baseline" + title={tx.to ?? tx.createdContractAddress} + > <span className="truncate"> {tx.to ? ( <AddressHighlighter address={tx.to}> diff --git a/src/search/search.ts b/src/search/search.ts index 25bda65..57a5958 100644 --- a/src/search/search.ts +++ b/src/search/search.ts @@ -52,7 +52,7 @@ export class SearchController { idx: _receipt.transactionIndex, hash: t.hash, from: t.from, - to: t.to, + to: t.to ?? null, createdContractAddress: _receipt.contractAddress, value: t.value, fee: _receipt.gasUsed.mul(t.gasPrice!), diff --git a/src/types.ts b/src/types.ts index fde36bb..adedc86 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,7 +16,7 @@ export type ProcessedTransaction = { idx: number; hash: string; from?: string; - to?: string; + to: string | null; createdContractAddress?: string; internalMinerInteraction?: boolean; value: BigNumber; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index f722f03..a1e45f1 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -103,7 +103,7 @@ export const useBlockTransactions = ( idx: i, hash: t.hash, from: t.from, - to: t.to, + to: t.to ?? null, createdContractAddress: _receipts[i].contractAddress, value: t.value, fee: @@ -457,8 +457,10 @@ export const useAddressesWithCode = ( const [results, setResults] = useState<ChecksummedAddress[] | undefined>(); useEffect(() => { + // Reset + setResults(undefined); + if (provider === undefined) { - setResults(undefined); return; } diff --git a/src/useResolvedAddresses.ts b/src/useResolvedAddresses.ts index dc0df3a..9c92310 100644 --- a/src/useResolvedAddresses.ts +++ b/src/useResolvedAddresses.ts @@ -27,6 +27,11 @@ export const useAddressOrENSFromURL = ( // If it looks like it is an ENS name, try to resolve it useEffect(() => { + // Reset + setENS(false); + setError(false); + setChecksummedAddress(undefined); + // TODO: handle and offer fallback to bad checksummed addresses if (isAddress(addressOrName)) { // Normalize to checksummed address From 9fb6e1877ddb5a0194e0112c55857658219ba2fe Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Sat, 4 Dec 2021 16:59:29 -0300 Subject: [PATCH 44/45] Fix address resolution for created contracts on results page --- src/useResolvedAddresses.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/useResolvedAddresses.ts b/src/useResolvedAddresses.ts index 9c92310..9250232 100644 --- a/src/useResolvedAddresses.ts +++ b/src/useResolvedAddresses.ts @@ -86,6 +86,9 @@ export const pageCollector = if (tx.to) { uniqueAddresses.add(tx.to); } + if (tx.createdContractAddress) { + uniqueAddresses.add(tx.createdContractAddress); + } } return Array.from(uniqueAddresses); From 0439be7ac9086f0cfa912fc0bcf70bacaf2be489 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda <wmitsuda@gmail.com> Date: Sat, 4 Dec 2021 17:22:17 -0300 Subject: [PATCH 45/45] Fix display Sourcify verification status for created contracts on block tx results --- src/address/AddressTransactionResults.tsx | 3 +++ src/block/BlockTransactionResults.tsx | 11 ++++++++++- src/useErigonHooks.ts | 21 +++++++++++++-------- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx index 07f8e4f..f1a373e 100644 --- a/src/address/AddressTransactionResults.tsx +++ b/src/address/AddressTransactionResults.tsx @@ -114,6 +114,9 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({ if (t.to) { _addresses.push(t.to); } + if (t.createdContractAddress) { + _addresses.push(t.createdContractAddress); + } } } return _addresses; diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index 3931c46..01d867c 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -40,7 +40,16 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({ return []; } - return page.map((t) => t.to).filter((to): to is string => to !== null); + const _addresses: ChecksummedAddress[] = []; + for (const t of page) { + if (t.to) { + _addresses.push(t.to); + } + if (t.createdContractAddress) { + _addresses.push(t.createdContractAddress); + } + } + return _addresses; }, [page]); const metadatas = useContractsMetadata(addresses, provider); diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index a1e45f1..30effa2 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -95,8 +95,13 @@ export const useBlockTransactions = ( const _receipts = result.receipts; const rawTxs = _block.transactions - .map( - (t, i): ProcessedTransaction => ({ + .map((t, i): ProcessedTransaction => { + const _rawReceipt = _receipts[i]; + // Empty logs on purpose because of ethers formatter requires it + _rawReceipt.logs = []; + const _receipt = provider.formatter.receipt(_rawReceipt); + + return { blockNumber: blockNumber, timestamp: _block.timestamp, miner: _block.miner, @@ -104,24 +109,24 @@ export const useBlockTransactions = ( hash: t.hash, from: t.from, to: t.to ?? null, - createdContractAddress: _receipts[i].contractAddress, + createdContractAddress: _receipt.contractAddress, value: t.value, fee: t.type !== 2 ? provider.formatter - .bigNumber(_receipts[i].gasUsed) + .bigNumber(_receipt.gasUsed) .mul(t.gasPrice!) : provider.formatter - .bigNumber(_receipts[i].gasUsed) + .bigNumber(_receipt.gasUsed) .mul(t.maxPriorityFeePerGas!.add(_block.baseFeePerGas!)), gasPrice: t.type !== 2 ? t.gasPrice! : t.maxPriorityFeePerGas!.add(_block.baseFeePerGas!), data: t.data, - status: provider.formatter.number(_receipts[i].status), - }) - ) + status: provider.formatter.number(_receipt.status), + }; + }) .reverse(); setTxs(rawTxs); setTotalTxs(result.fullblock.transactionCount);