Merge branch 'release/v2021.09.01-otterscan'
This commit is contained in:
commit
7c5be049b1
2
4bytes
2
4bytes
|
@ -1 +1 @@
|
|||
Subproject commit 79965318da56eed67366cf399ee5661b51af49cb
|
||||
Subproject commit 1cc7e25c840ae9d985c12768b0cbd0ece3fc5400
|
|
@ -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 <version-tag-otterscan>
|
||||
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/`.
|
||||
Otterscan should now be running at `http://localhost:3000/`.
|
||||
|
|
|
@ -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"
|
||||
|
|
13
package.json
13
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"
|
||||
},
|
||||
|
|
|
@ -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;"
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
))}
|
||||
<div className="flex justify-between items-baseline py-3">
|
||||
|
|
|
@ -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 (
|
||||
<StandardFrame>
|
||||
|
@ -91,18 +95,23 @@ const Block: React.FC = () => {
|
|||
<DecoratedAddressLink address={block.miner} miner />
|
||||
</InfoRow>
|
||||
<InfoRow title="Block Reward">
|
||||
<TransactionValue
|
||||
value={block.blockReward.add(netFeeReward ?? 0)}
|
||||
/>
|
||||
{!block.feeReward.isZero() && (
|
||||
<TransactionValue value={block.blockReward.add(netFeeReward)} />
|
||||
{!netFeeReward.isZero() && (
|
||||
<>
|
||||
{" "}
|
||||
(<TransactionValue value={block.blockReward} hideUnit /> +{" "}
|
||||
<TransactionValue
|
||||
value={netFeeReward ?? BigNumber.from(0)}
|
||||
hideUnit
|
||||
/>
|
||||
)
|
||||
<TransactionValue value={netFeeReward} hideUnit />)
|
||||
</>
|
||||
)}
|
||||
{blockETHUSDPrice && (
|
||||
<>
|
||||
{" "}
|
||||
<span className="px-2 border-yellow-200 border rounded-lg bg-yellow-100 text-yellow-600">
|
||||
<ETH2USDValue
|
||||
ethAmount={block.blockReward.add(netFeeReward)}
|
||||
eth2USDValue={blockETHUSDPrice}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</InfoRow>
|
||||
|
@ -153,7 +162,9 @@ const Block: React.FC = () => {
|
|||
{extraStr} (Hex:{" "}
|
||||
<span className="font-data">{block.extraData}</span>)
|
||||
</InfoRow>
|
||||
<InfoRow title="Ether Price">N/A</InfoRow>
|
||||
<InfoRow title="Ether Price">
|
||||
<USDValue value={blockETHUSDPrice} />
|
||||
</InfoRow>
|
||||
<InfoRow title="Difficult">{commify(block.difficulty)}</InfoRow>
|
||||
<InfoRow title="Total Difficult">
|
||||
{commify(block.totalDifficulty.toString())}
|
||||
|
|
|
@ -47,6 +47,7 @@ const BlockTransactions: React.FC = () => {
|
|||
<StandardFrame>
|
||||
<BlockTransactionHeader blockTag={blockNumber.toNumber()} />
|
||||
<BlockTransactionResults
|
||||
blockTag={blockNumber.toNumber()}
|
||||
page={txs}
|
||||
total={totalTxs ?? 0}
|
||||
pageNumber={pageNumber}
|
||||
|
|
32
src/Home.tsx
32
src/Home.tsx
|
@ -3,7 +3,9 @@ import { NavLink, useHistory } from "react-router-dom";
|
|||
import { commify } from "@ethersproject/units";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
|
||||
import { faQrcode } from "@fortawesome/free-solid-svg-icons/faQrcode";
|
||||
import Logo from "./Logo";
|
||||
import CameraScanner from "./search/CameraScanner";
|
||||
import Timestamp from "./components/Timestamp";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
import { useLatestBlock } from "./useLatestBlock";
|
||||
|
@ -30,11 +32,13 @@ const Home: React.FC = () => {
|
|||
};
|
||||
|
||||
const latestBlock = useLatestBlock(provider);
|
||||
const [isScanning, setScanning] = useState<boolean>(false);
|
||||
|
||||
document.title = "Home | Otterscan";
|
||||
|
||||
return (
|
||||
<div className="m-auto">
|
||||
{isScanning && <CameraScanner turnOffScan={() => setScanning(false)} />}
|
||||
<Logo />
|
||||
<form
|
||||
className="flex flex-col"
|
||||
|
@ -42,16 +46,26 @@ const Home: React.FC = () => {
|
|||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
>
|
||||
<input
|
||||
className="w-full border rounded focus:outline-none px-2 py-1 mb-10"
|
||||
type="text"
|
||||
size={50}
|
||||
placeholder="Search by address / txn hash / block number / ENS name"
|
||||
onChange={handleChange}
|
||||
autoFocus
|
||||
></input>
|
||||
<div className="flex mb-10">
|
||||
<input
|
||||
className="w-full border-l border-t border-b rounded-l focus:outline-none px-2 py-1"
|
||||
type="text"
|
||||
size={50}
|
||||
placeholder="Search by address / txn hash / block number / ENS name"
|
||||
onChange={handleChange}
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
className="border rounded-r bg-skin-button-fill hover:bg-skin-button-hover-fill focus:outline-none px-2 py-1 text-base text-skin-button flex justify-center items-center"
|
||||
type="button"
|
||||
onClick={() => setScanning(true)}
|
||||
title="Scan an ETH address using your camera"
|
||||
>
|
||||
<FontAwesomeIcon icon={faQrcode} />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="mx-auto px-3 py-1 mb-10 rounded bg-gray-100 hover:bg-gray-200 focus:outline-none"
|
||||
className="mx-auto px-3 py-1 mb-10 rounded bg-skin-button-fill hover:bg-skin-button-hover-fill focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
Search
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import React, { useState, useRef, useContext } from "react";
|
||||
import { Link, useHistory } 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 CameraScanner from "./search/CameraScanner";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
|
||||
const Title: React.FC = () => {
|
||||
|
@ -29,46 +32,59 @@ const Title: React.FC = () => {
|
|||
searchRef.current?.focus();
|
||||
});
|
||||
|
||||
const [isScanning, setScanning] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="px-9 py-2 flex justify-between items-baseline">
|
||||
<Link className="self-center" to="/">
|
||||
<div className="text-2xl text-link-blue font-title font-bold flex items-center space-x-2">
|
||||
<img
|
||||
className="rounded-full"
|
||||
src="/otter.jpg"
|
||||
width={32}
|
||||
height={32}
|
||||
alt="An otter scanning"
|
||||
title="An otter scanning"
|
||||
/>
|
||||
<span>Otterscan</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex items-baseline space-x-3">
|
||||
{provider?.network.chainId === 1 && <PriceBox />}
|
||||
<form
|
||||
className="flex"
|
||||
onSubmit={handleSubmit}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
>
|
||||
<input
|
||||
className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm"
|
||||
type="text"
|
||||
size={60}
|
||||
placeholder='Type "/" to search by address / txn hash / block number / ENS name'
|
||||
onChange={handleChange}
|
||||
ref={searchRef}
|
||||
/>
|
||||
<button
|
||||
className="rounded-r border-t border-b border-r bg-gray-100 hover:bg-gray-200 focus:outline-none px-2 py-1 text-sm text-gray-500"
|
||||
type="submit"
|
||||
<>
|
||||
{isScanning && <CameraScanner turnOffScan={() => setScanning(false)} />}
|
||||
<div className="px-9 py-2 flex justify-between items-baseline">
|
||||
<Link className="self-center" to="/">
|
||||
<div className="text-2xl text-link-blue font-title font-bold flex items-center space-x-2">
|
||||
<img
|
||||
className="rounded-full"
|
||||
src="/otter.jpg"
|
||||
width={32}
|
||||
height={32}
|
||||
alt="An otter scanning"
|
||||
title="An otter scanning"
|
||||
/>
|
||||
<span>Otterscan</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex items-baseline space-x-3">
|
||||
{provider?.network.chainId === 1 && <PriceBox />}
|
||||
<form
|
||||
className="flex"
|
||||
onSubmit={handleSubmit}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</form>
|
||||
<input
|
||||
className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm"
|
||||
type="text"
|
||||
size={60}
|
||||
placeholder='Type "/" to search by address / txn hash / block number / ENS name'
|
||||
onChange={handleChange}
|
||||
ref={searchRef}
|
||||
/>
|
||||
<button
|
||||
className="border bg-skin-button-fill hover:bg-skin-button-hover-fill focus:outline-none px-2 py-1 text-sm text-skin-button"
|
||||
type="button"
|
||||
onClick={() => setScanning(true)}
|
||||
title="Scan an ETH address using your camera"
|
||||
>
|
||||
<FontAwesomeIcon icon={faQrcode} />
|
||||
</button>
|
||||
<button
|
||||
className="rounded-r border-t border-b border-r bg-skin-button-fill hover:bg-skin-button-hover-fill focus:outline-none px-2 py-1 text-sm text-skin-button"
|
||||
type="submit"
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<StandardFrame>
|
||||
<StandardSubtitle>Transaction Details</StandardSubtitle>
|
||||
{txData === null && (
|
||||
<ContentFrame>
|
||||
<div className="py-4 text-sm">
|
||||
Transaction <span className="font-hash">{txhash}</span> not found.
|
||||
</div>
|
||||
</ContentFrame>
|
||||
)}
|
||||
{txData && (
|
||||
<SelectionContext.Provider value={selectionCtx}>
|
||||
<div className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
|
||||
<Tab href={`/tx/${txhash}`}>Overview</Tab>
|
||||
<Tab href={`/tx/${txhash}/logs`}>
|
||||
Logs{txData && ` (${txData.logs.length})`}
|
||||
</Tab>
|
||||
{txData.confirmedData?.blockNumber !== undefined && (
|
||||
<Tab href={`/tx/${txhash}/logs`}>
|
||||
Logs{txData && ` (${txData.confirmedData?.logs?.length ?? 0})`}
|
||||
</Tab>
|
||||
)}
|
||||
</div>
|
||||
<Switch>
|
||||
<Route path="/tx/:txhash/" exact>
|
||||
|
@ -53,6 +69,7 @@ const Transaction: React.FC = () => {
|
|||
txData={txData}
|
||||
internalOps={internalOps}
|
||||
sendsEthToMiner={sendsEthToMiner}
|
||||
ethUSDPrice={blockETHUSDPrice}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/tx/:txhash/logs/" exact>
|
||||
|
|
|
@ -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<BlockTransactionResultsProps> = ({
|
||||
blockTag,
|
||||
page,
|
||||
total,
|
||||
pageNumber,
|
||||
|
@ -26,6 +30,8 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
|||
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const reverseCache = useENSCache(provider, page);
|
||||
const blockTags = useMemo(() => [blockTag], [blockTag]);
|
||||
const priceMap = useMultipleETHUSDOracle(provider, blockTags);
|
||||
|
||||
return (
|
||||
<ContentFrame>
|
||||
|
@ -55,6 +61,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
|||
tx={tx}
|
||||
ensCache={reverseCache}
|
||||
feeDisplay={feeDisplay}
|
||||
priceMap={priceMap}
|
||||
/>
|
||||
))}
|
||||
<div className="flex justify-between items-baseline py-3">
|
||||
|
|
|
@ -41,11 +41,13 @@ const DecoratedAddresssLink: React.FC<DecoratedAddressLinkProps> = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-baseline space-x-1 ${txFrom ? "bg-red-50" : ""} ${
|
||||
txTo ? "bg-green-50" : ""
|
||||
} ${mint ? "italic text-green-500 hover:text-green-700" : ""} ${
|
||||
burn ? "line-through text-orange-500 hover:text-orange-700" : ""
|
||||
} ${selfDestruct ? "line-through opacity-70 hover:opacity-100" : ""}`}
|
||||
className={`flex items-baseline space-x-1 ${
|
||||
txFrom ? "bg-skin-from" : ""
|
||||
} ${txTo ? "bg-skin-to" : ""} ${
|
||||
mint ? "italic text-green-500 hover:text-green-700" : ""
|
||||
} ${burn ? "line-through text-orange-500 hover:text-orange-700" : ""} ${
|
||||
selfDestruct ? "line-through opacity-70 hover:opacity-100" : ""
|
||||
}`}
|
||||
>
|
||||
{creation && (
|
||||
<span className="text-yellow-300" title="Contract creation">
|
||||
|
|
|
@ -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<ETH2USDValueProps> = ({
|
||||
ethAmount,
|
||||
eth2USDValue,
|
||||
}) => {
|
||||
const value = ethAmount.mul(eth2USDValue).div(10 ** 8);
|
||||
|
||||
return (
|
||||
<span className="text-xs">
|
||||
$
|
||||
<span className="font-balance">
|
||||
{commify(FixedNumber.fromValue(value, 18).round(2).toString())}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ETH2USDValue);
|
|
@ -22,7 +22,9 @@ const InternalSelfDestruct: React.FC<InternalSelfDestructProps> = ({
|
|||
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 (
|
||||
<>
|
||||
|
|
|
@ -16,8 +16,11 @@ const InternalTransfer: React.FC<InternalTransferProps> = ({
|
|||
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 (
|
||||
<div className="flex items-baseline space-x-1 text-xs">
|
||||
|
|
|
@ -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<USDValueProps> = ({ value }) => (
|
||||
<span className="text-sm">
|
||||
{value ? (
|
||||
<>
|
||||
$
|
||||
<span className="font-balance">
|
||||
{commify(
|
||||
FixedNumber.fromValue(value, ETH_FEED_DECIMALS).round(2).toString()
|
||||
)}
|
||||
</span>{" "}
|
||||
<span className="text-xs text-gray-500">/ ETH</span>
|
||||
</>
|
||||
) : (
|
||||
"N/A"
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
export default React.memo(USDValue);
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CameraScannerProps> = ({ 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 (
|
||||
<Dialog
|
||||
className="fixed z-10 inset-0 overflow-y-auto"
|
||||
open={true}
|
||||
onClose={turnOffScan}
|
||||
>
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black opacity-30" />
|
||||
<Dialog.Title className="absolute top-0 w-full text-center bg-white text-lg">
|
||||
Point an ETH address QR code to camera
|
||||
</Dialog.Title>
|
||||
<div className="absolute inset-0 bg-transparent rounded min-w-max max-w-3xl w-full h-screen max-h-screen m-auto">
|
||||
<QrReader
|
||||
className="m-auto"
|
||||
constraints={{}}
|
||||
onResult={evaluateScan}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default CameraScanner;
|
|
@ -23,7 +23,9 @@ const ResultHeader: React.FC<ResultHeaderProps> = ({
|
|||
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"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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<BlockTag, BigNumber>;
|
||||
};
|
||||
|
||||
const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||
|
@ -28,6 +32,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
ensCache,
|
||||
selectedAddress,
|
||||
feeDisplay,
|
||||
priceMap,
|
||||
}) => {
|
||||
let direction: Direction | undefined;
|
||||
if (selectedAddress) {
|
||||
|
@ -56,7 +61,9 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
return (
|
||||
<div
|
||||
className={`grid grid-cols-12 gap-x-1 items-baseline text-sm border-t border-gray-200 ${
|
||||
flash ? "bg-yellow-100 hover:bg-yellow-200" : "hover:bg-gray-100"
|
||||
flash
|
||||
? "bg-yellow-100 hover:bg-yellow-200"
|
||||
: "hover:bg-skin-table-hover"
|
||||
} px-2 py-3`}
|
||||
>
|
||||
<div className="col-span-2 flex space-x-1 items-baseline">
|
||||
|
@ -121,9 +128,17 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
<TransactionValue value={tx.value} />
|
||||
</span>
|
||||
<span className="font-balance text-xs text-gray-500 truncate">
|
||||
{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] ? (
|
||||
<ETH2USDValue
|
||||
ethAmount={tx.fee}
|
||||
eth2USDValue={priceMap[tx.blockNumber]}
|
||||
/>
|
||||
) : (
|
||||
"N/A"
|
||||
))}
|
||||
{feeDisplay === FeeDisplay.GAS_PRICE && formatValue(tx.gasPrice, 9)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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>(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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ const BlockRow: React.FC<BlockRowProps> = ({ now, block, baseFeeDelta }) => {
|
|||
const totalReward = block.blockReward.add(netFeeReward ?? 0);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-9 gap-x-2 px-3 py-2 hover:bg-gray-100">
|
||||
<div className="grid grid-cols-9 gap-x-2 px-3 py-2 hover:bg-skin-table-hover">
|
||||
<div>
|
||||
<BlockLink blockTag={block.number} />
|
||||
</div>
|
||||
|
|
|
@ -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<DetailsProps> = ({
|
||||
txData,
|
||||
internalOps,
|
||||
sendsEthToMiner,
|
||||
ethUSDPrice,
|
||||
}) => {
|
||||
const hasEIP1559 =
|
||||
txData.blockBaseFeePerGas !== undefined &&
|
||||
txData.blockBaseFeePerGas !== null;
|
||||
txData.confirmedData?.blockBaseFeePerGas !== undefined &&
|
||||
txData.confirmedData?.blockBaseFeePerGas !== null;
|
||||
const [inputMode, setInputMode] = useState<number>(0);
|
||||
|
||||
const utfInput = useMemo(() => {
|
||||
|
@ -62,7 +66,9 @@ const Details: React.FC<DetailsProps> = ({
|
|||
</div>
|
||||
</InfoRow>
|
||||
<InfoRow title="Status">
|
||||
{txData.status ? (
|
||||
{txData.confirmedData === undefined ? (
|
||||
<span className="italic text-gray-400">Pending</span>
|
||||
) : txData.confirmedData.status ? (
|
||||
<span className="flex items-center w-min rounded-lg space-x-1 px-3 py-1 bg-green-50 text-green-500 text-xs">
|
||||
<FontAwesomeIcon icon={faCheckCircle} size="1x" />
|
||||
<span>Success</span>
|
||||
|
@ -74,38 +80,45 @@ const Details: React.FC<DetailsProps> = ({
|
|||
</span>
|
||||
)}
|
||||
</InfoRow>
|
||||
<InfoRow title="Block / Position">
|
||||
<div className="flex items-baseline divide-x-2 divide-dotted divide-gray-300">
|
||||
<div className="flex space-x-1 items-baseline mr-3">
|
||||
<span className="text-orange-500">
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
</span>
|
||||
<BlockLink blockTag={txData.blockNumber} />
|
||||
<BlockConfirmations confirmations={txData.confirmations} />
|
||||
</div>
|
||||
<div className="flex space-x-2 items-baseline pl-3">
|
||||
<RelativePosition
|
||||
pos={txData.transactionIndex}
|
||||
total={txData.blockTransactionCount - 1}
|
||||
/>
|
||||
<PercentagePosition
|
||||
perc={
|
||||
txData.transactionIndex / (txData.blockTransactionCount - 1)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</InfoRow>
|
||||
<InfoRow title="Timestamp">
|
||||
<Timestamp value={txData.timestamp} />
|
||||
</InfoRow>
|
||||
{txData.confirmedData && (
|
||||
<>
|
||||
<InfoRow title="Block / Position">
|
||||
<div className="flex items-baseline divide-x-2 divide-dotted divide-gray-300">
|
||||
<div className="flex space-x-1 items-baseline mr-3">
|
||||
<span className="text-orange-500">
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
</span>
|
||||
<BlockLink blockTag={txData.confirmedData.blockNumber} />
|
||||
<BlockConfirmations
|
||||
confirmations={txData.confirmedData.confirmations}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex space-x-2 items-baseline pl-3">
|
||||
<RelativePosition
|
||||
pos={txData.confirmedData.transactionIndex}
|
||||
total={txData.confirmedData.blockTransactionCount - 1}
|
||||
/>
|
||||
<PercentagePosition
|
||||
perc={
|
||||
txData.confirmedData.transactionIndex /
|
||||
(txData.confirmedData.blockTransactionCount - 1)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</InfoRow>
|
||||
<InfoRow title="Timestamp">
|
||||
<Timestamp value={txData.confirmedData.timestamp} />
|
||||
</InfoRow>
|
||||
</>
|
||||
)}
|
||||
<InfoRow title="From / Nonce">
|
||||
<div className="flex divide-x-2 divide-dotted divide-gray-300">
|
||||
<div className="flex items-baseline space-x-2 -ml-1 mr-3">
|
||||
<AddressHighlighter address={txData.from}>
|
||||
<DecoratedAddressLink
|
||||
address={txData.from}
|
||||
miner={txData.from === txData.miner}
|
||||
miner={txData.from === txData.confirmedData?.miner}
|
||||
txFrom
|
||||
/>
|
||||
</AddressHighlighter>
|
||||
|
@ -122,22 +135,28 @@ const Details: React.FC<DetailsProps> = ({
|
|||
<AddressHighlighter address={txData.to}>
|
||||
<DecoratedAddressLink
|
||||
address={txData.to}
|
||||
miner={txData.to === txData.miner}
|
||||
miner={txData.to === txData.confirmedData?.miner}
|
||||
txTo
|
||||
/>
|
||||
</AddressHighlighter>
|
||||
<Copy value={txData.to} />
|
||||
</div>
|
||||
) : txData.confirmedData === undefined ? (
|
||||
<span className="italic text-gray-400">
|
||||
Pending contract creation
|
||||
</span>
|
||||
) : (
|
||||
<div className="flex items-baseline space-x-2 -ml-1">
|
||||
<AddressHighlighter address={txData.createdContractAddress!}>
|
||||
<AddressHighlighter
|
||||
address={txData.confirmedData?.createdContractAddress!}
|
||||
>
|
||||
<DecoratedAddressLink
|
||||
address={txData.createdContractAddress!}
|
||||
address={txData.confirmedData.createdContractAddress!}
|
||||
creation
|
||||
txTo
|
||||
/>
|
||||
</AddressHighlighter>
|
||||
<Copy value={txData.createdContractAddress!} />
|
||||
<Copy value={txData.confirmedData.createdContractAddress!} />
|
||||
</div>
|
||||
)}
|
||||
{internalOps && internalOps.length > 0 && (
|
||||
|
@ -170,9 +189,12 @@ const Details: React.FC<DetailsProps> = ({
|
|||
</InfoRow>
|
||||
)}
|
||||
<InfoRow title="Value">
|
||||
<span className="rounded bg-gray-100 px-2 py-1 text-xs">
|
||||
{formatEther(txData.value)} Ether
|
||||
</span>
|
||||
<FormattedBalance value={txData.value} /> Ether{" "}
|
||||
{!txData.value.isZero() && ethUSDPrice && (
|
||||
<span className="px-2 border-skin-from border rounded-lg bg-skin-from text-skin-from">
|
||||
<ETH2USDValue ethAmount={txData.value} eth2USDValue={ethUSDPrice} />
|
||||
</span>
|
||||
)}
|
||||
</InfoRow>
|
||||
<InfoRow
|
||||
title={
|
||||
|
@ -211,58 +233,81 @@ const Details: React.FC<DetailsProps> = ({
|
|||
</InfoRow>
|
||||
</>
|
||||
)}
|
||||
<InfoRow title="Gas Price">
|
||||
<div className="flex items-baseline space-x-1">
|
||||
<span>
|
||||
<FormattedBalance value={txData.gasPrice} /> Ether (
|
||||
<FormattedBalance value={txData.gasPrice} decimals={9} /> Gwei)
|
||||
</span>
|
||||
{sendsEthToMiner && (
|
||||
<span className="rounded text-yellow-500 bg-yellow-100 text-xs px-2 py-1">
|
||||
Flashbots
|
||||
{txData.gasPrice && (
|
||||
<InfoRow title="Gas Price">
|
||||
<div className="flex items-baseline space-x-1">
|
||||
<span>
|
||||
<FormattedBalance value={txData.gasPrice} /> Ether (
|
||||
<FormattedBalance value={txData.gasPrice} decimals={9} /> Gwei)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</InfoRow>
|
||||
<InfoRow title="Gas Used / Limit">
|
||||
<div className="flex space-x-3 items-baseline">
|
||||
<div>
|
||||
<RelativePosition
|
||||
pos={<GasValue value={txData.gasUsed} />}
|
||||
total={<GasValue value={txData.gasLimit} />}
|
||||
{sendsEthToMiner && (
|
||||
<span className="rounded text-yellow-500 bg-yellow-100 text-xs px-2 py-1">
|
||||
Flashbots
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</InfoRow>
|
||||
)}
|
||||
{txData.confirmedData && (
|
||||
<InfoRow title="Gas Used / Limit">
|
||||
<div className="flex space-x-3 items-baseline">
|
||||
<div>
|
||||
<RelativePosition
|
||||
pos={<GasValue value={txData.confirmedData.gasUsed} />}
|
||||
total={<GasValue value={txData.gasLimit} />}
|
||||
/>
|
||||
</div>
|
||||
<PercentageBar
|
||||
perc={
|
||||
Math.round(
|
||||
(txData.confirmedData.gasUsed.toNumber() /
|
||||
txData.gasLimit.toNumber()) *
|
||||
10000
|
||||
) / 100
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<PercentageBar
|
||||
perc={
|
||||
Math.round(
|
||||
(txData.gasUsed.toNumber() / txData.gasLimit.toNumber()) * 10000
|
||||
) / 100
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</InfoRow>
|
||||
{hasEIP1559 && (
|
||||
</InfoRow>
|
||||
)}
|
||||
{txData.confirmedData && hasEIP1559 && (
|
||||
<InfoRow title="Block Base Fee">
|
||||
<span>
|
||||
<FormattedBalance value={txData.blockBaseFeePerGas!} decimals={9} />{" "}
|
||||
<FormattedBalance
|
||||
value={txData.confirmedData.blockBaseFeePerGas!}
|
||||
decimals={9}
|
||||
/>{" "}
|
||||
Gwei (
|
||||
<FormattedBalance
|
||||
value={txData.blockBaseFeePerGas!}
|
||||
value={txData.confirmedData.blockBaseFeePerGas!}
|
||||
decimals={0}
|
||||
/>{" "}
|
||||
wei)
|
||||
</span>
|
||||
</InfoRow>
|
||||
)}
|
||||
<InfoRow title="Transaction Fee">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<FormattedBalance value={txData.fee} /> Ether
|
||||
</div>
|
||||
{hasEIP1559 && <RewardSplit txData={txData} />}
|
||||
</div>
|
||||
</InfoRow>
|
||||
<InfoRow title="Ether Price">N/A</InfoRow>
|
||||
{txData.confirmedData && (
|
||||
<>
|
||||
<InfoRow title="Transaction Fee">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<FormattedBalance value={txData.confirmedData.fee} /> Ether{" "}
|
||||
{ethUSDPrice && (
|
||||
<span className="px-2 border-skin-from border rounded-lg bg-skin-from text-skin-from">
|
||||
<ETH2USDValue
|
||||
ethAmount={txData.confirmedData.fee}
|
||||
eth2USDValue={ethUSDPrice}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{hasEIP1559 && <RewardSplit txData={txData} />}
|
||||
</div>
|
||||
</InfoRow>
|
||||
<InfoRow title="Ether Price">
|
||||
<USDValue value={ethUSDPrice} />
|
||||
</InfoRow>
|
||||
</>
|
||||
)}
|
||||
<InfoRow title="Input Data">
|
||||
<div className="space-y-1">
|
||||
<div className="flex space-x-1">
|
||||
|
|
|
@ -10,8 +10,8 @@ type LogsProps = {
|
|||
const Logs: React.FC<LogsProps> = ({ txData }) => (
|
||||
<ContentFrame tabs>
|
||||
<div className="text-sm py-4">Transaction Receipt Event Logs</div>
|
||||
{txData &&
|
||||
txData.logs.map((l, i) => (
|
||||
{txData.confirmedData &&
|
||||
txData.confirmedData.logs.map((l, i) => (
|
||||
<div className="flex space-x-10 py-5" key={i}>
|
||||
<div>
|
||||
<span className="rounded-full w-12 h-12 flex items-center justify-center bg-green-50 text-green-500">
|
||||
|
@ -24,7 +24,7 @@ const Logs: React.FC<LogsProps> = ({ txData }) => (
|
|||
<div className="col-span-11 mr-auto">
|
||||
<DecoratedAddressLink
|
||||
address={l.address}
|
||||
miner={l.address === txData.miner}
|
||||
miner={l.address === txData.confirmedData?.miner}
|
||||
txFrom={l.address === txData.from}
|
||||
txTo={l.address === txData.to}
|
||||
/>
|
||||
|
|
|
@ -11,8 +11,10 @@ type RewardSplitProps = {
|
|||
};
|
||||
|
||||
const RewardSplit: React.FC<RewardSplitProps> = ({ 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 =
|
||||
|
|
28
src/types.ts
28
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[];
|
||||
};
|
||||
|
||||
|
|
|
@ -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>();
|
||||
): TransactionData | undefined | null => {
|
||||
const [txData, setTxData] = useState<TransactionData | undefined | null>();
|
||||
|
||||
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<InternalOperation[]>();
|
||||
|
||||
useEffect(() => {
|
||||
const traceTransfers = async () => {
|
||||
if (!provider || !txData) {
|
||||
if (!provider || !txData || !txData.confirmedData) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<BlockTag, BigNumber>
|
||||
>({});
|
||||
useEffect(() => {
|
||||
if (!ethFeed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const priceReaders: Promise<BigNumber | undefined>[] = [];
|
||||
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<BlockTag, BigNumber> = {};
|
||||
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;
|
||||
};
|
|
@ -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: {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 9bc40f37d95234810bc7e176513c8366c81080ce
|
||||
Subproject commit 7bfa06acc125a4874d86bc1fa8e4547a46846e31
|
Loading…
Reference in New Issue