diff --git a/4bytes b/4bytes index 7996531..1cc7e25 160000 --- a/4bytes +++ b/4bytes @@ -1 +1 @@ -Subproject commit 79965318da56eed67366cf399ee5661b51af49cb +Subproject commit 1cc7e25c840ae9d985c12768b0cbd0ece3fc5400 diff --git a/docs/other-ways-to-run-otterscan.md b/docs/other-ways-to-run-otterscan.md index cddb843..07e3358 100644 --- a/docs/other-ways-to-run-otterscan.md +++ b/docs/other-ways-to-run-otterscan.md @@ -13,7 +13,7 @@ The entire build process will take place inside the docker multi-stage build. Clone Otterscan repo and its submodules. Checkout the tag corresponding to your Erigon + Otterscan patches. It uses the same version tag from Erigon + Otterscan repo, i.e., if you built the `v2021.07.01-otterscan`, you should build the `v2021.07.01-otterscan` of Otterscan. ``` -git clone --recurse-submodules git@github.com:wmitsuda/otterscan.git +git clone --recurse-submodules https://github.com/wmitsuda/otterscan.git cd otterscan git checkout DOCKER_BUILDKIT=1 docker build -t otterscan -f Dockerfile . @@ -63,4 +63,4 @@ npm install npm start ``` -Otterscan should now be running at `http://localhost:3000/`. \ No newline at end of file +Otterscan should now be running at `http://localhost:3000/`. diff --git a/package-lock.json b/package-lock.json index ca530a0..21ccd93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,12 +5,14 @@ "requires": true, "packages": { "": { + "name": "otterscan", "version": "0.1.0", "license": "MIT", "dependencies": { + "@blackbox-vision/react-qr-reader": "^5.0.0", "@chainlink/contracts": "^0.2.1", "@craco/craco": "^6.2.0", - "@fontsource/fira-code": "^4.5.0", + "@fontsource/fira-code": "^4.5.1", "@fontsource/roboto": "^4.5.0", "@fontsource/roboto-mono": "^4.5.0", "@fontsource/space-grotesk": "^4.5.0", @@ -19,17 +21,17 @@ "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.15", - "@headlessui/react": "^1.4.0", + "@headlessui/react": "^1.4.1", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.24", "@types/node": "^14.17.5", - "@types/react": "^17.0.17", + "@types/react": "^17.0.19", "@types/react-blockies": "^1.4.1", "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.1.8", - "chart.js": "^3.5.0", + "chart.js": "^3.5.1", "ethers": "^5.4.1", "query-string": "^7.0.1", "react": "^17.0.2", @@ -38,10 +40,10 @@ "react-dom": "^17.0.2", "react-error-boundary": "^3.1.3", "react-image": "^4.0.3", - "react-router-dom": "^5.2.0", + "react-router-dom": "^5.2.1", "react-scripts": "4.0.3", "serve": "^12.0.0", - "typescript": "^4.3.5", + "typescript": "^4.4.2", "use-keyboard-shortcut": "^1.0.6", "web-vitals": "^1.0.1" }, @@ -1208,6 +1210,19 @@ "version": "0.2.3", "license": "MIT" }, + "node_modules/@blackbox-vision/react-qr-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@blackbox-vision/react-qr-reader/-/react-qr-reader-5.0.0.tgz", + "integrity": "sha512-VLNKwwJTv4UX1inUNgt2aGC2yIhKBYptW9EOhn7Nq//WzjD5KvHG7WR48HTzGUZ2s/EA0XlxZSfKOarHV1Vb/A==", + "dependencies": { + "@zxing/browser": "0.0.7", + "@zxing/library": "^0.18.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } + }, "node_modules/@chainlink/contracts": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz", @@ -1993,9 +2008,9 @@ } }, "node_modules/@fontsource/fira-code": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.0.tgz", - "integrity": "sha512-fxRV3qt0eJaIXZvICXZMhXVR0lSyxZTC0cnM+1Ma/1JShGrIjCQ3yJ0W05rwaEoF3cAbpU2lKMrXfE7Of/zpIA==" + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.1.tgz", + "integrity": "sha512-8KTCsfs5m3UgICpHLglIKAS7vc2FFOu7/vvpWcE/42SWbh+9X8EJbEyJp6W96kU5iDVlAlUv4Cqc36Z9XUpLmA==" }, "node_modules/@fontsource/roboto": { "version": "4.5.0", @@ -2111,9 +2126,9 @@ } }, "node_modules/@headlessui/react": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.0.tgz", - "integrity": "sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.1.tgz", + "integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==", "engines": { "node": ">=10" }, @@ -3063,9 +3078,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "17.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.17.tgz", - "integrity": "sha512-nrfi7I13cAmrd0wje8czYpf5SFbryczCtPzFc6ijqvdjKcyA3tCvGxwchOUlxb2ucBPuJ9Y3oUqKrRqZvrz0lw==", + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.19.tgz", + "integrity": "sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3511,6 +3526,37 @@ "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==" }, + "node_modules/@zxing/browser": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.0.7.tgz", + "integrity": "sha512-AepzMgDnD6EjxewqmXpHJsi4S3Gw9ilZJLIbTf6fWuWySEcHBodnGu3p7FWlgq1Sd5QyfPhTum5z3CBkkhMVng==", + "optionalDependencies": { + "@zxing/text-encoding": "^0.9.0" + }, + "peerDependencies": { + "@zxing/library": "^0.18.3" + } + }, + "node_modules/@zxing/library": { + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.18.6.tgz", + "integrity": "sha512-bulZ9JHoLFd9W36pi+7e7DnEYNJhljYjZ1UTsKPOoLMU3qtC+REHITeCRNx40zTRJZx18W5TBRXt5pq2Uopjsw==", + "dependencies": { + "ts-custom-error": "^3.0.0" + }, + "engines": { + "node": ">= 10.4.0" + }, + "optionalDependencies": { + "@zxing/text-encoding": "~0.9.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "node_modules/abab": { "version": "2.0.5", "license": "BSD-3-Clause" @@ -5530,9 +5576,9 @@ } }, "node_modules/chart.js": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.0.tgz", - "integrity": "sha512-J1a4EAb1Gi/KbhwDRmoovHTRuqT8qdF0kZ4XgwxpGethJHUdDrkqyPYwke0a+BuvSeUxPf8Cos6AX2AB8H8GLA==" + "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==" }, "node_modules/check-types": { "version": "11.1.2", @@ -14395,11 +14441,11 @@ } }, "node_modules/react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", + "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", "dependencies": { - "@babel/runtime": "^7.1.2", + "@babel/runtime": "^7.12.13", "history": "^4.9.0", "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", @@ -14415,15 +14461,15 @@ } }, "node_modules/react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.1.tgz", + "integrity": "sha512-xhFFkBGVcIVPbWM2KEYzED+nuHQPmulVa7sqIs3ESxzYd1pYg8N8rxPnQ4T2o1zu/2QeDUWcaqST131SO1LR3w==", "dependencies": { - "@babel/runtime": "^7.1.2", + "@babel/runtime": "^7.12.13", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.2.0", + "react-router": "5.2.1", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" }, @@ -17824,6 +17870,14 @@ "version": "1.0.1", "license": "MIT" }, + "node_modules/ts-custom-error": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.2.0.tgz", + "integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ts-pnp": { "version": "1.2.0", "license": "MIT", @@ -17936,9 +17990,9 @@ } }, "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20239,6 +20293,15 @@ "@bcoe/v8-coverage": { "version": "0.2.3" }, + "@blackbox-vision/react-qr-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@blackbox-vision/react-qr-reader/-/react-qr-reader-5.0.0.tgz", + "integrity": "sha512-VLNKwwJTv4UX1inUNgt2aGC2yIhKBYptW9EOhn7Nq//WzjD5KvHG7WR48HTzGUZ2s/EA0XlxZSfKOarHV1Vb/A==", + "requires": { + "@zxing/browser": "0.0.7", + "@zxing/library": "^0.18.3" + } + }, "@chainlink/contracts": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz", @@ -20678,9 +20741,9 @@ } }, "@fontsource/fira-code": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.0.tgz", - "integrity": "sha512-fxRV3qt0eJaIXZvICXZMhXVR0lSyxZTC0cnM+1Ma/1JShGrIjCQ3yJ0W05rwaEoF3cAbpU2lKMrXfE7Of/zpIA==" + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.1.tgz", + "integrity": "sha512-8KTCsfs5m3UgICpHLglIKAS7vc2FFOu7/vvpWcE/42SWbh+9X8EJbEyJp6W96kU5iDVlAlUv4Cqc36Z9XUpLmA==" }, "@fontsource/roboto": { "version": "4.5.0", @@ -20767,9 +20830,9 @@ } }, "@headlessui/react": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.0.tgz", - "integrity": "sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.1.tgz", + "integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==", "requires": {} }, "@istanbuljs/load-nyc-config": { @@ -21377,9 +21440,9 @@ "version": "1.5.4" }, "@types/react": { - "version": "17.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.17.tgz", - "integrity": "sha512-nrfi7I13cAmrd0wje8czYpf5SFbryczCtPzFc6ijqvdjKcyA3tCvGxwchOUlxb2ucBPuJ9Y3oUqKrRqZvrz0lw==", + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.19.tgz", + "integrity": "sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -21699,6 +21762,29 @@ "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==" }, + "@zxing/browser": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.0.7.tgz", + "integrity": "sha512-AepzMgDnD6EjxewqmXpHJsi4S3Gw9ilZJLIbTf6fWuWySEcHBodnGu3p7FWlgq1Sd5QyfPhTum5z3CBkkhMVng==", + "requires": { + "@zxing/text-encoding": "^0.9.0" + } + }, + "@zxing/library": { + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.18.6.tgz", + "integrity": "sha512-bulZ9JHoLFd9W36pi+7e7DnEYNJhljYjZ1UTsKPOoLMU3qtC+REHITeCRNx40zTRJZx18W5TBRXt5pq2Uopjsw==", + "requires": { + "@zxing/text-encoding": "~0.9.0", + "ts-custom-error": "^3.0.0" + } + }, + "@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "abab": { "version": "2.0.5" }, @@ -23101,9 +23187,9 @@ "version": "1.0.2" }, "chart.js": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.0.tgz", - "integrity": "sha512-J1a4EAb1Gi/KbhwDRmoovHTRuqT8qdF0kZ4XgwxpGethJHUdDrkqyPYwke0a+BuvSeUxPf8Cos6AX2AB8H8GLA==" + "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==" }, "check-types": { "version": "11.1.2" @@ -28992,11 +29078,11 @@ "version": "0.8.3" }, "react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", + "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", "requires": { - "@babel/runtime": "^7.1.2", + "@babel/runtime": "^7.12.13", "history": "^4.9.0", "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", @@ -29029,15 +29115,15 @@ } }, "react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.1.tgz", + "integrity": "sha512-xhFFkBGVcIVPbWM2KEYzED+nuHQPmulVa7sqIs3ESxzYd1pYg8N8rxPnQ4T2o1zu/2QeDUWcaqST131SO1LR3w==", "requires": { - "@babel/runtime": "^7.1.2", + "@babel/runtime": "^7.12.13", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.2.0", + "react-router": "5.2.1", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } @@ -31386,6 +31472,11 @@ "tryer": { "version": "1.0.1" }, + "ts-custom-error": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.2.0.tgz", + "integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A==" + }, "ts-pnp": { "version": "1.2.0" }, @@ -31456,9 +31547,9 @@ } }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==" + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==" }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4" diff --git a/package.json b/package.json index 31ea1ac..fb6e41e 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "private": true, "license": "MIT", "dependencies": { + "@blackbox-vision/react-qr-reader": "^5.0.0", "@chainlink/contracts": "^0.2.1", "@craco/craco": "^6.2.0", - "@fontsource/fira-code": "^4.5.0", + "@fontsource/fira-code": "^4.5.1", "@fontsource/roboto": "^4.5.0", "@fontsource/roboto-mono": "^4.5.0", "@fontsource/space-grotesk": "^4.5.0", @@ -15,17 +16,17 @@ "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.15", - "@headlessui/react": "^1.4.0", + "@headlessui/react": "^1.4.1", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.24", "@types/node": "^14.17.5", - "@types/react": "^17.0.17", + "@types/react": "^17.0.19", "@types/react-blockies": "^1.4.1", "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.1.8", - "chart.js": "^3.5.0", + "chart.js": "^3.5.1", "ethers": "^5.4.1", "query-string": "^7.0.1", "react": "^17.0.2", @@ -34,10 +35,10 @@ "react-dom": "^17.0.2", "react-error-boundary": "^3.1.3", "react-image": "^4.0.3", - "react-router-dom": "^5.2.0", + "react-router-dom": "^5.2.1", "react-scripts": "4.0.3", "serve": "^12.0.0", - "typescript": "^4.3.5", + "typescript": "^4.4.2", "use-keyboard-shortcut": "^1.0.6", "web-vitals": "^1.0.1" }, diff --git a/run-nginx.sh b/run-nginx.sh index 7d83028..604c8b4 100755 --- a/run-nginx.sh +++ b/run-nginx.sh @@ -1,4 +1,4 @@ #!/bin/sh -PARAMS="{\"erigonURL\": $(echo $ERIGON_URL | jq -aR .)}" +PARAMS="{\"erigonURL\": $(echo $ERIGON_URL | jq -aR .), \"assetsURLPrefix\": \"\"}" echo $PARAMS > /usr/share/nginx/html/config.json nginx -g "daemon off;" diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index e79253e..f4fd2ff 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useMemo, useContext } from "react"; import { useParams, useLocation, useHistory } from "react-router-dom"; +import { BlockTag } from "@ethersproject/abstract-provider"; import { getAddress, isAddress } from "@ethersproject/address"; import queryString from "query-string"; import Blockies from "react-blockies"; @@ -16,6 +17,7 @@ import { RuntimeContext } from "./useRuntime"; import { useENSCache } from "./useReverseCache"; import { useFeeToggler } from "./search/useFeeToggler"; import { SelectionContext, useSelection } from "./useSelection"; +import { useMultipleETHUSDOracle } from "./usePriceOracle"; type BlockParams = { addressOrName: string; @@ -150,6 +152,14 @@ const AddressTransactions: React.FC = () => { const page = useMemo(() => controller?.getPage(), [controller]); const reverseCache = useENSCache(provider, page); + const blockTags: BlockTag[] = useMemo(() => { + if (!page) { + return []; + } + return page.map((p) => p.blockNumber); + }, [page]); + const priceMap = useMultipleETHUSDOracle(provider, blockTags); + document.title = `Address ${params.addressOrName} | Otterscan`; const [feeDisplay, feeDisplayToggler] = useFeeToggler(); @@ -215,6 +225,7 @@ const AddressTransactions: React.FC = () => { ensCache={reverseCache} selectedAddress={checksummedAddress} feeDisplay={feeDisplay} + priceMap={priceMap} /> ))}
diff --git a/src/Block.tsx b/src/Block.tsx index 875d2f4..caa309b 100644 --- a/src/Block.tsx +++ b/src/Block.tsx @@ -17,11 +17,14 @@ import BlockLink from "./components/BlockLink"; import DecoratedAddressLink from "./components/DecoratedAddressLink"; import TransactionValue from "./components/TransactionValue"; import FormattedBalance from "./components/FormattedBalance"; +import ETH2USDValue from "./components/ETH2USDValue"; +import USDValue from "./components/USDValue"; import HexValue from "./components/HexValue"; import { RuntimeContext } from "./useRuntime"; import { useLatestBlockNumber } from "./useLatestBlock"; import { blockTxsURL } from "./url"; import { useBlockData } from "./useErigonHooks"; +import { useETHUSDOracle } from "./usePriceOracle"; type BlockParams = { blockNumberOrHash: string; @@ -48,11 +51,12 @@ const Block: React.FC = () => { }, [block]); const burntFees = block?.baseFeePerGas && block.baseFeePerGas.mul(block.gasUsed); - const netFeeReward = block && block.feeReward.sub(burntFees ?? 0); + const netFeeReward = block?.feeReward ?? BigNumber.from(0); const gasUsedPerc = block && block.gasUsed.mul(10000).div(block.gasLimit).toNumber() / 100; const latestBlockNumber = useLatestBlockNumber(provider); + const blockETHUSDPrice = useETHUSDOracle(provider, block?.number); return ( @@ -91,18 +95,23 @@ const Block: React.FC = () => { - - {!block.feeReward.isZero() && ( + + {!netFeeReward.isZero() && ( <> {" "} ( +{" "} - - ) + ) + + )} + {blockETHUSDPrice && ( + <> + {" "} + + + )} @@ -153,7 +162,9 @@ const Block: React.FC = () => { {extraStr} (Hex:{" "} {block.extraData}) - N/A + + + {commify(block.difficulty)} {commify(block.totalDifficulty.toString())} diff --git a/src/BlockTransactions.tsx b/src/BlockTransactions.tsx index 5a7209f..ed9b2c7 100644 --- a/src/BlockTransactions.tsx +++ b/src/BlockTransactions.tsx @@ -47,6 +47,7 @@ const BlockTransactions: React.FC = () => { { }; const latestBlock = useLatestBlock(provider); + const [isScanning, setScanning] = useState(false); document.title = "Home | Otterscan"; return (
+ {isScanning && setScanning(false)} />}
{ autoComplete="off" spellCheck={false} > - +
+ + +
-
+ + + + +
- + ); }; diff --git a/src/Transaction.tsx b/src/Transaction.tsx index cb5a4e1..5d10101 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -2,12 +2,14 @@ import React, { useMemo, useContext } from "react"; import { Route, Switch, useParams } from "react-router-dom"; import StandardFrame from "./StandardFrame"; import StandardSubtitle from "./StandardSubtitle"; +import ContentFrame from "./ContentFrame"; import Tab from "./components/Tab"; import Details from "./transaction/Details"; import Logs from "./transaction/Logs"; import { RuntimeContext } from "./useRuntime"; import { SelectionContext, useSelection } from "./useSelection"; import { useInternalOperations, useTxData } from "./useErigonHooks"; +import { useETHUSDOracle } from "./usePriceOracle"; type TransactionParams = { txhash: string; @@ -27,7 +29,7 @@ const Transaction: React.FC = () => { } for (const t of internalOps) { - if (t.to === txData.miner) { + if (t.to === txData.confirmedData?.miner) { return true; } } @@ -36,16 +38,30 @@ const Transaction: React.FC = () => { const selectionCtx = useSelection(); + const blockETHUSDPrice = useETHUSDOracle( + provider, + txData?.confirmedData?.blockNumber + ); + return ( Transaction Details + {txData === null && ( + +
+ Transaction {txhash} not found. +
+
+ )} {txData && (
Overview - - Logs{txData && ` (${txData.logs.length})`} - + {txData.confirmedData?.blockNumber !== undefined && ( + + Logs{txData && ` (${txData.confirmedData?.logs?.length ?? 0})`} + + )}
@@ -53,6 +69,7 @@ const Transaction: React.FC = () => { txData={txData} internalOps={internalOps} sendsEthToMiner={sendsEthToMiner} + ethUSDPrice={blockETHUSDPrice} /> diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index 5937199..b911013 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -1,4 +1,5 @@ -import React, { useContext } from "react"; +import React, { useContext, useMemo } from "react"; +import { BlockTag } from "@ethersproject/abstract-provider"; import ContentFrame from "../ContentFrame"; import PageControl from "../search/PageControl"; import ResultHeader from "../search/ResultHeader"; @@ -10,14 +11,17 @@ import { SelectionContext, useSelection } from "../useSelection"; import { useENSCache } from "../useReverseCache"; import { ProcessedTransaction } from "../types"; import { PAGE_SIZE } from "../params"; +import { useMultipleETHUSDOracle } from "../usePriceOracle"; type BlockTransactionResultsProps = { + blockTag: BlockTag; page?: ProcessedTransaction[]; total: number; pageNumber: number; }; const BlockTransactionResults: React.FC = ({ + blockTag, page, total, pageNumber, @@ -26,6 +30,8 @@ const BlockTransactionResults: React.FC = ({ const [feeDisplay, feeDisplayToggler] = useFeeToggler(); const { provider } = useContext(RuntimeContext); const reverseCache = useENSCache(provider, page); + const blockTags = useMemo(() => [blockTag], [blockTag]); + const priceMap = useMultipleETHUSDOracle(provider, blockTags); return ( @@ -55,6 +61,7 @@ const BlockTransactionResults: React.FC = ({ tx={tx} ensCache={reverseCache} feeDisplay={feeDisplay} + priceMap={priceMap} /> ))}
diff --git a/src/components/DecoratedAddressLink.tsx b/src/components/DecoratedAddressLink.tsx index a325343..0b38fab 100644 --- a/src/components/DecoratedAddressLink.tsx +++ b/src/components/DecoratedAddressLink.tsx @@ -41,11 +41,13 @@ const DecoratedAddresssLink: React.FC = ({ return (
{creation && ( diff --git a/src/components/ETH2USDValue.tsx b/src/components/ETH2USDValue.tsx new file mode 100644 index 0000000..ef131d8 --- /dev/null +++ b/src/components/ETH2USDValue.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { BigNumber, FixedNumber } from "@ethersproject/bignumber"; +import { commify } from "@ethersproject/units"; + +type ETH2USDValueProps = { + ethAmount: BigNumber; + eth2USDValue: BigNumber; +}; + +const ETH2USDValue: React.FC = ({ + ethAmount, + eth2USDValue, +}) => { + const value = ethAmount.mul(eth2USDValue).div(10 ** 8); + + return ( + + $ + + {commify(FixedNumber.fromValue(value, 18).round(2).toString())} + + + ); +}; + +export default React.memo(ETH2USDValue); diff --git a/src/components/InternalSelfDestruct.tsx b/src/components/InternalSelfDestruct.tsx index 384ef14..007fb32 100644 --- a/src/components/InternalSelfDestruct.tsx +++ b/src/components/InternalSelfDestruct.tsx @@ -22,7 +22,9 @@ const InternalSelfDestruct: React.FC = ({ const { provider } = useContext(RuntimeContext); const network = provider?.network; - const toMiner = txData.miner !== undefined && internalOp.to === txData.miner; + const toMiner = + txData.confirmedData?.miner !== undefined && + internalOp.to === txData.confirmedData.miner; return ( <> diff --git a/src/components/InternalTransfer.tsx b/src/components/InternalTransfer.tsx index b126d6f..dc53d05 100644 --- a/src/components/InternalTransfer.tsx +++ b/src/components/InternalTransfer.tsx @@ -16,8 +16,11 @@ const InternalTransfer: React.FC = ({ internalOp, }) => { const fromMiner = - txData.miner !== undefined && internalOp.from === txData.miner; - const toMiner = txData.miner !== undefined && internalOp.to === txData.miner; + txData.confirmedData?.miner !== undefined && + internalOp.from === txData.confirmedData.miner; + const toMiner = + txData.confirmedData?.miner !== undefined && + internalOp.to === txData.confirmedData.miner; return (
diff --git a/src/components/USDValue.tsx b/src/components/USDValue.tsx new file mode 100644 index 0000000..e488d25 --- /dev/null +++ b/src/components/USDValue.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { BigNumber, FixedNumber } from "@ethersproject/bignumber"; +import { commify } from "@ethersproject/units"; + +const ETH_FEED_DECIMALS = 8; + +type USDValueProps = { + value: BigNumber | undefined; +}; + +const USDValue: React.FC = ({ value }) => ( + + {value ? ( + <> + $ + + {commify( + FixedNumber.fromValue(value, ETH_FEED_DECIMALS).round(2).toString() + )} + {" "} + / ETH + + ) : ( + "N/A" + )} + +); + +export default React.memo(USDValue); diff --git a/src/index.css b/src/index.css index b5c61c9..771e212 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,30 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer base { + :root { + --color-button-fill: 244, 244, 245; /* gray-100 */ + --color-button-hover-fill: 228, 228, 231; /* gray-200 */ + --color-button-text: 113, 113, 122; /* gray-500 */ + + --color-from-border: 254, 226, 226; + --color-from-text: 220, 38, 38; + --color-from-fill: 254, 242, 242; + --color-to-fill: 236, 253, 245; + + --color-table-row-hover: 243, 244, 246; + } + .test-theme { + --color-button-fill: 14, 165, 233; /* sky-500 */ + --color-button-hover-fill: 56, 189, 248; /* sky-400 */ + --color-button-text: 186, 230, 253; /* sky-200 */ + + --color-from-border: 251, 146, 60; + --color-from-text: 249, 115, 22; + --color-from-fill: 254, 215, 170; + --color-to-fill: 125, 211, 252; + + --color-table-row-hover: 2, 132, 199; + } +} diff --git a/src/search/CameraScanner.tsx b/src/search/CameraScanner.tsx new file mode 100644 index 0000000..b32d707 --- /dev/null +++ b/src/search/CameraScanner.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { useHistory } 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"; +import { BarcodeFormat } from "@zxing/library"; +import { Dialog } from "@headlessui/react"; + +type CameraScannerProps = { + turnOffScan: () => void; +}; + +const CameraScanner: React.FC = ({ turnOffScan }) => { + const history = useHistory(); + + const evaluateScan: OnResultFunction = (result, error, codeReader) => { + console.log("scan"); + if (!error && result?.getBarcodeFormat() === BarcodeFormat.QR_CODE) { + const text = result.getText(); + console.log(`Scanned: ${text}`); + if (!isAddress(text)) { + console.warn("Not an ETH address"); + return; + } + + history.push(`/search?q=${text}`); + turnOffScan(); + } + }; + + return ( + +
+ + + Point an ETH address QR code to camera + +
+ +
+
+
+ ); +}; + +export default CameraScanner; diff --git a/src/search/ResultHeader.tsx b/src/search/ResultHeader.tsx index 13880bd..5fbcbc2 100644 --- a/src/search/ResultHeader.tsx +++ b/src/search/ResultHeader.tsx @@ -23,7 +23,9 @@ const ResultHeader: React.FC = ({ className="text-link-blue hover:text-link-blue-hover" onClick={feeDisplayToggler} > - {feeDisplay === FeeDisplay.TX_FEE ? "Txn Fee" : "Gas Price"} + {feeDisplay === FeeDisplay.TX_FEE && "Txn Fee"} + {feeDisplay === FeeDisplay.TX_FEE_USD && "Txn Fee (USD)"} + {feeDisplay === FeeDisplay.GAS_PRICE && "Gas Price"}
diff --git a/src/search/TransactionItem.tsx b/src/search/TransactionItem.tsx index 32aab32..ffafc09 100644 --- a/src/search/TransactionItem.tsx +++ b/src/search/TransactionItem.tsx @@ -1,4 +1,6 @@ import React from "react"; +import { BlockTag } from "@ethersproject/abstract-provider"; +import { BigNumber } from "@ethersproject/bignumber"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle"; import MethodName from "../components/MethodName"; @@ -15,12 +17,14 @@ import TransactionValue from "../components/TransactionValue"; import { ENSReverseCache, ProcessedTransaction } from "../types"; import { FeeDisplay } from "./useFeeToggler"; import { formatValue } from "../components/formatter"; +import ETH2USDValue from "../components/ETH2USDValue"; type TransactionItemProps = { tx: ProcessedTransaction; ensCache?: ENSReverseCache; selectedAddress?: string; feeDisplay: FeeDisplay; + priceMap: Record; }; const TransactionItem: React.FC = ({ @@ -28,6 +32,7 @@ const TransactionItem: React.FC = ({ ensCache, selectedAddress, feeDisplay, + priceMap, }) => { let direction: Direction | undefined; if (selectedAddress) { @@ -56,7 +61,9 @@ const TransactionItem: React.FC = ({ return (
@@ -121,9 +128,17 @@ const TransactionItem: React.FC = ({ - {feeDisplay === FeeDisplay.TX_FEE - ? formatValue(tx.fee, 18) - : formatValue(tx.gasPrice, 9)} + {feeDisplay === FeeDisplay.TX_FEE && formatValue(tx.fee, 18)} + {feeDisplay === FeeDisplay.TX_FEE_USD && + (priceMap[tx.blockNumber] ? ( + + ) : ( + "N/A" + ))} + {feeDisplay === FeeDisplay.GAS_PRICE && formatValue(tx.gasPrice, 9)}
); diff --git a/src/search/useFeeToggler.ts b/src/search/useFeeToggler.ts index 7a82a9c..722221c 100644 --- a/src/search/useFeeToggler.ts +++ b/src/search/useFeeToggler.ts @@ -2,16 +2,16 @@ import { useState } from "react"; export enum FeeDisplay { TX_FEE, + TX_FEE_USD, GAS_PRICE, } export const useFeeToggler = (): [FeeDisplay, () => void] => { const [feeDisplay, setFeeDisplay] = useState(FeeDisplay.TX_FEE); const feeDisplayToggler = () => { - if (feeDisplay === FeeDisplay.TX_FEE) { - setFeeDisplay(FeeDisplay.GAS_PRICE); - } else { - setFeeDisplay(FeeDisplay.TX_FEE); + setFeeDisplay(feeDisplay + 1); + if (feeDisplay === FeeDisplay.GAS_PRICE) { + setFeeDisplay(0); } }; diff --git a/src/special/london/BlockRow.tsx b/src/special/london/BlockRow.tsx index 8915417..df22a85 100644 --- a/src/special/london/BlockRow.tsx +++ b/src/special/london/BlockRow.tsx @@ -22,7 +22,7 @@ const BlockRow: React.FC = ({ now, block, baseFeeDelta }) => { const totalReward = block.blockReward.add(netFeeReward ?? 0); return ( -
+
diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 500d45e..e5ba19a 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useState } from "react"; -import { formatEther } from "@ethersproject/units"; +import { BigNumber } from "@ethersproject/bignumber"; import { toUtf8String } from "@ethersproject/strings"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle"; @@ -19,7 +19,9 @@ import MethodName from "../components/MethodName"; import TransactionType from "../components/TransactionType"; import RewardSplit from "./RewardSplit"; import GasValue from "../components/GasValue"; +import USDValue from "../components/USDValue"; import FormattedBalance from "../components/FormattedBalance"; +import ETH2USDValue from "../components/ETH2USDValue"; import TokenTransferItem from "../TokenTransferItem"; import { TransactionData, InternalOperation } from "../types"; import PercentageBar from "../components/PercentageBar"; @@ -31,16 +33,18 @@ type DetailsProps = { txData: TransactionData; internalOps?: InternalOperation[]; sendsEthToMiner: boolean; + ethUSDPrice: BigNumber | undefined; }; const Details: React.FC = ({ txData, internalOps, sendsEthToMiner, + ethUSDPrice, }) => { const hasEIP1559 = - txData.blockBaseFeePerGas !== undefined && - txData.blockBaseFeePerGas !== null; + txData.confirmedData?.blockBaseFeePerGas !== undefined && + txData.confirmedData?.blockBaseFeePerGas !== null; const [inputMode, setInputMode] = useState(0); const utfInput = useMemo(() => { @@ -62,7 +66,9 @@ const Details: React.FC = ({
- {txData.status ? ( + {txData.confirmedData === undefined ? ( + Pending + ) : txData.confirmedData.status ? ( Success @@ -74,38 +80,45 @@ const Details: React.FC = ({ )} - -
-
- - - - - -
-
- - -
-
-
- - - + {txData.confirmedData && ( + <> + +
+
+ + + + + +
+
+ + +
+
+
+ + + + + )}
@@ -122,22 +135,28 @@ const Details: React.FC = ({
+ ) : txData.confirmedData === undefined ? ( + + Pending contract creation + ) : (
- + - +
)} {internalOps && internalOps.length > 0 && ( @@ -170,9 +189,12 @@ const Details: React.FC = ({ )} - - {formatEther(txData.value)} Ether - + Ether{" "} + {!txData.value.isZero() && ethUSDPrice && ( + + + + )} = ({ )} - -
- - Ether ( - Gwei) - - {sendsEthToMiner && ( - - Flashbots + {txData.gasPrice && ( + +
+ + Ether ( + Gwei) - )} -
-
- -
-
- } - total={} + {sendsEthToMiner && ( + + Flashbots + + )} +
+ + )} + {txData.confirmedData && ( + +
+
+ } + total={} + /> +
+
- -
-
- {hasEIP1559 && ( + + )} + {txData.confirmedData && hasEIP1559 && ( - {" "} + {" "} Gwei ( {" "} wei) )} - -
-
- Ether -
- {hasEIP1559 && } -
-
- N/A + {txData.confirmedData && ( + <> + +
+
+ Ether{" "} + {ethUSDPrice && ( + + + + )} +
+ {hasEIP1559 && } +
+
+ + + + + )}
diff --git a/src/transaction/Logs.tsx b/src/transaction/Logs.tsx index b81fd9d..e57dc6a 100644 --- a/src/transaction/Logs.tsx +++ b/src/transaction/Logs.tsx @@ -10,8 +10,8 @@ type LogsProps = { const Logs: React.FC = ({ txData }) => (
Transaction Receipt Event Logs
- {txData && - txData.logs.map((l, i) => ( + {txData.confirmedData && + txData.confirmedData.logs.map((l, i) => (
@@ -24,7 +24,7 @@ const Logs: React.FC = ({ txData }) => (
diff --git a/src/transaction/RewardSplit.tsx b/src/transaction/RewardSplit.tsx index 8c45cd0..7d35a7e 100644 --- a/src/transaction/RewardSplit.tsx +++ b/src/transaction/RewardSplit.tsx @@ -11,8 +11,10 @@ type RewardSplitProps = { }; const RewardSplit: React.FC = ({ txData }) => { - const paidFees = txData.gasPrice.mul(txData.gasUsed); - const burntFees = txData.blockBaseFeePerGas!.mul(txData.gasUsed); + const paidFees = txData.gasPrice.mul(txData.confirmedData!.gasUsed); + const burntFees = txData.confirmedData!.blockBaseFeePerGas!.mul( + txData.confirmedData!.gasUsed + ); const minerReward = paidFees.sub(burntFees); const burntPerc = diff --git a/src/types.ts b/src/types.ts index 42c298d..5a5dad9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -38,29 +38,33 @@ export type ENSReverseCache = { export type TransactionData = { transactionHash: string; - status: boolean; - blockNumber: number; - transactionIndex: number; - blockTransactionCount: number; - confirmations: number; - timestamp: number; - miner?: string; from: string; - to: string; - createdContractAddress?: string; + to?: string; value: BigNumber; tokenTransfers: TokenTransfer[]; tokenMetas: TokenMetas; type: number; maxFeePerGas?: BigNumber | undefined; maxPriorityFeePerGas?: BigNumber | undefined; - fee: BigNumber; - blockBaseFeePerGas?: BigNumber | undefined | null; gasPrice: BigNumber; - gasUsed: BigNumber; gasLimit: BigNumber; nonce: number; data: string; + confirmedData?: ConfirmedTransactionData | undefined; +}; + +export type ConfirmedTransactionData = { + status: boolean; + blockNumber: number; + transactionIndex: number; + blockBaseFeePerGas?: BigNumber | undefined | null; + blockTransactionCount: number; + confirmations: number; + timestamp: number; + miner: string; + createdContractAddress?: string; + fee: BigNumber; + gasUsed: BigNumber; logs: Log[]; }; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 4067f4c..477f7b4 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -48,12 +48,11 @@ export const readBlock = async ( const _rawBlock = await blockPromise; const _block = provider.formatter.block(_rawBlock.block); const _rawIssuance = _rawBlock.issuance; - const fees = provider.formatter.bigNumber(_rawBlock.totalFees); const extBlock: ExtendedBlock = { blockReward: provider.formatter.bigNumber(_rawIssuance.blockReward ?? 0), unclesReward: provider.formatter.bigNumber(_rawIssuance.uncleReward ?? 0), - feeReward: fees, + feeReward: provider.formatter.bigNumber(_rawBlock.totalFees), size: provider.formatter.number(_rawBlock.block.size), sha3Uncles: _rawBlock.block.sha3Uncles, stateRoot: _rawBlock.block.stateRoot, @@ -175,8 +174,8 @@ export const useBlockData = ( export const useTxData = ( provider: JsonRpcProvider | undefined, txhash: string -): TransactionData | undefined => { - const [txData, setTxData] = useState(); +): TransactionData | undefined | null => { + const [txData, setTxData] = useState(); useEffect(() => { if (!provider) { @@ -188,24 +187,35 @@ export const useTxData = ( provider.getTransaction(txhash), provider.getTransactionReceipt(txhash), ]); - const _block = await readBlock(provider, _receipt.blockNumber.toString()); + if (_response === null) { + setTxData(null); + return; + } + + let _block: ExtendedBlock | undefined; + if (_response.blockNumber) { + _block = await readBlock(provider, _response.blockNumber.toString()); + } + document.title = `Transaction ${_response.hash} | Otterscan`; // Extract token transfers const tokenTransfers: TokenTransfer[] = []; - for (const l of _receipt.logs) { - if (l.topics.length !== 3) { - continue; + if (_receipt) { + for (const l of _receipt.logs) { + if (l.topics.length !== 3) { + continue; + } + if (l.topics[0] !== TRANSFER_TOPIC) { + continue; + } + tokenTransfers.push({ + token: l.address, + from: getAddress(hexDataSlice(arrayify(l.topics[1]), 12)), + to: getAddress(hexDataSlice(arrayify(l.topics[2]), 12)), + value: BigNumber.from(l.data), + }); } - if (l.topics[0] !== TRANSFER_TOPIC) { - continue; - } - tokenTransfers.push({ - token: l.address, - from: getAddress(hexDataSlice(arrayify(l.topics[1]), 12)), - to: getAddress(hexDataSlice(arrayify(l.topics[2]), 12)), - value: BigNumber.from(l.data), - }); } // Extract token meta @@ -228,31 +238,36 @@ export const useTxData = ( } setTxData({ - transactionHash: _receipt.transactionHash, - status: _receipt.status === 1, - blockNumber: _receipt.blockNumber, - transactionIndex: _receipt.transactionIndex, - blockTransactionCount: _block.transactionCount, - confirmations: _receipt.confirmations, - timestamp: _block.timestamp, - miner: _block.miner, - from: _receipt.from, - to: _receipt.to, - createdContractAddress: _receipt.contractAddress, + transactionHash: _response.hash, + from: _response.from, + to: _response.to, value: _response.value, tokenTransfers, tokenMetas, type: _response.type ?? 0, - fee: _response.gasPrice!.mul(_receipt.gasUsed), - blockBaseFeePerGas: _block.baseFeePerGas, maxFeePerGas: _response.maxFeePerGas, maxPriorityFeePerGas: _response.maxPriorityFeePerGas, gasPrice: _response.gasPrice!, - gasUsed: _receipt.gasUsed, gasLimit: _response.gasLimit, nonce: _response.nonce, data: _response.data, - logs: _receipt.logs, + confirmedData: + _receipt === null + ? undefined + : { + status: _receipt.status === 1, + blockNumber: _receipt.blockNumber, + transactionIndex: _receipt.transactionIndex, + blockBaseFeePerGas: _block!.baseFeePerGas, + blockTransactionCount: _block!.transactionCount, + confirmations: _receipt.confirmations, + timestamp: _block!.timestamp, + miner: _block!.miner, + createdContractAddress: _receipt.contractAddress, + fee: _response.gasPrice!.mul(_receipt.gasUsed), + gasUsed: _receipt.gasUsed, + logs: _receipt.logs, + }, }); }; readTxData(); @@ -263,13 +278,13 @@ export const useTxData = ( export const useInternalOperations = ( provider: JsonRpcProvider | undefined, - txData: TransactionData | undefined + txData: TransactionData | undefined | null ): InternalOperation[] | undefined => { const [intTransfers, setIntTransfers] = useState(); useEffect(() => { const traceTransfers = async () => { - if (!provider || !txData) { + if (!provider || !txData || !txData.confirmedData) { return; } diff --git a/src/usePriceOracle.ts b/src/usePriceOracle.ts new file mode 100644 index 0000000..f41a1be --- /dev/null +++ b/src/usePriceOracle.ts @@ -0,0 +1,78 @@ +import { useEffect, useMemo, useState } from "react"; +import { JsonRpcProvider, BlockTag } from "@ethersproject/providers"; +import { Contract } from "@ethersproject/contracts"; +import { BigNumber } from "@ethersproject/bignumber"; +import AggregatorV3Interface from "@chainlink/contracts/abi/v0.8/AggregatorV3Interface.json"; + +export const useETHUSDOracle = ( + provider: JsonRpcProvider | undefined, + blockTag: BlockTag | undefined +) => { + const blockTags = useMemo(() => [blockTag], [blockTag]); + const priceMap = useMultipleETHUSDOracle(provider, blockTags); + + if (blockTag === undefined) { + return undefined; + } + return priceMap[blockTag]; +}; + +export const useMultipleETHUSDOracle = ( + provider: JsonRpcProvider | undefined, + blockTags: (BlockTag | undefined)[] +) => { + const ethFeed = useMemo(() => { + if (!provider || provider.network.chainId !== 1) { + return undefined; + } + + try { + return new Contract("eth-usd.data.eth", AggregatorV3Interface, provider); + } catch (err) { + console.error(err); + return undefined; + } + }, [provider]); + + const [latestPriceData, setLatestPriceData] = useState< + Record + >({}); + useEffect(() => { + if (!ethFeed) { + return; + } + + const priceReaders: Promise[] = []; + for (const blockTag of blockTags) { + priceReaders.push( + (async () => { + try { + const priceData = await ethFeed.latestRoundData({ blockTag }); + return BigNumber.from(priceData.answer); + } catch (err) { + console.error(err); + return undefined; + } + })() + ); + } + const readData = async () => { + const results = await Promise.all(priceReaders); + const priceMap: Record = {}; + for (let i = 0; i < blockTags.length; i++) { + const blockTag = blockTags[i]; + const result = results[i]; + if (blockTag === undefined || result === undefined) { + continue; + } + + priceMap[blockTag] = result; + } + + setLatestPriceData(priceMap); + }; + readData(); + }, [ethFeed, blockTags]); + + return latestPriceData; +}; diff --git a/tailwind.config.js b/tailwind.config.js index 3f72cce..a6eafd8 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,14 @@ const colors = require("tailwindcss/colors"); +function withOpacity(variableName) { + return ({ opacityValue }) => { + if (opacityValue !== undefined) { + return `rgba(var(${variableName}), ${opacityValue})`; + } + return `rgb(var(${variableName}))`; + }; +} + module.exports = { purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], darkMode: false, // or 'media' or 'class' @@ -19,6 +28,28 @@ module.exports = { balance: ["Fira Code"], blocknum: ["Roboto"], }, + borderColor: { + skin: { + from: withOpacity("--color-from-border"), + }, + }, + textColor: { + skin: { + button: withOpacity("--color-button-text"), + + from: withOpacity("--color-from-text"), + }, + }, + backgroundColor: { + skin: { + "button-fill": withOpacity("--color-button-fill"), + "button-hover-fill": withOpacity("--color-button-hover-fill"), + + from: withOpacity("--color-from-fill"), + to: withOpacity("--color-to-fill"), + "table-hover": withOpacity("--color-table-row-hover"), + }, + }, }, }, variants: { diff --git a/trustwallet b/trustwallet index 9bc40f3..7bfa06a 160000 --- a/trustwallet +++ b/trustwallet @@ -1 +1 @@ -Subproject commit 9bc40f37d95234810bc7e176513c8366c81080ce +Subproject commit 7bfa06acc125a4874d86bc1fa8e4547a46846e31