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.
|
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
|
cd otterscan
|
||||||
git checkout <version-tag-otterscan>
|
git checkout <version-tag-otterscan>
|
||||||
DOCKER_BUILDKIT=1 docker build -t otterscan -f Dockerfile .
|
DOCKER_BUILDKIT=1 docker build -t otterscan -f Dockerfile .
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "otterscan",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@blackbox-vision/react-qr-reader": "^5.0.0",
|
||||||
"@chainlink/contracts": "^0.2.1",
|
"@chainlink/contracts": "^0.2.1",
|
||||||
"@craco/craco": "^6.2.0",
|
"@craco/craco": "^6.2.0",
|
||||||
"@fontsource/fira-code": "^4.5.0",
|
"@fontsource/fira-code": "^4.5.1",
|
||||||
"@fontsource/roboto": "^4.5.0",
|
"@fontsource/roboto": "^4.5.0",
|
||||||
"@fontsource/roboto-mono": "^4.5.0",
|
"@fontsource/roboto-mono": "^4.5.0",
|
||||||
"@fontsource/space-grotesk": "^4.5.0",
|
"@fontsource/space-grotesk": "^4.5.0",
|
||||||
|
@ -19,17 +21,17 @@
|
||||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.15",
|
"@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/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/node": "^14.17.5",
|
"@types/node": "^14.17.5",
|
||||||
"@types/react": "^17.0.17",
|
"@types/react": "^17.0.19",
|
||||||
"@types/react-blockies": "^1.4.1",
|
"@types/react-blockies": "^1.4.1",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"@types/react-router-dom": "^5.1.8",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
"chart.js": "^3.5.0",
|
"chart.js": "^3.5.1",
|
||||||
"ethers": "^5.4.1",
|
"ethers": "^5.4.1",
|
||||||
"query-string": "^7.0.1",
|
"query-string": "^7.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
@ -38,10 +40,10 @@
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-error-boundary": "^3.1.3",
|
"react-error-boundary": "^3.1.3",
|
||||||
"react-image": "^4.0.3",
|
"react-image": "^4.0.3",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.1",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"serve": "^12.0.0",
|
"serve": "^12.0.0",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.4.2",
|
||||||
"use-keyboard-shortcut": "^1.0.6",
|
"use-keyboard-shortcut": "^1.0.6",
|
||||||
"web-vitals": "^1.0.1"
|
"web-vitals": "^1.0.1"
|
||||||
},
|
},
|
||||||
|
@ -1208,6 +1210,19 @@
|
||||||
"version": "0.2.3",
|
"version": "0.2.3",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@chainlink/contracts": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz",
|
||||||
|
@ -1993,9 +2008,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/fira-code": {
|
"node_modules/@fontsource/fira-code": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.1.tgz",
|
||||||
"integrity": "sha512-fxRV3qt0eJaIXZvICXZMhXVR0lSyxZTC0cnM+1Ma/1JShGrIjCQ3yJ0W05rwaEoF3cAbpU2lKMrXfE7Of/zpIA=="
|
"integrity": "sha512-8KTCsfs5m3UgICpHLglIKAS7vc2FFOu7/vvpWcE/42SWbh+9X8EJbEyJp6W96kU5iDVlAlUv4Cqc36Z9XUpLmA=="
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/roboto": {
|
"node_modules/@fontsource/roboto": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
|
@ -2111,9 +2126,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@headlessui/react": {
|
"node_modules/@headlessui/react": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.1.tgz",
|
||||||
"integrity": "sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw==",
|
"integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
|
@ -3063,9 +3078,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "17.0.17",
|
"version": "17.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.19.tgz",
|
||||||
"integrity": "sha512-nrfi7I13cAmrd0wje8czYpf5SFbryczCtPzFc6ijqvdjKcyA3tCvGxwchOUlxb2ucBPuJ9Y3oUqKrRqZvrz0lw==",
|
"integrity": "sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
@ -3511,6 +3526,37 @@
|
||||||
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz",
|
||||||
"integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg=="
|
"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": {
|
"node_modules/abab": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
|
@ -5530,9 +5576,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chart.js": {
|
"node_modules/chart.js": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz",
|
||||||
"integrity": "sha512-J1a4EAb1Gi/KbhwDRmoovHTRuqT8qdF0kZ4XgwxpGethJHUdDrkqyPYwke0a+BuvSeUxPf8Cos6AX2AB8H8GLA=="
|
"integrity": "sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ=="
|
||||||
},
|
},
|
||||||
"node_modules/check-types": {
|
"node_modules/check-types": {
|
||||||
"version": "11.1.2",
|
"version": "11.1.2",
|
||||||
|
@ -14395,11 +14441,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz",
|
||||||
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
|
"integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.1.2",
|
"@babel/runtime": "^7.12.13",
|
||||||
"history": "^4.9.0",
|
"history": "^4.9.0",
|
||||||
"hoist-non-react-statics": "^3.1.0",
|
"hoist-non-react-statics": "^3.1.0",
|
||||||
"loose-envify": "^1.3.1",
|
"loose-envify": "^1.3.1",
|
||||||
|
@ -14415,15 +14461,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.1.tgz",
|
||||||
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
|
"integrity": "sha512-xhFFkBGVcIVPbWM2KEYzED+nuHQPmulVa7sqIs3ESxzYd1pYg8N8rxPnQ4T2o1zu/2QeDUWcaqST131SO1LR3w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.1.2",
|
"@babel/runtime": "^7.12.13",
|
||||||
"history": "^4.9.0",
|
"history": "^4.9.0",
|
||||||
"loose-envify": "^1.3.1",
|
"loose-envify": "^1.3.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react-router": "5.2.0",
|
"react-router": "5.2.1",
|
||||||
"tiny-invariant": "^1.0.2",
|
"tiny-invariant": "^1.0.2",
|
||||||
"tiny-warning": "^1.0.0"
|
"tiny-warning": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
@ -17824,6 +17870,14 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/ts-pnp": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -17936,9 +17990,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.3.5",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
|
||||||
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
|
"integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
@ -20239,6 +20293,15 @@
|
||||||
"@bcoe/v8-coverage": {
|
"@bcoe/v8-coverage": {
|
||||||
"version": "0.2.3"
|
"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": {
|
"@chainlink/contracts": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz",
|
||||||
|
@ -20678,9 +20741,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fontsource/fira-code": {
|
"@fontsource/fira-code": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.1.tgz",
|
||||||
"integrity": "sha512-fxRV3qt0eJaIXZvICXZMhXVR0lSyxZTC0cnM+1Ma/1JShGrIjCQ3yJ0W05rwaEoF3cAbpU2lKMrXfE7Of/zpIA=="
|
"integrity": "sha512-8KTCsfs5m3UgICpHLglIKAS7vc2FFOu7/vvpWcE/42SWbh+9X8EJbEyJp6W96kU5iDVlAlUv4Cqc36Z9XUpLmA=="
|
||||||
},
|
},
|
||||||
"@fontsource/roboto": {
|
"@fontsource/roboto": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
|
@ -20767,9 +20830,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@headlessui/react": {
|
"@headlessui/react": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.1.tgz",
|
||||||
"integrity": "sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw==",
|
"integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@istanbuljs/load-nyc-config": {
|
"@istanbuljs/load-nyc-config": {
|
||||||
|
@ -21377,9 +21440,9 @@
|
||||||
"version": "1.5.4"
|
"version": "1.5.4"
|
||||||
},
|
},
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
"version": "17.0.17",
|
"version": "17.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.19.tgz",
|
||||||
"integrity": "sha512-nrfi7I13cAmrd0wje8czYpf5SFbryczCtPzFc6ijqvdjKcyA3tCvGxwchOUlxb2ucBPuJ9Y3oUqKrRqZvrz0lw==",
|
"integrity": "sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
@ -21699,6 +21762,29 @@
|
||||||
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz",
|
||||||
"integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg=="
|
"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": {
|
"abab": {
|
||||||
"version": "2.0.5"
|
"version": "2.0.5"
|
||||||
},
|
},
|
||||||
|
@ -23101,9 +23187,9 @@
|
||||||
"version": "1.0.2"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
"chart.js": {
|
"chart.js": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz",
|
||||||
"integrity": "sha512-J1a4EAb1Gi/KbhwDRmoovHTRuqT8qdF0kZ4XgwxpGethJHUdDrkqyPYwke0a+BuvSeUxPf8Cos6AX2AB8H8GLA=="
|
"integrity": "sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ=="
|
||||||
},
|
},
|
||||||
"check-types": {
|
"check-types": {
|
||||||
"version": "11.1.2"
|
"version": "11.1.2"
|
||||||
|
@ -28992,11 +29078,11 @@
|
||||||
"version": "0.8.3"
|
"version": "0.8.3"
|
||||||
},
|
},
|
||||||
"react-router": {
|
"react-router": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz",
|
||||||
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
|
"integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.1.2",
|
"@babel/runtime": "^7.12.13",
|
||||||
"history": "^4.9.0",
|
"history": "^4.9.0",
|
||||||
"hoist-non-react-statics": "^3.1.0",
|
"hoist-non-react-statics": "^3.1.0",
|
||||||
"loose-envify": "^1.3.1",
|
"loose-envify": "^1.3.1",
|
||||||
|
@ -29029,15 +29115,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-router-dom": {
|
"react-router-dom": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.1.tgz",
|
||||||
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
|
"integrity": "sha512-xhFFkBGVcIVPbWM2KEYzED+nuHQPmulVa7sqIs3ESxzYd1pYg8N8rxPnQ4T2o1zu/2QeDUWcaqST131SO1LR3w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.1.2",
|
"@babel/runtime": "^7.12.13",
|
||||||
"history": "^4.9.0",
|
"history": "^4.9.0",
|
||||||
"loose-envify": "^1.3.1",
|
"loose-envify": "^1.3.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react-router": "5.2.0",
|
"react-router": "5.2.1",
|
||||||
"tiny-invariant": "^1.0.2",
|
"tiny-invariant": "^1.0.2",
|
||||||
"tiny-warning": "^1.0.0"
|
"tiny-warning": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -31386,6 +31472,11 @@
|
||||||
"tryer": {
|
"tryer": {
|
||||||
"version": "1.0.1"
|
"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": {
|
"ts-pnp": {
|
||||||
"version": "1.2.0"
|
"version": "1.2.0"
|
||||||
},
|
},
|
||||||
|
@ -31456,9 +31547,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.3.5",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
|
||||||
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA=="
|
"integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ=="
|
||||||
},
|
},
|
||||||
"unicode-canonical-property-names-ecmascript": {
|
"unicode-canonical-property-names-ecmascript": {
|
||||||
"version": "1.0.4"
|
"version": "1.0.4"
|
||||||
|
|
13
package.json
13
package.json
|
@ -4,9 +4,10 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@blackbox-vision/react-qr-reader": "^5.0.0",
|
||||||
"@chainlink/contracts": "^0.2.1",
|
"@chainlink/contracts": "^0.2.1",
|
||||||
"@craco/craco": "^6.2.0",
|
"@craco/craco": "^6.2.0",
|
||||||
"@fontsource/fira-code": "^4.5.0",
|
"@fontsource/fira-code": "^4.5.1",
|
||||||
"@fontsource/roboto": "^4.5.0",
|
"@fontsource/roboto": "^4.5.0",
|
||||||
"@fontsource/roboto-mono": "^4.5.0",
|
"@fontsource/roboto-mono": "^4.5.0",
|
||||||
"@fontsource/space-grotesk": "^4.5.0",
|
"@fontsource/space-grotesk": "^4.5.0",
|
||||||
|
@ -15,17 +16,17 @@
|
||||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.15",
|
"@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/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/node": "^14.17.5",
|
"@types/node": "^14.17.5",
|
||||||
"@types/react": "^17.0.17",
|
"@types/react": "^17.0.19",
|
||||||
"@types/react-blockies": "^1.4.1",
|
"@types/react-blockies": "^1.4.1",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"@types/react-router-dom": "^5.1.8",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
"chart.js": "^3.5.0",
|
"chart.js": "^3.5.1",
|
||||||
"ethers": "^5.4.1",
|
"ethers": "^5.4.1",
|
||||||
"query-string": "^7.0.1",
|
"query-string": "^7.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
@ -34,10 +35,10 @@
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-error-boundary": "^3.1.3",
|
"react-error-boundary": "^3.1.3",
|
||||||
"react-image": "^4.0.3",
|
"react-image": "^4.0.3",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.1",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"serve": "^12.0.0",
|
"serve": "^12.0.0",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.4.2",
|
||||||
"use-keyboard-shortcut": "^1.0.6",
|
"use-keyboard-shortcut": "^1.0.6",
|
||||||
"web-vitals": "^1.0.1"
|
"web-vitals": "^1.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/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
|
echo $PARAMS > /usr/share/nginx/html/config.json
|
||||||
nginx -g "daemon off;"
|
nginx -g "daemon off;"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useState, useEffect, useMemo, useContext } from "react";
|
import React, { useState, useEffect, useMemo, useContext } from "react";
|
||||||
import { useParams, useLocation, useHistory } from "react-router-dom";
|
import { useParams, useLocation, useHistory } from "react-router-dom";
|
||||||
|
import { BlockTag } from "@ethersproject/abstract-provider";
|
||||||
import { getAddress, isAddress } from "@ethersproject/address";
|
import { getAddress, isAddress } from "@ethersproject/address";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import Blockies from "react-blockies";
|
import Blockies from "react-blockies";
|
||||||
|
@ -16,6 +17,7 @@ import { RuntimeContext } from "./useRuntime";
|
||||||
import { useENSCache } from "./useReverseCache";
|
import { useENSCache } from "./useReverseCache";
|
||||||
import { useFeeToggler } from "./search/useFeeToggler";
|
import { useFeeToggler } from "./search/useFeeToggler";
|
||||||
import { SelectionContext, useSelection } from "./useSelection";
|
import { SelectionContext, useSelection } from "./useSelection";
|
||||||
|
import { useMultipleETHUSDOracle } from "./usePriceOracle";
|
||||||
|
|
||||||
type BlockParams = {
|
type BlockParams = {
|
||||||
addressOrName: string;
|
addressOrName: string;
|
||||||
|
@ -150,6 +152,14 @@ const AddressTransactions: React.FC = () => {
|
||||||
const page = useMemo(() => controller?.getPage(), [controller]);
|
const page = useMemo(() => controller?.getPage(), [controller]);
|
||||||
const reverseCache = useENSCache(provider, page);
|
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`;
|
document.title = `Address ${params.addressOrName} | Otterscan`;
|
||||||
|
|
||||||
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
||||||
|
@ -215,6 +225,7 @@ const AddressTransactions: React.FC = () => {
|
||||||
ensCache={reverseCache}
|
ensCache={reverseCache}
|
||||||
selectedAddress={checksummedAddress}
|
selectedAddress={checksummedAddress}
|
||||||
feeDisplay={feeDisplay}
|
feeDisplay={feeDisplay}
|
||||||
|
priceMap={priceMap}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div className="flex justify-between items-baseline py-3">
|
<div className="flex justify-between items-baseline py-3">
|
||||||
|
|
|
@ -17,11 +17,14 @@ import BlockLink from "./components/BlockLink";
|
||||||
import DecoratedAddressLink from "./components/DecoratedAddressLink";
|
import DecoratedAddressLink from "./components/DecoratedAddressLink";
|
||||||
import TransactionValue from "./components/TransactionValue";
|
import TransactionValue from "./components/TransactionValue";
|
||||||
import FormattedBalance from "./components/FormattedBalance";
|
import FormattedBalance from "./components/FormattedBalance";
|
||||||
|
import ETH2USDValue from "./components/ETH2USDValue";
|
||||||
|
import USDValue from "./components/USDValue";
|
||||||
import HexValue from "./components/HexValue";
|
import HexValue from "./components/HexValue";
|
||||||
import { RuntimeContext } from "./useRuntime";
|
import { RuntimeContext } from "./useRuntime";
|
||||||
import { useLatestBlockNumber } from "./useLatestBlock";
|
import { useLatestBlockNumber } from "./useLatestBlock";
|
||||||
import { blockTxsURL } from "./url";
|
import { blockTxsURL } from "./url";
|
||||||
import { useBlockData } from "./useErigonHooks";
|
import { useBlockData } from "./useErigonHooks";
|
||||||
|
import { useETHUSDOracle } from "./usePriceOracle";
|
||||||
|
|
||||||
type BlockParams = {
|
type BlockParams = {
|
||||||
blockNumberOrHash: string;
|
blockNumberOrHash: string;
|
||||||
|
@ -48,11 +51,12 @@ const Block: React.FC = () => {
|
||||||
}, [block]);
|
}, [block]);
|
||||||
const burntFees =
|
const burntFees =
|
||||||
block?.baseFeePerGas && block.baseFeePerGas.mul(block.gasUsed);
|
block?.baseFeePerGas && block.baseFeePerGas.mul(block.gasUsed);
|
||||||
const netFeeReward = block && block.feeReward.sub(burntFees ?? 0);
|
const netFeeReward = block?.feeReward ?? BigNumber.from(0);
|
||||||
const gasUsedPerc =
|
const gasUsedPerc =
|
||||||
block && block.gasUsed.mul(10000).div(block.gasLimit).toNumber() / 100;
|
block && block.gasUsed.mul(10000).div(block.gasLimit).toNumber() / 100;
|
||||||
|
|
||||||
const latestBlockNumber = useLatestBlockNumber(provider);
|
const latestBlockNumber = useLatestBlockNumber(provider);
|
||||||
|
const blockETHUSDPrice = useETHUSDOracle(provider, block?.number);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StandardFrame>
|
<StandardFrame>
|
||||||
|
@ -91,18 +95,23 @@ const Block: React.FC = () => {
|
||||||
<DecoratedAddressLink address={block.miner} miner />
|
<DecoratedAddressLink address={block.miner} miner />
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Block Reward">
|
<InfoRow title="Block Reward">
|
||||||
<TransactionValue
|
<TransactionValue value={block.blockReward.add(netFeeReward)} />
|
||||||
value={block.blockReward.add(netFeeReward ?? 0)}
|
{!netFeeReward.isZero() && (
|
||||||
/>
|
|
||||||
{!block.feeReward.isZero() && (
|
|
||||||
<>
|
<>
|
||||||
{" "}
|
{" "}
|
||||||
(<TransactionValue value={block.blockReward} hideUnit /> +{" "}
|
(<TransactionValue value={block.blockReward} hideUnit /> +{" "}
|
||||||
<TransactionValue
|
<TransactionValue value={netFeeReward} hideUnit />)
|
||||||
value={netFeeReward ?? BigNumber.from(0)}
|
</>
|
||||||
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>
|
</InfoRow>
|
||||||
|
@ -153,7 +162,9 @@ const Block: React.FC = () => {
|
||||||
{extraStr} (Hex:{" "}
|
{extraStr} (Hex:{" "}
|
||||||
<span className="font-data">{block.extraData}</span>)
|
<span className="font-data">{block.extraData}</span>)
|
||||||
</InfoRow>
|
</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="Difficult">{commify(block.difficulty)}</InfoRow>
|
||||||
<InfoRow title="Total Difficult">
|
<InfoRow title="Total Difficult">
|
||||||
{commify(block.totalDifficulty.toString())}
|
{commify(block.totalDifficulty.toString())}
|
||||||
|
|
|
@ -47,6 +47,7 @@ const BlockTransactions: React.FC = () => {
|
||||||
<StandardFrame>
|
<StandardFrame>
|
||||||
<BlockTransactionHeader blockTag={blockNumber.toNumber()} />
|
<BlockTransactionHeader blockTag={blockNumber.toNumber()} />
|
||||||
<BlockTransactionResults
|
<BlockTransactionResults
|
||||||
|
blockTag={blockNumber.toNumber()}
|
||||||
page={txs}
|
page={txs}
|
||||||
total={totalTxs ?? 0}
|
total={totalTxs ?? 0}
|
||||||
pageNumber={pageNumber}
|
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 { commify } from "@ethersproject/units";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
|
import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
|
||||||
|
import { faQrcode } from "@fortawesome/free-solid-svg-icons/faQrcode";
|
||||||
import Logo from "./Logo";
|
import Logo from "./Logo";
|
||||||
|
import CameraScanner from "./search/CameraScanner";
|
||||||
import Timestamp from "./components/Timestamp";
|
import Timestamp from "./components/Timestamp";
|
||||||
import { RuntimeContext } from "./useRuntime";
|
import { RuntimeContext } from "./useRuntime";
|
||||||
import { useLatestBlock } from "./useLatestBlock";
|
import { useLatestBlock } from "./useLatestBlock";
|
||||||
|
@ -30,11 +32,13 @@ const Home: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const latestBlock = useLatestBlock(provider);
|
const latestBlock = useLatestBlock(provider);
|
||||||
|
const [isScanning, setScanning] = useState<boolean>(false);
|
||||||
|
|
||||||
document.title = "Home | Otterscan";
|
document.title = "Home | Otterscan";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="m-auto">
|
<div className="m-auto">
|
||||||
|
{isScanning && <CameraScanner turnOffScan={() => setScanning(false)} />}
|
||||||
<Logo />
|
<Logo />
|
||||||
<form
|
<form
|
||||||
className="flex flex-col"
|
className="flex flex-col"
|
||||||
|
@ -42,16 +46,26 @@ const Home: React.FC = () => {
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
>
|
>
|
||||||
<input
|
<div className="flex mb-10">
|
||||||
className="w-full border rounded focus:outline-none px-2 py-1 mb-10"
|
<input
|
||||||
type="text"
|
className="w-full border-l border-t border-b rounded-l focus:outline-none px-2 py-1"
|
||||||
size={50}
|
type="text"
|
||||||
placeholder="Search by address / txn hash / block number / ENS name"
|
size={50}
|
||||||
onChange={handleChange}
|
placeholder="Search by address / txn hash / block number / ENS name"
|
||||||
autoFocus
|
onChange={handleChange}
|
||||||
></input>
|
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
|
<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"
|
type="submit"
|
||||||
>
|
>
|
||||||
Search
|
Search
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import React, { useState, useRef, useContext } from "react";
|
import React, { useState, useRef, useContext } from "react";
|
||||||
import { Link, useHistory } from "react-router-dom";
|
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 useKeyboardShortcut from "use-keyboard-shortcut";
|
||||||
import PriceBox from "./PriceBox";
|
import PriceBox from "./PriceBox";
|
||||||
|
import CameraScanner from "./search/CameraScanner";
|
||||||
import { RuntimeContext } from "./useRuntime";
|
import { RuntimeContext } from "./useRuntime";
|
||||||
|
|
||||||
const Title: React.FC = () => {
|
const Title: React.FC = () => {
|
||||||
|
@ -29,46 +32,59 @@ const Title: React.FC = () => {
|
||||||
searchRef.current?.focus();
|
searchRef.current?.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [isScanning, setScanning] = useState<boolean>(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-9 py-2 flex justify-between items-baseline">
|
<>
|
||||||
<Link className="self-center" to="/">
|
{isScanning && <CameraScanner turnOffScan={() => setScanning(false)} />}
|
||||||
<div className="text-2xl text-link-blue font-title font-bold flex items-center space-x-2">
|
<div className="px-9 py-2 flex justify-between items-baseline">
|
||||||
<img
|
<Link className="self-center" to="/">
|
||||||
className="rounded-full"
|
<div className="text-2xl text-link-blue font-title font-bold flex items-center space-x-2">
|
||||||
src="/otter.jpg"
|
<img
|
||||||
width={32}
|
className="rounded-full"
|
||||||
height={32}
|
src="/otter.jpg"
|
||||||
alt="An otter scanning"
|
width={32}
|
||||||
title="An otter scanning"
|
height={32}
|
||||||
/>
|
alt="An otter scanning"
|
||||||
<span>Otterscan</span>
|
title="An otter scanning"
|
||||||
</div>
|
/>
|
||||||
</Link>
|
<span>Otterscan</span>
|
||||||
<div className="flex items-baseline space-x-3">
|
</div>
|
||||||
{provider?.network.chainId === 1 && <PriceBox />}
|
</Link>
|
||||||
<form
|
<div className="flex items-baseline space-x-3">
|
||||||
className="flex"
|
{provider?.network.chainId === 1 && <PriceBox />}
|
||||||
onSubmit={handleSubmit}
|
<form
|
||||||
autoComplete="off"
|
className="flex"
|
||||||
spellCheck={false}
|
onSubmit={handleSubmit}
|
||||||
>
|
autoComplete="off"
|
||||||
<input
|
spellCheck={false}
|
||||||
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"
|
|
||||||
>
|
>
|
||||||
Search
|
<input
|
||||||
</button>
|
className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm"
|
||||||
</form>
|
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>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,14 @@ import React, { useMemo, useContext } from "react";
|
||||||
import { Route, Switch, useParams } from "react-router-dom";
|
import { Route, Switch, useParams } from "react-router-dom";
|
||||||
import StandardFrame from "./StandardFrame";
|
import StandardFrame from "./StandardFrame";
|
||||||
import StandardSubtitle from "./StandardSubtitle";
|
import StandardSubtitle from "./StandardSubtitle";
|
||||||
|
import ContentFrame from "./ContentFrame";
|
||||||
import Tab from "./components/Tab";
|
import Tab from "./components/Tab";
|
||||||
import Details from "./transaction/Details";
|
import Details from "./transaction/Details";
|
||||||
import Logs from "./transaction/Logs";
|
import Logs from "./transaction/Logs";
|
||||||
import { RuntimeContext } from "./useRuntime";
|
import { RuntimeContext } from "./useRuntime";
|
||||||
import { SelectionContext, useSelection } from "./useSelection";
|
import { SelectionContext, useSelection } from "./useSelection";
|
||||||
import { useInternalOperations, useTxData } from "./useErigonHooks";
|
import { useInternalOperations, useTxData } from "./useErigonHooks";
|
||||||
|
import { useETHUSDOracle } from "./usePriceOracle";
|
||||||
|
|
||||||
type TransactionParams = {
|
type TransactionParams = {
|
||||||
txhash: string;
|
txhash: string;
|
||||||
|
@ -27,7 +29,7 @@ const Transaction: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const t of internalOps) {
|
for (const t of internalOps) {
|
||||||
if (t.to === txData.miner) {
|
if (t.to === txData.confirmedData?.miner) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,16 +38,30 @@ const Transaction: React.FC = () => {
|
||||||
|
|
||||||
const selectionCtx = useSelection();
|
const selectionCtx = useSelection();
|
||||||
|
|
||||||
|
const blockETHUSDPrice = useETHUSDOracle(
|
||||||
|
provider,
|
||||||
|
txData?.confirmedData?.blockNumber
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StandardFrame>
|
<StandardFrame>
|
||||||
<StandardSubtitle>Transaction Details</StandardSubtitle>
|
<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 && (
|
{txData && (
|
||||||
<SelectionContext.Provider value={selectionCtx}>
|
<SelectionContext.Provider value={selectionCtx}>
|
||||||
<div className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
|
<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}`}>Overview</Tab>
|
||||||
<Tab href={`/tx/${txhash}/logs`}>
|
{txData.confirmedData?.blockNumber !== undefined && (
|
||||||
Logs{txData && ` (${txData.logs.length})`}
|
<Tab href={`/tx/${txhash}/logs`}>
|
||||||
</Tab>
|
Logs{txData && ` (${txData.confirmedData?.logs?.length ?? 0})`}
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/tx/:txhash/" exact>
|
<Route path="/tx/:txhash/" exact>
|
||||||
|
@ -53,6 +69,7 @@ const Transaction: React.FC = () => {
|
||||||
txData={txData}
|
txData={txData}
|
||||||
internalOps={internalOps}
|
internalOps={internalOps}
|
||||||
sendsEthToMiner={sendsEthToMiner}
|
sendsEthToMiner={sendsEthToMiner}
|
||||||
|
ethUSDPrice={blockETHUSDPrice}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/tx/:txhash/logs/" exact>
|
<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 ContentFrame from "../ContentFrame";
|
||||||
import PageControl from "../search/PageControl";
|
import PageControl from "../search/PageControl";
|
||||||
import ResultHeader from "../search/ResultHeader";
|
import ResultHeader from "../search/ResultHeader";
|
||||||
|
@ -10,14 +11,17 @@ import { SelectionContext, useSelection } from "../useSelection";
|
||||||
import { useENSCache } from "../useReverseCache";
|
import { useENSCache } from "../useReverseCache";
|
||||||
import { ProcessedTransaction } from "../types";
|
import { ProcessedTransaction } from "../types";
|
||||||
import { PAGE_SIZE } from "../params";
|
import { PAGE_SIZE } from "../params";
|
||||||
|
import { useMultipleETHUSDOracle } from "../usePriceOracle";
|
||||||
|
|
||||||
type BlockTransactionResultsProps = {
|
type BlockTransactionResultsProps = {
|
||||||
|
blockTag: BlockTag;
|
||||||
page?: ProcessedTransaction[];
|
page?: ProcessedTransaction[];
|
||||||
total: number;
|
total: number;
|
||||||
pageNumber: number;
|
pageNumber: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
||||||
|
blockTag,
|
||||||
page,
|
page,
|
||||||
total,
|
total,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
|
@ -26,6 +30,8 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
||||||
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
||||||
const { provider } = useContext(RuntimeContext);
|
const { provider } = useContext(RuntimeContext);
|
||||||
const reverseCache = useENSCache(provider, page);
|
const reverseCache = useENSCache(provider, page);
|
||||||
|
const blockTags = useMemo(() => [blockTag], [blockTag]);
|
||||||
|
const priceMap = useMultipleETHUSDOracle(provider, blockTags);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentFrame>
|
<ContentFrame>
|
||||||
|
@ -55,6 +61,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
||||||
tx={tx}
|
tx={tx}
|
||||||
ensCache={reverseCache}
|
ensCache={reverseCache}
|
||||||
feeDisplay={feeDisplay}
|
feeDisplay={feeDisplay}
|
||||||
|
priceMap={priceMap}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div className="flex justify-between items-baseline py-3">
|
<div className="flex justify-between items-baseline py-3">
|
||||||
|
|
|
@ -41,11 +41,13 @@ const DecoratedAddresssLink: React.FC<DecoratedAddressLinkProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-baseline space-x-1 ${txFrom ? "bg-red-50" : ""} ${
|
className={`flex items-baseline space-x-1 ${
|
||||||
txTo ? "bg-green-50" : ""
|
txFrom ? "bg-skin-from" : ""
|
||||||
} ${mint ? "italic text-green-500 hover:text-green-700" : ""} ${
|
} ${txTo ? "bg-skin-to" : ""} ${
|
||||||
burn ? "line-through text-orange-500 hover:text-orange-700" : ""
|
mint ? "italic text-green-500 hover:text-green-700" : ""
|
||||||
} ${selfDestruct ? "line-through opacity-70 hover:opacity-100" : ""}`}
|
} ${burn ? "line-through text-orange-500 hover:text-orange-700" : ""} ${
|
||||||
|
selfDestruct ? "line-through opacity-70 hover:opacity-100" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{creation && (
|
{creation && (
|
||||||
<span className="text-yellow-300" title="Contract 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 { provider } = useContext(RuntimeContext);
|
||||||
const network = provider?.network;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -16,8 +16,11 @@ const InternalTransfer: React.FC<InternalTransferProps> = ({
|
||||||
internalOp,
|
internalOp,
|
||||||
}) => {
|
}) => {
|
||||||
const fromMiner =
|
const fromMiner =
|
||||||
txData.miner !== undefined && internalOp.from === txData.miner;
|
txData.confirmedData?.miner !== undefined &&
|
||||||
const toMiner = txData.miner !== undefined && internalOp.to === txData.miner;
|
internalOp.from === txData.confirmedData.miner;
|
||||||
|
const toMiner =
|
||||||
|
txData.confirmedData?.miner !== undefined &&
|
||||||
|
internalOp.to === txData.confirmedData.miner;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-baseline space-x-1 text-xs">
|
<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 base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@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"
|
className="text-link-blue hover:text-link-blue-hover"
|
||||||
onClick={feeDisplayToggler}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { BlockTag } from "@ethersproject/abstract-provider";
|
||||||
|
import { BigNumber } from "@ethersproject/bignumber";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||||
import MethodName from "../components/MethodName";
|
import MethodName from "../components/MethodName";
|
||||||
|
@ -15,12 +17,14 @@ import TransactionValue from "../components/TransactionValue";
|
||||||
import { ENSReverseCache, ProcessedTransaction } from "../types";
|
import { ENSReverseCache, ProcessedTransaction } from "../types";
|
||||||
import { FeeDisplay } from "./useFeeToggler";
|
import { FeeDisplay } from "./useFeeToggler";
|
||||||
import { formatValue } from "../components/formatter";
|
import { formatValue } from "../components/formatter";
|
||||||
|
import ETH2USDValue from "../components/ETH2USDValue";
|
||||||
|
|
||||||
type TransactionItemProps = {
|
type TransactionItemProps = {
|
||||||
tx: ProcessedTransaction;
|
tx: ProcessedTransaction;
|
||||||
ensCache?: ENSReverseCache;
|
ensCache?: ENSReverseCache;
|
||||||
selectedAddress?: string;
|
selectedAddress?: string;
|
||||||
feeDisplay: FeeDisplay;
|
feeDisplay: FeeDisplay;
|
||||||
|
priceMap: Record<BlockTag, BigNumber>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TransactionItem: React.FC<TransactionItemProps> = ({
|
const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
|
@ -28,6 +32,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
ensCache,
|
ensCache,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
feeDisplay,
|
feeDisplay,
|
||||||
|
priceMap,
|
||||||
}) => {
|
}) => {
|
||||||
let direction: Direction | undefined;
|
let direction: Direction | undefined;
|
||||||
if (selectedAddress) {
|
if (selectedAddress) {
|
||||||
|
@ -56,7 +61,9 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`grid grid-cols-12 gap-x-1 items-baseline text-sm border-t border-gray-200 ${
|
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`}
|
} px-2 py-3`}
|
||||||
>
|
>
|
||||||
<div className="col-span-2 flex space-x-1 items-baseline">
|
<div className="col-span-2 flex space-x-1 items-baseline">
|
||||||
|
@ -121,9 +128,17 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
<TransactionValue value={tx.value} />
|
<TransactionValue value={tx.value} />
|
||||||
</span>
|
</span>
|
||||||
<span className="font-balance text-xs text-gray-500 truncate">
|
<span className="font-balance text-xs text-gray-500 truncate">
|
||||||
{feeDisplay === FeeDisplay.TX_FEE
|
{feeDisplay === FeeDisplay.TX_FEE && formatValue(tx.fee, 18)}
|
||||||
? formatValue(tx.fee, 18)
|
{feeDisplay === FeeDisplay.TX_FEE_USD &&
|
||||||
: formatValue(tx.gasPrice, 9)}
|
(priceMap[tx.blockNumber] ? (
|
||||||
|
<ETH2USDValue
|
||||||
|
ethAmount={tx.fee}
|
||||||
|
eth2USDValue={priceMap[tx.blockNumber]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
"N/A"
|
||||||
|
))}
|
||||||
|
{feeDisplay === FeeDisplay.GAS_PRICE && formatValue(tx.gasPrice, 9)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,16 +2,16 @@ import { useState } from "react";
|
||||||
|
|
||||||
export enum FeeDisplay {
|
export enum FeeDisplay {
|
||||||
TX_FEE,
|
TX_FEE,
|
||||||
|
TX_FEE_USD,
|
||||||
GAS_PRICE,
|
GAS_PRICE,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFeeToggler = (): [FeeDisplay, () => void] => {
|
export const useFeeToggler = (): [FeeDisplay, () => void] => {
|
||||||
const [feeDisplay, setFeeDisplay] = useState<FeeDisplay>(FeeDisplay.TX_FEE);
|
const [feeDisplay, setFeeDisplay] = useState<FeeDisplay>(FeeDisplay.TX_FEE);
|
||||||
const feeDisplayToggler = () => {
|
const feeDisplayToggler = () => {
|
||||||
if (feeDisplay === FeeDisplay.TX_FEE) {
|
setFeeDisplay(feeDisplay + 1);
|
||||||
setFeeDisplay(FeeDisplay.GAS_PRICE);
|
if (feeDisplay === FeeDisplay.GAS_PRICE) {
|
||||||
} else {
|
setFeeDisplay(0);
|
||||||
setFeeDisplay(FeeDisplay.TX_FEE);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ const BlockRow: React.FC<BlockRowProps> = ({ now, block, baseFeeDelta }) => {
|
||||||
const totalReward = block.blockReward.add(netFeeReward ?? 0);
|
const totalReward = block.blockReward.add(netFeeReward ?? 0);
|
||||||
|
|
||||||
return (
|
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>
|
<div>
|
||||||
<BlockLink blockTag={block.number} />
|
<BlockLink blockTag={block.number} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { formatEther } from "@ethersproject/units";
|
import { BigNumber } from "@ethersproject/bignumber";
|
||||||
import { toUtf8String } from "@ethersproject/strings";
|
import { toUtf8String } from "@ethersproject/strings";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
|
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
|
||||||
|
@ -19,7 +19,9 @@ import MethodName from "../components/MethodName";
|
||||||
import TransactionType from "../components/TransactionType";
|
import TransactionType from "../components/TransactionType";
|
||||||
import RewardSplit from "./RewardSplit";
|
import RewardSplit from "./RewardSplit";
|
||||||
import GasValue from "../components/GasValue";
|
import GasValue from "../components/GasValue";
|
||||||
|
import USDValue from "../components/USDValue";
|
||||||
import FormattedBalance from "../components/FormattedBalance";
|
import FormattedBalance from "../components/FormattedBalance";
|
||||||
|
import ETH2USDValue from "../components/ETH2USDValue";
|
||||||
import TokenTransferItem from "../TokenTransferItem";
|
import TokenTransferItem from "../TokenTransferItem";
|
||||||
import { TransactionData, InternalOperation } from "../types";
|
import { TransactionData, InternalOperation } from "../types";
|
||||||
import PercentageBar from "../components/PercentageBar";
|
import PercentageBar from "../components/PercentageBar";
|
||||||
|
@ -31,16 +33,18 @@ type DetailsProps = {
|
||||||
txData: TransactionData;
|
txData: TransactionData;
|
||||||
internalOps?: InternalOperation[];
|
internalOps?: InternalOperation[];
|
||||||
sendsEthToMiner: boolean;
|
sendsEthToMiner: boolean;
|
||||||
|
ethUSDPrice: BigNumber | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Details: React.FC<DetailsProps> = ({
|
const Details: React.FC<DetailsProps> = ({
|
||||||
txData,
|
txData,
|
||||||
internalOps,
|
internalOps,
|
||||||
sendsEthToMiner,
|
sendsEthToMiner,
|
||||||
|
ethUSDPrice,
|
||||||
}) => {
|
}) => {
|
||||||
const hasEIP1559 =
|
const hasEIP1559 =
|
||||||
txData.blockBaseFeePerGas !== undefined &&
|
txData.confirmedData?.blockBaseFeePerGas !== undefined &&
|
||||||
txData.blockBaseFeePerGas !== null;
|
txData.confirmedData?.blockBaseFeePerGas !== null;
|
||||||
const [inputMode, setInputMode] = useState<number>(0);
|
const [inputMode, setInputMode] = useState<number>(0);
|
||||||
|
|
||||||
const utfInput = useMemo(() => {
|
const utfInput = useMemo(() => {
|
||||||
|
@ -62,7 +66,9 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Status">
|
<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">
|
<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" />
|
<FontAwesomeIcon icon={faCheckCircle} size="1x" />
|
||||||
<span>Success</span>
|
<span>Success</span>
|
||||||
|
@ -74,38 +80,45 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Block / Position">
|
{txData.confirmedData && (
|
||||||
<div className="flex items-baseline divide-x-2 divide-dotted divide-gray-300">
|
<>
|
||||||
<div className="flex space-x-1 items-baseline mr-3">
|
<InfoRow title="Block / Position">
|
||||||
<span className="text-orange-500">
|
<div className="flex items-baseline divide-x-2 divide-dotted divide-gray-300">
|
||||||
<FontAwesomeIcon icon={faCube} />
|
<div className="flex space-x-1 items-baseline mr-3">
|
||||||
</span>
|
<span className="text-orange-500">
|
||||||
<BlockLink blockTag={txData.blockNumber} />
|
<FontAwesomeIcon icon={faCube} />
|
||||||
<BlockConfirmations confirmations={txData.confirmations} />
|
</span>
|
||||||
</div>
|
<BlockLink blockTag={txData.confirmedData.blockNumber} />
|
||||||
<div className="flex space-x-2 items-baseline pl-3">
|
<BlockConfirmations
|
||||||
<RelativePosition
|
confirmations={txData.confirmedData.confirmations}
|
||||||
pos={txData.transactionIndex}
|
/>
|
||||||
total={txData.blockTransactionCount - 1}
|
</div>
|
||||||
/>
|
<div className="flex space-x-2 items-baseline pl-3">
|
||||||
<PercentagePosition
|
<RelativePosition
|
||||||
perc={
|
pos={txData.confirmedData.transactionIndex}
|
||||||
txData.transactionIndex / (txData.blockTransactionCount - 1)
|
total={txData.confirmedData.blockTransactionCount - 1}
|
||||||
}
|
/>
|
||||||
/>
|
<PercentagePosition
|
||||||
</div>
|
perc={
|
||||||
</div>
|
txData.confirmedData.transactionIndex /
|
||||||
</InfoRow>
|
(txData.confirmedData.blockTransactionCount - 1)
|
||||||
<InfoRow title="Timestamp">
|
}
|
||||||
<Timestamp value={txData.timestamp} />
|
/>
|
||||||
</InfoRow>
|
</div>
|
||||||
|
</div>
|
||||||
|
</InfoRow>
|
||||||
|
<InfoRow title="Timestamp">
|
||||||
|
<Timestamp value={txData.confirmedData.timestamp} />
|
||||||
|
</InfoRow>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<InfoRow title="From / Nonce">
|
<InfoRow title="From / Nonce">
|
||||||
<div className="flex divide-x-2 divide-dotted divide-gray-300">
|
<div className="flex divide-x-2 divide-dotted divide-gray-300">
|
||||||
<div className="flex items-baseline space-x-2 -ml-1 mr-3">
|
<div className="flex items-baseline space-x-2 -ml-1 mr-3">
|
||||||
<AddressHighlighter address={txData.from}>
|
<AddressHighlighter address={txData.from}>
|
||||||
<DecoratedAddressLink
|
<DecoratedAddressLink
|
||||||
address={txData.from}
|
address={txData.from}
|
||||||
miner={txData.from === txData.miner}
|
miner={txData.from === txData.confirmedData?.miner}
|
||||||
txFrom
|
txFrom
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
|
@ -122,22 +135,28 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
<AddressHighlighter address={txData.to}>
|
<AddressHighlighter address={txData.to}>
|
||||||
<DecoratedAddressLink
|
<DecoratedAddressLink
|
||||||
address={txData.to}
|
address={txData.to}
|
||||||
miner={txData.to === txData.miner}
|
miner={txData.to === txData.confirmedData?.miner}
|
||||||
txTo
|
txTo
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
<Copy value={txData.to} />
|
<Copy value={txData.to} />
|
||||||
</div>
|
</div>
|
||||||
|
) : txData.confirmedData === undefined ? (
|
||||||
|
<span className="italic text-gray-400">
|
||||||
|
Pending contract creation
|
||||||
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-baseline space-x-2 -ml-1">
|
<div className="flex items-baseline space-x-2 -ml-1">
|
||||||
<AddressHighlighter address={txData.createdContractAddress!}>
|
<AddressHighlighter
|
||||||
|
address={txData.confirmedData?.createdContractAddress!}
|
||||||
|
>
|
||||||
<DecoratedAddressLink
|
<DecoratedAddressLink
|
||||||
address={txData.createdContractAddress!}
|
address={txData.confirmedData.createdContractAddress!}
|
||||||
creation
|
creation
|
||||||
txTo
|
txTo
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
<Copy value={txData.createdContractAddress!} />
|
<Copy value={txData.confirmedData.createdContractAddress!} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{internalOps && internalOps.length > 0 && (
|
{internalOps && internalOps.length > 0 && (
|
||||||
|
@ -170,9 +189,12 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
)}
|
)}
|
||||||
<InfoRow title="Value">
|
<InfoRow title="Value">
|
||||||
<span className="rounded bg-gray-100 px-2 py-1 text-xs">
|
<FormattedBalance value={txData.value} /> Ether{" "}
|
||||||
{formatEther(txData.value)} Ether
|
{!txData.value.isZero() && ethUSDPrice && (
|
||||||
</span>
|
<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>
|
||||||
<InfoRow
|
<InfoRow
|
||||||
title={
|
title={
|
||||||
|
@ -211,58 +233,81 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<InfoRow title="Gas Price">
|
{txData.gasPrice && (
|
||||||
<div className="flex items-baseline space-x-1">
|
<InfoRow title="Gas Price">
|
||||||
<span>
|
<div className="flex items-baseline space-x-1">
|
||||||
<FormattedBalance value={txData.gasPrice} /> Ether (
|
<span>
|
||||||
<FormattedBalance value={txData.gasPrice} decimals={9} /> Gwei)
|
<FormattedBalance value={txData.gasPrice} /> Ether (
|
||||||
</span>
|
<FormattedBalance value={txData.gasPrice} decimals={9} /> Gwei)
|
||||||
{sendsEthToMiner && (
|
|
||||||
<span className="rounded text-yellow-500 bg-yellow-100 text-xs px-2 py-1">
|
|
||||||
Flashbots
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
{sendsEthToMiner && (
|
||||||
</div>
|
<span className="rounded text-yellow-500 bg-yellow-100 text-xs px-2 py-1">
|
||||||
</InfoRow>
|
Flashbots
|
||||||
<InfoRow title="Gas Used / Limit">
|
</span>
|
||||||
<div className="flex space-x-3 items-baseline">
|
)}
|
||||||
<div>
|
</div>
|
||||||
<RelativePosition
|
</InfoRow>
|
||||||
pos={<GasValue value={txData.gasUsed} />}
|
)}
|
||||||
total={<GasValue value={txData.gasLimit} />}
|
{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>
|
</div>
|
||||||
<PercentageBar
|
</InfoRow>
|
||||||
perc={
|
)}
|
||||||
Math.round(
|
{txData.confirmedData && hasEIP1559 && (
|
||||||
(txData.gasUsed.toNumber() / txData.gasLimit.toNumber()) * 10000
|
|
||||||
) / 100
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</InfoRow>
|
|
||||||
{hasEIP1559 && (
|
|
||||||
<InfoRow title="Block Base Fee">
|
<InfoRow title="Block Base Fee">
|
||||||
<span>
|
<span>
|
||||||
<FormattedBalance value={txData.blockBaseFeePerGas!} decimals={9} />{" "}
|
<FormattedBalance
|
||||||
|
value={txData.confirmedData.blockBaseFeePerGas!}
|
||||||
|
decimals={9}
|
||||||
|
/>{" "}
|
||||||
Gwei (
|
Gwei (
|
||||||
<FormattedBalance
|
<FormattedBalance
|
||||||
value={txData.blockBaseFeePerGas!}
|
value={txData.confirmedData.blockBaseFeePerGas!}
|
||||||
decimals={0}
|
decimals={0}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
wei)
|
wei)
|
||||||
</span>
|
</span>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
)}
|
)}
|
||||||
<InfoRow title="Transaction Fee">
|
{txData.confirmedData && (
|
||||||
<div className="space-y-3">
|
<>
|
||||||
<div>
|
<InfoRow title="Transaction Fee">
|
||||||
<FormattedBalance value={txData.fee} /> Ether
|
<div className="space-y-3">
|
||||||
</div>
|
<div>
|
||||||
{hasEIP1559 && <RewardSplit txData={txData} />}
|
<FormattedBalance value={txData.confirmedData.fee} /> Ether{" "}
|
||||||
</div>
|
{ethUSDPrice && (
|
||||||
</InfoRow>
|
<span className="px-2 border-skin-from border rounded-lg bg-skin-from text-skin-from">
|
||||||
<InfoRow title="Ether Price">N/A</InfoRow>
|
<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">
|
<InfoRow title="Input Data">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex space-x-1">
|
<div className="flex space-x-1">
|
||||||
|
|
|
@ -10,8 +10,8 @@ type LogsProps = {
|
||||||
const Logs: React.FC<LogsProps> = ({ txData }) => (
|
const Logs: React.FC<LogsProps> = ({ txData }) => (
|
||||||
<ContentFrame tabs>
|
<ContentFrame tabs>
|
||||||
<div className="text-sm py-4">Transaction Receipt Event Logs</div>
|
<div className="text-sm py-4">Transaction Receipt Event Logs</div>
|
||||||
{txData &&
|
{txData.confirmedData &&
|
||||||
txData.logs.map((l, i) => (
|
txData.confirmedData.logs.map((l, i) => (
|
||||||
<div className="flex space-x-10 py-5" key={i}>
|
<div className="flex space-x-10 py-5" key={i}>
|
||||||
<div>
|
<div>
|
||||||
<span className="rounded-full w-12 h-12 flex items-center justify-center bg-green-50 text-green-500">
|
<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">
|
<div className="col-span-11 mr-auto">
|
||||||
<DecoratedAddressLink
|
<DecoratedAddressLink
|
||||||
address={l.address}
|
address={l.address}
|
||||||
miner={l.address === txData.miner}
|
miner={l.address === txData.confirmedData?.miner}
|
||||||
txFrom={l.address === txData.from}
|
txFrom={l.address === txData.from}
|
||||||
txTo={l.address === txData.to}
|
txTo={l.address === txData.to}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,8 +11,10 @@ type RewardSplitProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const RewardSplit: React.FC<RewardSplitProps> = ({ txData }) => {
|
const RewardSplit: React.FC<RewardSplitProps> = ({ txData }) => {
|
||||||
const paidFees = txData.gasPrice.mul(txData.gasUsed);
|
const paidFees = txData.gasPrice.mul(txData.confirmedData!.gasUsed);
|
||||||
const burntFees = txData.blockBaseFeePerGas!.mul(txData.gasUsed);
|
const burntFees = txData.confirmedData!.blockBaseFeePerGas!.mul(
|
||||||
|
txData.confirmedData!.gasUsed
|
||||||
|
);
|
||||||
|
|
||||||
const minerReward = paidFees.sub(burntFees);
|
const minerReward = paidFees.sub(burntFees);
|
||||||
const burntPerc =
|
const burntPerc =
|
||||||
|
|
28
src/types.ts
28
src/types.ts
|
@ -38,29 +38,33 @@ export type ENSReverseCache = {
|
||||||
|
|
||||||
export type TransactionData = {
|
export type TransactionData = {
|
||||||
transactionHash: string;
|
transactionHash: string;
|
||||||
status: boolean;
|
|
||||||
blockNumber: number;
|
|
||||||
transactionIndex: number;
|
|
||||||
blockTransactionCount: number;
|
|
||||||
confirmations: number;
|
|
||||||
timestamp: number;
|
|
||||||
miner?: string;
|
|
||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to?: string;
|
||||||
createdContractAddress?: string;
|
|
||||||
value: BigNumber;
|
value: BigNumber;
|
||||||
tokenTransfers: TokenTransfer[];
|
tokenTransfers: TokenTransfer[];
|
||||||
tokenMetas: TokenMetas;
|
tokenMetas: TokenMetas;
|
||||||
type: number;
|
type: number;
|
||||||
maxFeePerGas?: BigNumber | undefined;
|
maxFeePerGas?: BigNumber | undefined;
|
||||||
maxPriorityFeePerGas?: BigNumber | undefined;
|
maxPriorityFeePerGas?: BigNumber | undefined;
|
||||||
fee: BigNumber;
|
|
||||||
blockBaseFeePerGas?: BigNumber | undefined | null;
|
|
||||||
gasPrice: BigNumber;
|
gasPrice: BigNumber;
|
||||||
gasUsed: BigNumber;
|
|
||||||
gasLimit: BigNumber;
|
gasLimit: BigNumber;
|
||||||
nonce: number;
|
nonce: number;
|
||||||
data: string;
|
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[];
|
logs: Log[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -48,12 +48,11 @@ export const readBlock = async (
|
||||||
const _rawBlock = await blockPromise;
|
const _rawBlock = await blockPromise;
|
||||||
const _block = provider.formatter.block(_rawBlock.block);
|
const _block = provider.formatter.block(_rawBlock.block);
|
||||||
const _rawIssuance = _rawBlock.issuance;
|
const _rawIssuance = _rawBlock.issuance;
|
||||||
const fees = provider.formatter.bigNumber(_rawBlock.totalFees);
|
|
||||||
|
|
||||||
const extBlock: ExtendedBlock = {
|
const extBlock: ExtendedBlock = {
|
||||||
blockReward: provider.formatter.bigNumber(_rawIssuance.blockReward ?? 0),
|
blockReward: provider.formatter.bigNumber(_rawIssuance.blockReward ?? 0),
|
||||||
unclesReward: provider.formatter.bigNumber(_rawIssuance.uncleReward ?? 0),
|
unclesReward: provider.formatter.bigNumber(_rawIssuance.uncleReward ?? 0),
|
||||||
feeReward: fees,
|
feeReward: provider.formatter.bigNumber(_rawBlock.totalFees),
|
||||||
size: provider.formatter.number(_rawBlock.block.size),
|
size: provider.formatter.number(_rawBlock.block.size),
|
||||||
sha3Uncles: _rawBlock.block.sha3Uncles,
|
sha3Uncles: _rawBlock.block.sha3Uncles,
|
||||||
stateRoot: _rawBlock.block.stateRoot,
|
stateRoot: _rawBlock.block.stateRoot,
|
||||||
|
@ -175,8 +174,8 @@ export const useBlockData = (
|
||||||
export const useTxData = (
|
export const useTxData = (
|
||||||
provider: JsonRpcProvider | undefined,
|
provider: JsonRpcProvider | undefined,
|
||||||
txhash: string
|
txhash: string
|
||||||
): TransactionData | undefined => {
|
): TransactionData | undefined | null => {
|
||||||
const [txData, setTxData] = useState<TransactionData>();
|
const [txData, setTxData] = useState<TransactionData | undefined | null>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
|
@ -188,24 +187,35 @@ export const useTxData = (
|
||||||
provider.getTransaction(txhash),
|
provider.getTransaction(txhash),
|
||||||
provider.getTransactionReceipt(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`;
|
document.title = `Transaction ${_response.hash} | Otterscan`;
|
||||||
|
|
||||||
// Extract token transfers
|
// Extract token transfers
|
||||||
const tokenTransfers: TokenTransfer[] = [];
|
const tokenTransfers: TokenTransfer[] = [];
|
||||||
for (const l of _receipt.logs) {
|
if (_receipt) {
|
||||||
if (l.topics.length !== 3) {
|
for (const l of _receipt.logs) {
|
||||||
continue;
|
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
|
// Extract token meta
|
||||||
|
@ -228,31 +238,36 @@ export const useTxData = (
|
||||||
}
|
}
|
||||||
|
|
||||||
setTxData({
|
setTxData({
|
||||||
transactionHash: _receipt.transactionHash,
|
transactionHash: _response.hash,
|
||||||
status: _receipt.status === 1,
|
from: _response.from,
|
||||||
blockNumber: _receipt.blockNumber,
|
to: _response.to,
|
||||||
transactionIndex: _receipt.transactionIndex,
|
|
||||||
blockTransactionCount: _block.transactionCount,
|
|
||||||
confirmations: _receipt.confirmations,
|
|
||||||
timestamp: _block.timestamp,
|
|
||||||
miner: _block.miner,
|
|
||||||
from: _receipt.from,
|
|
||||||
to: _receipt.to,
|
|
||||||
createdContractAddress: _receipt.contractAddress,
|
|
||||||
value: _response.value,
|
value: _response.value,
|
||||||
tokenTransfers,
|
tokenTransfers,
|
||||||
tokenMetas,
|
tokenMetas,
|
||||||
type: _response.type ?? 0,
|
type: _response.type ?? 0,
|
||||||
fee: _response.gasPrice!.mul(_receipt.gasUsed),
|
|
||||||
blockBaseFeePerGas: _block.baseFeePerGas,
|
|
||||||
maxFeePerGas: _response.maxFeePerGas,
|
maxFeePerGas: _response.maxFeePerGas,
|
||||||
maxPriorityFeePerGas: _response.maxPriorityFeePerGas,
|
maxPriorityFeePerGas: _response.maxPriorityFeePerGas,
|
||||||
gasPrice: _response.gasPrice!,
|
gasPrice: _response.gasPrice!,
|
||||||
gasUsed: _receipt.gasUsed,
|
|
||||||
gasLimit: _response.gasLimit,
|
gasLimit: _response.gasLimit,
|
||||||
nonce: _response.nonce,
|
nonce: _response.nonce,
|
||||||
data: _response.data,
|
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();
|
readTxData();
|
||||||
|
@ -263,13 +278,13 @@ export const useTxData = (
|
||||||
|
|
||||||
export const useInternalOperations = (
|
export const useInternalOperations = (
|
||||||
provider: JsonRpcProvider | undefined,
|
provider: JsonRpcProvider | undefined,
|
||||||
txData: TransactionData | undefined
|
txData: TransactionData | undefined | null
|
||||||
): InternalOperation[] | undefined => {
|
): InternalOperation[] | undefined => {
|
||||||
const [intTransfers, setIntTransfers] = useState<InternalOperation[]>();
|
const [intTransfers, setIntTransfers] = useState<InternalOperation[]>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const traceTransfers = async () => {
|
const traceTransfers = async () => {
|
||||||
if (!provider || !txData) {
|
if (!provider || !txData || !txData.confirmedData) {
|
||||||
return;
|
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");
|
const colors = require("tailwindcss/colors");
|
||||||
|
|
||||||
|
function withOpacity(variableName) {
|
||||||
|
return ({ opacityValue }) => {
|
||||||
|
if (opacityValue !== undefined) {
|
||||||
|
return `rgba(var(${variableName}), ${opacityValue})`;
|
||||||
|
}
|
||||||
|
return `rgb(var(${variableName}))`;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
|
purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
|
||||||
darkMode: false, // or 'media' or 'class'
|
darkMode: false, // or 'media' or 'class'
|
||||||
|
@ -19,6 +28,28 @@ module.exports = {
|
||||||
balance: ["Fira Code"],
|
balance: ["Fira Code"],
|
||||||
blocknum: ["Roboto"],
|
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: {
|
variants: {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9bc40f37d95234810bc7e176513c8366c81080ce
|
Subproject commit 7bfa06acc125a4874d86bc1fa8e4547a46846e31
|
Loading…
Reference in New Issue