Merge branch 'release/v2021.07.02-otterscan'
This commit is contained in:
commit
a2e984aea2
|
@ -0,0 +1,6 @@
|
||||||
|
.git
|
||||||
|
node_modules
|
||||||
|
4bytes
|
||||||
|
!4bytes/signatures
|
||||||
|
trustwallet
|
||||||
|
!trustwallet/blockchains/ethereum/assets
|
|
@ -1,6 +1,6 @@
|
||||||
[submodule "4bytes"]
|
[submodule "4bytes"]
|
||||||
path = 4bytes
|
path = 4bytes
|
||||||
url = git@github.com:ethereum-lists/4bytes.git
|
url = https://github.com/ethereum-lists/4bytes.git
|
||||||
[submodule "trustwallet"]
|
[submodule "trustwallet"]
|
||||||
path = trustwallet
|
path = trustwallet
|
||||||
url = git@github.com:trustwallet/assets.git
|
url = https://github.com/trustwallet/assets.git
|
||||||
|
|
2
4bytes
2
4bytes
|
@ -1 +1 @@
|
||||||
Subproject commit bea447b61551c329215bf11dcf4c57b62210de8a
|
Subproject commit 3fde3a1b2e002736e9a5e0c35df5bbe386a18d55
|
|
@ -0,0 +1,19 @@
|
||||||
|
FROM node:12.22.3-alpine AS builder
|
||||||
|
WORKDIR /otterscan-build
|
||||||
|
COPY ["package.json", "package-lock.json", "/otterscan-build"]
|
||||||
|
RUN npm install
|
||||||
|
COPY ["run-nginx.sh", "tsconfig.json", "craco.config.js", "tailwind.config.js", "/otterscan-build"]
|
||||||
|
COPY ["public", "/otterscan-build/public"]
|
||||||
|
COPY ["src", "/otterscan-build/src"]
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM nginx:1.21.1-alpine
|
||||||
|
RUN apk add jq
|
||||||
|
COPY 4bytes/signatures /usr/share/nginx/html/signatures/
|
||||||
|
COPY trustwallet/blockchains/ethereum/assets /usr/share/nginx/html/assets/
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY --from=builder /otterscan-build/build /usr/share/nginx/html/
|
||||||
|
COPY --from=builder /otterscan-build/run-nginx.sh /
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
CMD ["/run-nginx.sh"]
|
59
README.md
59
README.md
|
@ -72,15 +72,21 @@ They have weekly stable releases, make sure you are running on of them, not deve
|
||||||
|
|
||||||
Add our forked Erigon git tree as an additional remote and checkout the corresponding branch.
|
Add our forked Erigon git tree as an additional remote and checkout the corresponding branch.
|
||||||
|
|
||||||
|
The repository with Otterscan patches is [here](https://github.com/wmitsuda/erigon).
|
||||||
|
|
||||||
```
|
```
|
||||||
git remote add otterscan git@github.com:wmitsuda/erigon.git
|
git remote add otterscan git@github.com:wmitsuda/erigon.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Checkout the `otterscan-develop` branch (be sure to check from which tag it is branched from to be sure it is a compatible branch).
|
Checkout the tag corresponding to the stable version you are running. For each supported Erigon version, there should be a corresponding tag containing Otterscan patches.
|
||||||
|
|
||||||
|
For example, if you are running Erigon from `v2021.07.01` tag, checkout the tag `v2021.07.01-otterscan` and rebuild `rpcdaemon`.
|
||||||
|
|
||||||
|
We intend to release a compatible rebased version containing our changes every week just after Erigon's weekly release, as time permits.
|
||||||
|
|
||||||
```
|
```
|
||||||
git fetch --all
|
git fetch --all
|
||||||
git checkout otterscan-develop
|
git checkout <version-tag-otterscan>
|
||||||
```
|
```
|
||||||
|
|
||||||
Build the patched `rpcdaemon` binary.
|
Build the patched `rpcdaemon` binary.
|
||||||
|
@ -94,63 +100,42 @@ Run it paying attention to enable the `erigon`, `ots`, `eth` apis to whatever cl
|
||||||
`ots` stands for Otterscan and it is the namespace we use for our own custom APIs.
|
`ots` stands for Otterscan and it is the namespace we use for our own custom APIs.
|
||||||
|
|
||||||
```
|
```
|
||||||
./build/bin/rpcdaemon --http.api "eth,erigon,ots,<your-other-apis>" --private.api.addr 127.0.0.1:9090 --chaindata <erigon-chaindata-dir> --http.corsdomain "*"
|
./build/bin/rpcdaemon --http.api "eth,erigon,ots,<your-other-apis>" --private.api.addr 127.0.0.1:9090 --datadir <erigon-datadir> --http.corsdomain "*"
|
||||||
```
|
```
|
||||||
|
|
||||||
Be sure to include both `--private.api.addr` and `--chaindata` parameter so you run it in dual mode, otherwise the performance will be much worse.
|
Be sure to include both `--private.api.addr` and `--datadir` parameter so you run it in dual mode, otherwise the performance will be much worse.
|
||||||
|
|
||||||
Also pay attention to the `--http.corsdomain` parameter, CORS is required for the browser to call the node directly.
|
Also pay attention to the `--http.corsdomain` parameter, CORS is required for the browser to call the node directly.
|
||||||
|
|
||||||
Now you should have an Erigon node with Otterscan jsonrpc APIs enabled, running in dual mode with CORS enabled.
|
Now you should have an Erigon node with Otterscan jsonrpc APIs enabled, running in dual mode with CORS enabled.
|
||||||
|
|
||||||
### Clone Otterscan repository and build the project
|
### Run Otterscan docker image from Docker Hub
|
||||||
|
|
||||||
Make sure you have a working node 12/npm installation.
|
The Otterscan official repo on Docker Hub is [here](https://hub.docker.com/orgs/otterscan/repositories).
|
||||||
|
|
||||||
Clone Otterscan repo and its submodules. For now, only the default `develop` branch is available (it is alpha...).
|
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone --recurse-submodules git@github.com:wmitsuda/otterscan.git
|
docker run --rm -p 5000:80 --name otterscan -d otterscan/otterscan:<versiontag>
|
||||||
cd otterscan
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run it from the source
|
This will download the Otterscan image from Docker Hub, run it locally using the default parameters, binding it to port 5000 (see the `-p` docker run parameter).
|
||||||
|
|
||||||
First, a brief explanation about the app:
|
To stop Otterscan service, run:
|
||||||
|
|
||||||
- The app itself is a simple React app which will be run locally and communicates with your Erigon node.
|
|
||||||
- The app makes use of two sources of external databases for cosmetic reasons:
|
|
||||||
- Token icons come from the trustwallet public assets repository.
|
|
||||||
- Method names come from the 4bytes database.
|
|
||||||
- These 2 repositories were cloned as submodules and are made available to the app through separate http services. They are accessed at browser level and are optional, if the service is down the result will be broken icons and default 4bytes method selectors instead of human-readable names.
|
|
||||||
|
|
||||||
These instructions are subjected to changes in future for the sake of simplification.
|
|
||||||
|
|
||||||
Open a new terminal and start the 4bytes method decoding service:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run serve-4bytes
|
docker stop otterscan
|
||||||
```
|
```
|
||||||
|
|
||||||
Open another terminal and start the trustwallet assets service:
|
By default it assumes your Erigon node is at `http://127.0.0.1:8545`. You can override the URL by setting the `ERIGON_URL` env variable on `docker run`:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run serve-trustwallet-assets
|
docker run --rm -p 5000:80 --name otterscan -d --env ERIGON_URL="<your-erigon-node-url>" otterscan/otterscan:<versiontag>
|
||||||
```
|
```
|
||||||
|
|
||||||
In another terminal start the Otterscan app:
|
This is the preferred way to run Otterscan. You can read about other ways [here](docs/other-ways-to-run-otterscan.md).
|
||||||
|
|
||||||
```
|
## Validating the installation (all methods)
|
||||||
npm run serve
|
|
||||||
```
|
|
||||||
|
|
||||||
Otterscan should now be running at http://localhost:5000/.
|
You can make sure it is working correctly if the homepage is able to show the latest block/timestamp your Erigon node is at just bellow the search button.
|
||||||
|
|
||||||
For now, it assumes the `rpcdaemon` is running on 127.0.0.1:8545. If for some reason your installation is running in another host/port, change it in the `src/ethersconfig.ts` file (patches to make it parameterized are welcome).
|
|
||||||
|
|
||||||
**You can make sure it is working correctly if the homepage is able to show the latest block/timestamp your Erigon node is at just bellow the search button.**
|
|
||||||
|
|
||||||
## Kudos
|
## Kudos
|
||||||
|
|
||||||
|
@ -162,7 +147,7 @@ To the [mdbx](https://github.com/erthink/libmdbx) team which is the blazingly fa
|
||||||
|
|
||||||
To [Trust Wallet](https://github.com/trustwallet/assets) who sponsor and make available their icons under a permissive license.
|
To [Trust Wallet](https://github.com/trustwallet/assets) who sponsor and make available their icons under a permissive license.
|
||||||
|
|
||||||
To the owners of the [4bytes repository](https://github.com/ethereum-lists/4bytes) that we import and use to translate and method selector to human-friendly strings.
|
To the owners of the [4bytes repository](https://github.com/ethereum-lists/4bytes) that we import and use to translate the method selectors to human-friendly strings.
|
||||||
|
|
||||||
## Future
|
## Future
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Running Otterscan (other methods)
|
||||||
|
|
||||||
|
## (Alternative 1) Build Otterscan docker image locally and run it
|
||||||
|
|
||||||
|
If you don't want to download from Docker Hub, you can build the docker image from the sources and run it.
|
||||||
|
|
||||||
|
If you just want to build the image locally, there is no need to install the development toolchain, just make sure you have a recent working Docker installation.
|
||||||
|
|
||||||
|
This method requires only `git` and `docker`.
|
||||||
|
|
||||||
|
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
|
||||||
|
cd otterscan
|
||||||
|
git checkout <version-tag-otterscan>
|
||||||
|
docker build -t otterscan -f Dockerfile .
|
||||||
|
```
|
||||||
|
|
||||||
|
This will run the entire build process inside a build container, merge the production build of the React app with the 4bytes and trustwallet assets into the same image format it is published in Docker Hub, but locally under the name `otterscan`.
|
||||||
|
|
||||||
|
Then you can start/stop it using the commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm -p 5000:80 --name otterscan -d otterscan
|
||||||
|
docker stop otterscan
|
||||||
|
```
|
||||||
|
|
||||||
|
## (Alternative 2) Run a development build from the source
|
||||||
|
|
||||||
|
First, a brief explanation about the app:
|
||||||
|
|
||||||
|
- The app itself is a simple React app which will be run locally and communicates with your Erigon node.
|
||||||
|
- The app makes use of two sources of external databases for cosmetic reasons:
|
||||||
|
- Token icons come from the trustwallet public assets repository.
|
||||||
|
- Method names come from the 4bytes database.
|
||||||
|
- These 2 repositories were cloned as submodules and are made available to the app through separate http services. They are accessed at browser level and are optional, if the service is down the result will be broken icons and default 4bytes method selectors instead of human-readable names.
|
||||||
|
|
||||||
|
These instructions are subjected to changes in future for the sake of simplification.
|
||||||
|
|
||||||
|
Make sure you have a working node 12/npm installation.
|
||||||
|
|
||||||
|
By default, it assumes your Erigon `rpcdaemon` processs is serving requests at `http://localhost:8545`. You can customize this URL by changing the `public/config.json` file.
|
||||||
|
|
||||||
|
Start serving 4bytes and trustwallet assets at `localhost:3001` using a dockerized nginx:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run start-assets
|
||||||
|
```
|
||||||
|
|
||||||
|
To stop it, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run stop-assets
|
||||||
|
```
|
||||||
|
|
||||||
|
To run Otterscan development build:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Otterscan should now be running at `http://localhost:3000/`.
|
|
@ -0,0 +1,90 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
#access_log /var/log/nginx/host.access.log main;
|
||||||
|
|
||||||
|
location /signatures {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
expires 30d;
|
||||||
|
|
||||||
|
# Base on: https://michielkalkman.com/snippets/nginx-cors-open-configuration/
|
||||||
|
if ($request_method = 'OPTIONS') {
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*';
|
||||||
|
#
|
||||||
|
# Om nom nom cookies
|
||||||
|
#
|
||||||
|
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
|
||||||
|
|
||||||
|
#
|
||||||
|
# Custom headers and headers various browsers *should* be OK with but aren't
|
||||||
|
#
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tell client that this pre-flight info is valid for 20 days
|
||||||
|
#
|
||||||
|
add_header 'Access-Control-Max-Age' 1728000;
|
||||||
|
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||||
|
add_header 'Content-Length' 0;
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
if ($request_method = 'GET') {
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
|
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' always;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
location /assets {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
expires 30d;
|
||||||
|
|
||||||
|
# Base on: https://michielkalkman.com/snippets/nginx-cors-open-configuration/
|
||||||
|
if ($request_method = 'OPTIONS') {
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*';
|
||||||
|
#
|
||||||
|
# Om nom nom cookies
|
||||||
|
#
|
||||||
|
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
|
||||||
|
|
||||||
|
#
|
||||||
|
# Custom headers and headers various browsers *should* be OK with but aren't
|
||||||
|
#
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tell client that this pre-flight info is valid for 20 days
|
||||||
|
#
|
||||||
|
add_header 'Access-Control-Max-Age' 1728000;
|
||||||
|
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||||
|
add_header 'Content-Length' 0;
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
if ($request_method = 'GET') {
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
|
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' always;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
#error_page 404 /404.html;
|
||||||
|
|
||||||
|
# redirect server error pages to the static page /50x.html
|
||||||
|
#
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,11 +8,11 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@craco/craco": "^6.1.2",
|
"@craco/craco": "^6.2.0",
|
||||||
"@fontsource/fira-code": "^4.4.5",
|
"@fontsource/fira-code": "^4.5.0",
|
||||||
"@fontsource/roboto": "^4.4.5",
|
"@fontsource/roboto": "^4.5.0",
|
||||||
"@fontsource/roboto-mono": "^4.4.5",
|
"@fontsource/roboto-mono": "^4.5.0",
|
||||||
"@fontsource/space-grotesk": "^4.4.5",
|
"@fontsource/space-grotesk": "^4.5.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
||||||
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
||||||
|
@ -21,13 +21,13 @@
|
||||||
"@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.15",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/node": "^12.0.0",
|
"@types/node": "^12.20.16",
|
||||||
"@types/react": "^17.0.13",
|
"@types/react": "^17.0.14",
|
||||||
"@types/react-blockies": "^1.4.0",
|
"@types/react-blockies": "^1.4.1",
|
||||||
"@types/react-dom": "^17.0.8",
|
"@types/react-dom": "^17.0.9",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
"ethers": "^5.4.0",
|
"ethers": "^5.4.1",
|
||||||
"query-string": "^7.0.1",
|
"query-string": "^7.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-blockies": "^1.4.1",
|
"react-blockies": "^1.4.1",
|
||||||
|
@ -1218,9 +1218,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@craco/craco": {
|
"node_modules/@craco/craco": {
|
||||||
"version": "6.1.2",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.2.0.tgz",
|
||||||
"integrity": "sha512-GlQZn+g+yNlaDvIL5m6mcCoBGyFDwO4kkNx3fNFf98wuldkdWyBFoQbtOFOIb4gvkTh4VntOOxtJEoZfKs7XXw==",
|
"integrity": "sha512-kLc4GSdgR9D5JiZmSxtzbvBKcUFSJqMXImRjjYf5pacwiyAs3XfQwai7T+pExfLQNUnytgkL8jRFUJeYrkVr7g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "^7.0.0",
|
"cross-spawn": "^7.0.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
@ -1627,9 +1627,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@ethersproject/networks": {
|
"node_modules/@ethersproject/networks": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.4.1.tgz",
|
||||||
"integrity": "sha512-5fywtKRDcnaVeA5SjxXH3DOQqe/IbeD/plwydi94SdPps1fbDUrnO6SzDExaruBZXxpxJcO9upG9UComsei4bg==",
|
"integrity": "sha512-8SvowCKz9Uf4xC5DTKI8+il8lWqOr78kmiqAVLYT9lzB8aSmJHQMD1GSuJI0CW4hMAnzocpGpZLgiMdzsNSPig==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
|
@ -1682,9 +1682,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ethersproject/providers": {
|
"node_modules/@ethersproject/providers": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.4.1.tgz",
|
||||||
"integrity": "sha512-XRmI9syLnkNdLA8ikEeg0duxmwSWTTt9S+xabnTOyI51JPJyhQ0QUNT+wvmod218ebb7rLupHDPQ7UVe2/+Tjg==",
|
"integrity": "sha512-p06eiFKz8nu/5Ju0kIX024gzEQIgE5pvvGrBCngpyVjpuLtUIWT3097Agw4mTn9/dEA0FMcfByzFqacBMSgCVg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
|
@ -1983,24 +1983,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/fira-code": {
|
"node_modules/@fontsource/fira-code": {
|
||||||
"version": "4.4.5",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.0.tgz",
|
||||||
"integrity": "sha512-ap1UKABzbPrUTgIB376n8CSJ7mbJYxGm52A60BHnGHjU2cSDBqNUAColdERgFWgLHfybWXQT13ZrNhAdynf9rg=="
|
"integrity": "sha512-fxRV3qt0eJaIXZvICXZMhXVR0lSyxZTC0cnM+1Ma/1JShGrIjCQ3yJ0W05rwaEoF3cAbpU2lKMrXfE7Of/zpIA=="
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/roboto": {
|
"node_modules/@fontsource/roboto": {
|
||||||
"version": "4.4.5",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.0.tgz",
|
||||||
"integrity": "sha512-e3s7BF8MDBLpkA2r6lnl5PMnllF0McVvpolK9h2zzvVJw2WPexP1GTgMKHISlglYZRij2lKg/ZjQcIUUYDsAXg=="
|
"integrity": "sha512-ja4XYw/9kNRFM5Ndk9vwzHWsdBMXczyBazFkTXJQ74QQBnT0BbSsHn0pF60AU0Iznig1Wt9x3rADfG8LANvMpw=="
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/roboto-mono": {
|
"node_modules/@fontsource/roboto-mono": {
|
||||||
"version": "4.4.5",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-mono/-/roboto-mono-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/roboto-mono/-/roboto-mono-4.5.0.tgz",
|
||||||
"integrity": "sha512-y9fbKH1eCCkcqtRGhevFlmR5schCRz1GiTT/VYz6z8Ij0WHW0tS26BhMyXhmSgEIiFt+254yS8teqP+cc7Xq0w=="
|
"integrity": "sha512-/6Gm6fJjBHZiFNyvzIKGJkVuyifoc1aoTel+pkzdhxNh7yNhFyokCoChdbbqZEpGKpqs5uld74G5TJthUVFyjw=="
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource/space-grotesk": {
|
"node_modules/@fontsource/space-grotesk": {
|
||||||
"version": "4.4.5",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/space-grotesk/-/space-grotesk-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/space-grotesk/-/space-grotesk-4.5.0.tgz",
|
||||||
"integrity": "sha512-9gD8j+uxTggMdPStZi4SLjwWBpmKQteuP1NMtdegPzbbnfVsjzg4++w+KnpkRp2PMWECcGzyNkW8uNXqP+H6+w=="
|
"integrity": "sha512-Kdnq5m31DsJ8l/VCpZGz4QpIxLLvNVh4MyVGY0PmDh7aY9aM5rmO2NF49dJ0749gLPgMW6Moc/xjgW95G0MbMg=="
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||||
"version": "0.2.35",
|
"version": "0.2.35",
|
||||||
|
@ -2152,10 +2152,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jest/console/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@jest/core": {
|
"node_modules/@jest/core": {
|
||||||
"version": "26.6.3",
|
"version": "26.6.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -2193,10 +2189,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jest/core/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@jest/core/node_modules/rimraf": {
|
"node_modules/@jest/core/node_modules/rimraf": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
@ -2223,10 +2215,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jest/environment/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@jest/fake-timers": {
|
"node_modules/@jest/fake-timers": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -2242,10 +2230,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jest/fake-timers/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@jest/globals": {
|
"node_modules/@jest/globals": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -2371,10 +2355,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jest/types/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -2989,10 +2969,6 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/glob/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/graceful-fs": {
|
"node_modules/@types/graceful-fs": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -3000,10 +2976,6 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/graceful-fs/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/history": {
|
"node_modules/@types/history": {
|
||||||
"version": "4.7.8",
|
"version": "4.7.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz",
|
||||||
|
@ -3032,8 +3004,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/jest": {
|
"node_modules/@types/jest": {
|
||||||
"version": "26.0.23",
|
"version": "26.0.24",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz",
|
||||||
|
"integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jest-diff": "^26.0.0",
|
"jest-diff": "^26.0.0",
|
||||||
"pretty-format": "^26.0.0"
|
"pretty-format": "^26.0.0"
|
||||||
|
@ -3052,8 +3025,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "12.20.15",
|
"version": "12.20.16",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.16.tgz",
|
||||||
|
"integrity": "sha512-6CLxw83vQf6DKqXxMPwl8qpF8I7THFZuIwLt4TnNsumxkp1VsRZWT8txQxncT/Rl2UojTsFzWgDG4FRMwafrlA=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/normalize-package-data": {
|
"node_modules/@types/normalize-package-data": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
|
@ -3076,9 +3050,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "17.0.13",
|
"version": "17.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz",
|
||||||
"integrity": "sha512-D/G3PiuqTfE3IMNjLn/DCp6umjVCSvtZTPdtAFy5+Ved6CsdRvivfKeCzw79W4AatShtU4nGqgvOv5Gro534vQ==",
|
"integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
@ -3086,17 +3060,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-blockies": {
|
"node_modules/@types/react-blockies": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-blockies/-/react-blockies-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-blockies/-/react-blockies-1.4.1.tgz",
|
||||||
"integrity": "sha512-Bw0CraHz5XE61yu64KqfyQ6D9XaPlZo96E2+D2mCE2ZiCeaj5rCRhalHeQ2R684qIInkHACTyAw2BZNWd73PeQ==",
|
"integrity": "sha512-aDX0g0hwzdodkGLSDNUQr6gXxwclGjnhS8jhsR8uQhAfe/7i3GZD/NDcSlQ2SiQiLhfRxX3NlY+nvBwf5Y0tTg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-dom": {
|
"node_modules/@types/react-dom": {
|
||||||
"version": "17.0.8",
|
"version": "17.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
|
||||||
"integrity": "sha512-0ohAiJAx1DAUEcY9UopnfwCE9sSMDGnY/oXjWMax6g3RpzmTt2GMyMVAXcbn0mo8XAff0SbQJl2/SBU+hjSZ1A==",
|
"integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
|
@ -3111,9 +3085,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-router-dom": {
|
"node_modules/@types/react-router-dom": {
|
||||||
"version": "5.1.7",
|
"version": "5.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz",
|
||||||
"integrity": "sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==",
|
"integrity": "sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/history": "*",
|
"@types/history": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
|
@ -3127,10 +3101,6 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/resolve/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/scheduler": {
|
"node_modules/@types/scheduler": {
|
||||||
"version": "0.16.1",
|
"version": "0.16.1",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
@ -3182,10 +3152,6 @@
|
||||||
"source-map": "^0.7.3"
|
"source-map": "^0.7.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/webpack-sources/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/webpack-sources/node_modules/source-map": {
|
"node_modules/@types/webpack-sources/node_modules/source-map": {
|
||||||
"version": "0.7.3",
|
"version": "0.7.3",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
|
@ -3193,10 +3159,6 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/webpack/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "15.0.13",
|
"version": "15.0.13",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -8010,9 +7972,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ethers": {
|
"node_modules/ethers": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.4.1.tgz",
|
||||||
"integrity": "sha512-hqN1x0CV8VMpQ25WnNEjaMqtB3nA4DRAb2FSmmNaUbD1dF6kWbHs8YaXbVvD37FCg3GTEyc4rV9Pxafk1ByHKw==",
|
"integrity": "sha512-SrcddMdCgP1hukDvCPd87Aipbf4NWjQvdfAbZ65XSZGbfyuYPtIrUJPDH5B1SBRsdlfiEgX3eoz28DdBDzMNFg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
|
@ -8039,10 +8001,10 @@
|
||||||
"@ethersproject/json-wallets": "5.4.0",
|
"@ethersproject/json-wallets": "5.4.0",
|
||||||
"@ethersproject/keccak256": "5.4.0",
|
"@ethersproject/keccak256": "5.4.0",
|
||||||
"@ethersproject/logger": "5.4.0",
|
"@ethersproject/logger": "5.4.0",
|
||||||
"@ethersproject/networks": "5.4.0",
|
"@ethersproject/networks": "5.4.1",
|
||||||
"@ethersproject/pbkdf2": "5.4.0",
|
"@ethersproject/pbkdf2": "5.4.0",
|
||||||
"@ethersproject/properties": "5.4.0",
|
"@ethersproject/properties": "5.4.0",
|
||||||
"@ethersproject/providers": "5.4.0",
|
"@ethersproject/providers": "5.4.1",
|
||||||
"@ethersproject/random": "5.4.0",
|
"@ethersproject/random": "5.4.0",
|
||||||
"@ethersproject/rlp": "5.4.0",
|
"@ethersproject/rlp": "5.4.0",
|
||||||
"@ethersproject/sha2": "5.4.0",
|
"@ethersproject/sha2": "5.4.0",
|
||||||
|
@ -10206,10 +10168,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-circus/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-cli": {
|
"node_modules/jest-cli": {
|
||||||
"version": "26.6.3",
|
"version": "26.6.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10323,10 +10281,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-environment-jsdom/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-environment-node": {
|
"node_modules/jest-environment-node": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10342,10 +10296,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-environment-node/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-get-type": {
|
"node_modules/jest-get-type": {
|
||||||
"version": "26.3.0",
|
"version": "26.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10378,10 +10328,6 @@
|
||||||
"fsevents": "^2.1.2"
|
"fsevents": "^2.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-haste-map/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-haste-map/node_modules/anymatch": {
|
"node_modules/jest-haste-map/node_modules/anymatch": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
@ -10420,10 +10366,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-jasmine2/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-leak-detector": {
|
"node_modules/jest-leak-detector": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10477,10 +10419,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-mock/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-pnp-resolver": {
|
"node_modules/jest-pnp-resolver": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10561,10 +10499,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-runner/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-runtime": {
|
"node_modules/jest-runtime": {
|
||||||
"version": "26.6.3",
|
"version": "26.6.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10622,10 +10556,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-serializer/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-snapshot": {
|
"node_modules/jest-snapshot": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10679,10 +10609,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-util/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-validate": {
|
"node_modules/jest-validate": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10743,10 +10669,6 @@
|
||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-watcher/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jest-worker": {
|
"node_modules/jest-worker": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -10759,10 +10681,6 @@
|
||||||
"node": ">= 10.13.0"
|
"node": ">= 10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-worker/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/js-sha3": {
|
"node_modules/js-sha3": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz",
|
||||||
|
@ -15357,10 +15275,6 @@
|
||||||
"estree-walker": "^0.6.1"
|
"estree-walker": "^0.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@types/node": {
|
|
||||||
"version": "14.14.31",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/rsvp": {
|
"node_modules/rsvp": {
|
||||||
"version": "4.8.5",
|
"version": "4.8.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -20007,9 +19921,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@craco/craco": {
|
"@craco/craco": {
|
||||||
"version": "6.1.2",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.2.0.tgz",
|
||||||
"integrity": "sha512-GlQZn+g+yNlaDvIL5m6mcCoBGyFDwO4kkNx3fNFf98wuldkdWyBFoQbtOFOIb4gvkTh4VntOOxtJEoZfKs7XXw==",
|
"integrity": "sha512-kLc4GSdgR9D5JiZmSxtzbvBKcUFSJqMXImRjjYf5pacwiyAs3XfQwai7T+pExfLQNUnytgkL8jRFUJeYrkVr7g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"cross-spawn": "^7.0.0",
|
"cross-spawn": "^7.0.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
@ -20239,9 +20153,9 @@
|
||||||
"integrity": "sha512-xYdWGGQ9P2cxBayt64d8LC8aPFJk6yWCawQi/4eJ4+oJdMMjEBMrIcIMZ9AxhwpPVmnBPrsB10PcXGmGAqgUEQ=="
|
"integrity": "sha512-xYdWGGQ9P2cxBayt64d8LC8aPFJk6yWCawQi/4eJ4+oJdMMjEBMrIcIMZ9AxhwpPVmnBPrsB10PcXGmGAqgUEQ=="
|
||||||
},
|
},
|
||||||
"@ethersproject/networks": {
|
"@ethersproject/networks": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.4.1.tgz",
|
||||||
"integrity": "sha512-5fywtKRDcnaVeA5SjxXH3DOQqe/IbeD/plwydi94SdPps1fbDUrnO6SzDExaruBZXxpxJcO9upG9UComsei4bg==",
|
"integrity": "sha512-8SvowCKz9Uf4xC5DTKI8+il8lWqOr78kmiqAVLYT9lzB8aSmJHQMD1GSuJI0CW4hMAnzocpGpZLgiMdzsNSPig==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ethersproject/logger": "^5.4.0"
|
"@ethersproject/logger": "^5.4.0"
|
||||||
}
|
}
|
||||||
|
@ -20264,9 +20178,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@ethersproject/providers": {
|
"@ethersproject/providers": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.4.1.tgz",
|
||||||
"integrity": "sha512-XRmI9syLnkNdLA8ikEeg0duxmwSWTTt9S+xabnTOyI51JPJyhQ0QUNT+wvmod218ebb7rLupHDPQ7UVe2/+Tjg==",
|
"integrity": "sha512-p06eiFKz8nu/5Ju0kIX024gzEQIgE5pvvGrBCngpyVjpuLtUIWT3097Agw4mTn9/dEA0FMcfByzFqacBMSgCVg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ethersproject/abstract-provider": "^5.4.0",
|
"@ethersproject/abstract-provider": "^5.4.0",
|
||||||
"@ethersproject/abstract-signer": "^5.4.0",
|
"@ethersproject/abstract-signer": "^5.4.0",
|
||||||
|
@ -20433,24 +20347,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fontsource/fira-code": {
|
"@fontsource/fira-code": {
|
||||||
"version": "4.4.5",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.5.0.tgz",
|
||||||
"integrity": "sha512-ap1UKABzbPrUTgIB376n8CSJ7mbJYxGm52A60BHnGHjU2cSDBqNUAColdERgFWgLHfybWXQT13ZrNhAdynf9rg=="
|
"integrity": "sha512-fxRV3qt0eJaIXZvICXZMhXVR0lSyxZTC0cnM+1Ma/1JShGrIjCQ3yJ0W05rwaEoF3cAbpU2lKMrXfE7Of/zpIA=="
|
||||||
},
|
},
|
||||||
"@fontsource/roboto": {
|
"@fontsource/roboto": {
|
||||||
"version": "4.4.5",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.0.tgz",
|
||||||
"integrity": "sha512-e3s7BF8MDBLpkA2r6lnl5PMnllF0McVvpolK9h2zzvVJw2WPexP1GTgMKHISlglYZRij2lKg/ZjQcIUUYDsAXg=="
|
"integrity": "sha512-ja4XYw/9kNRFM5Ndk9vwzHWsdBMXczyBazFkTXJQ74QQBnT0BbSsHn0pF60AU0Iznig1Wt9x3rADfG8LANvMpw=="
|
||||||
},
|
},
|
||||||
"@fontsource/roboto-mono": {
|
"@fontsource/roboto-mono": {
|
||||||
"version": "4.4.5",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-mono/-/roboto-mono-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/roboto-mono/-/roboto-mono-4.5.0.tgz",
|
||||||
"integrity": "sha512-y9fbKH1eCCkcqtRGhevFlmR5schCRz1GiTT/VYz6z8Ij0WHW0tS26BhMyXhmSgEIiFt+254yS8teqP+cc7Xq0w=="
|
"integrity": "sha512-/6Gm6fJjBHZiFNyvzIKGJkVuyifoc1aoTel+pkzdhxNh7yNhFyokCoChdbbqZEpGKpqs5uld74G5TJthUVFyjw=="
|
||||||
},
|
},
|
||||||
"@fontsource/space-grotesk": {
|
"@fontsource/space-grotesk": {
|
||||||
"version": "4.4.5",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/space-grotesk/-/space-grotesk-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/space-grotesk/-/space-grotesk-4.5.0.tgz",
|
||||||
"integrity": "sha512-9gD8j+uxTggMdPStZi4SLjwWBpmKQteuP1NMtdegPzbbnfVsjzg4++w+KnpkRp2PMWECcGzyNkW8uNXqP+H6+w=="
|
"integrity": "sha512-Kdnq5m31DsJ8l/VCpZGz4QpIxLLvNVh4MyVGY0PmDh7aY9aM5rmO2NF49dJ0749gLPgMW6Moc/xjgW95G0MbMg=="
|
||||||
},
|
},
|
||||||
"@fortawesome/fontawesome-common-types": {
|
"@fortawesome/fontawesome-common-types": {
|
||||||
"version": "0.2.35",
|
"version": "0.2.35",
|
||||||
|
@ -20557,11 +20471,6 @@
|
||||||
"jest-message-util": "^26.6.2",
|
"jest-message-util": "^26.6.2",
|
||||||
"jest-util": "^26.6.2",
|
"jest-util": "^26.6.2",
|
||||||
"slash": "^3.0.0"
|
"slash": "^3.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@jest/core": {
|
"@jest/core": {
|
||||||
|
@ -20597,9 +20506,6 @@
|
||||||
"strip-ansi": "^6.0.0"
|
"strip-ansi": "^6.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
},
|
|
||||||
"rimraf": {
|
"rimraf": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -20615,11 +20521,6 @@
|
||||||
"@jest/types": "^26.6.2",
|
"@jest/types": "^26.6.2",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"jest-mock": "^26.6.2"
|
"jest-mock": "^26.6.2"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@jest/fake-timers": {
|
"@jest/fake-timers": {
|
||||||
|
@ -20631,11 +20532,6 @@
|
||||||
"jest-message-util": "^26.6.2",
|
"jest-message-util": "^26.6.2",
|
||||||
"jest-mock": "^26.6.2",
|
"jest-mock": "^26.6.2",
|
||||||
"jest-util": "^26.6.2"
|
"jest-util": "^26.6.2"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@jest/globals": {
|
"@jest/globals": {
|
||||||
|
@ -20731,11 +20627,6 @@
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/yargs": "^15.0.0",
|
"@types/yargs": "^15.0.0",
|
||||||
"chalk": "^4.0.0"
|
"chalk": "^4.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
|
@ -21088,22 +20979,12 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/minimatch": "*",
|
"@types/minimatch": "*",
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/graceful-fs": {
|
"@types/graceful-fs": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/history": {
|
"@types/history": {
|
||||||
|
@ -21130,7 +21011,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/jest": {
|
"@types/jest": {
|
||||||
"version": "26.0.23",
|
"version": "26.0.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz",
|
||||||
|
"integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"jest-diff": "^26.0.0",
|
"jest-diff": "^26.0.0",
|
||||||
"pretty-format": "^26.0.0"
|
"pretty-format": "^26.0.0"
|
||||||
|
@ -21146,7 +21029,9 @@
|
||||||
"version": "3.0.3"
|
"version": "3.0.3"
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "12.20.15"
|
"version": "12.20.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.16.tgz",
|
||||||
|
"integrity": "sha512-6CLxw83vQf6DKqXxMPwl8qpF8I7THFZuIwLt4TnNsumxkp1VsRZWT8txQxncT/Rl2UojTsFzWgDG4FRMwafrlA=="
|
||||||
},
|
},
|
||||||
"@types/normalize-package-data": {
|
"@types/normalize-package-data": {
|
||||||
"version": "2.4.0"
|
"version": "2.4.0"
|
||||||
|
@ -21164,9 +21049,9 @@
|
||||||
"version": "1.5.4"
|
"version": "1.5.4"
|
||||||
},
|
},
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
"version": "17.0.13",
|
"version": "17.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz",
|
||||||
"integrity": "sha512-D/G3PiuqTfE3IMNjLn/DCp6umjVCSvtZTPdtAFy5+Ved6CsdRvivfKeCzw79W4AatShtU4nGqgvOv5Gro534vQ==",
|
"integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
@ -21174,17 +21059,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/react-blockies": {
|
"@types/react-blockies": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-blockies/-/react-blockies-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-blockies/-/react-blockies-1.4.1.tgz",
|
||||||
"integrity": "sha512-Bw0CraHz5XE61yu64KqfyQ6D9XaPlZo96E2+D2mCE2ZiCeaj5rCRhalHeQ2R684qIInkHACTyAw2BZNWd73PeQ==",
|
"integrity": "sha512-aDX0g0hwzdodkGLSDNUQr6gXxwclGjnhS8jhsR8uQhAfe/7i3GZD/NDcSlQ2SiQiLhfRxX3NlY+nvBwf5Y0tTg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/react-dom": {
|
"@types/react-dom": {
|
||||||
"version": "17.0.8",
|
"version": "17.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
|
||||||
"integrity": "sha512-0ohAiJAx1DAUEcY9UopnfwCE9sSMDGnY/oXjWMax6g3RpzmTt2GMyMVAXcbn0mo8XAff0SbQJl2/SBU+hjSZ1A==",
|
"integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
|
@ -21199,9 +21084,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/react-router-dom": {
|
"@types/react-router-dom": {
|
||||||
"version": "5.1.7",
|
"version": "5.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz",
|
||||||
"integrity": "sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==",
|
"integrity": "sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/history": "*",
|
"@types/history": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
|
@ -21212,11 +21097,6 @@
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/scheduler": {
|
"@types/scheduler": {
|
||||||
|
@ -21252,11 +21132,6 @@
|
||||||
"@types/uglify-js": "*",
|
"@types/uglify-js": "*",
|
||||||
"@types/webpack-sources": "*",
|
"@types/webpack-sources": "*",
|
||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/webpack-sources": {
|
"@types/webpack-sources": {
|
||||||
|
@ -21267,9 +21142,6 @@
|
||||||
"source-map": "^0.7.3"
|
"source-map": "^0.7.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
},
|
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.7.3"
|
"version": "0.7.3"
|
||||||
}
|
}
|
||||||
|
@ -24518,9 +24390,9 @@
|
||||||
"version": "1.8.1"
|
"version": "1.8.1"
|
||||||
},
|
},
|
||||||
"ethers": {
|
"ethers": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.4.1.tgz",
|
||||||
"integrity": "sha512-hqN1x0CV8VMpQ25WnNEjaMqtB3nA4DRAb2FSmmNaUbD1dF6kWbHs8YaXbVvD37FCg3GTEyc4rV9Pxafk1ByHKw==",
|
"integrity": "sha512-SrcddMdCgP1hukDvCPd87Aipbf4NWjQvdfAbZ65XSZGbfyuYPtIrUJPDH5B1SBRsdlfiEgX3eoz28DdBDzMNFg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ethersproject/abi": "5.4.0",
|
"@ethersproject/abi": "5.4.0",
|
||||||
"@ethersproject/abstract-provider": "5.4.0",
|
"@ethersproject/abstract-provider": "5.4.0",
|
||||||
|
@ -24537,10 +24409,10 @@
|
||||||
"@ethersproject/json-wallets": "5.4.0",
|
"@ethersproject/json-wallets": "5.4.0",
|
||||||
"@ethersproject/keccak256": "5.4.0",
|
"@ethersproject/keccak256": "5.4.0",
|
||||||
"@ethersproject/logger": "5.4.0",
|
"@ethersproject/logger": "5.4.0",
|
||||||
"@ethersproject/networks": "5.4.0",
|
"@ethersproject/networks": "5.4.1",
|
||||||
"@ethersproject/pbkdf2": "5.4.0",
|
"@ethersproject/pbkdf2": "5.4.0",
|
||||||
"@ethersproject/properties": "5.4.0",
|
"@ethersproject/properties": "5.4.0",
|
||||||
"@ethersproject/providers": "5.4.0",
|
"@ethersproject/providers": "5.4.1",
|
||||||
"@ethersproject/random": "5.4.0",
|
"@ethersproject/random": "5.4.0",
|
||||||
"@ethersproject/rlp": "5.4.0",
|
"@ethersproject/rlp": "5.4.0",
|
||||||
"@ethersproject/sha2": "5.4.0",
|
"@ethersproject/sha2": "5.4.0",
|
||||||
|
@ -25941,11 +25813,6 @@
|
||||||
"pretty-format": "^26.6.0",
|
"pretty-format": "^26.6.0",
|
||||||
"stack-utils": "^2.0.2",
|
"stack-utils": "^2.0.2",
|
||||||
"throat": "^5.0.0"
|
"throat": "^5.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-cli": {
|
"jest-cli": {
|
||||||
|
@ -26024,11 +25891,6 @@
|
||||||
"jest-mock": "^26.6.2",
|
"jest-mock": "^26.6.2",
|
||||||
"jest-util": "^26.6.2",
|
"jest-util": "^26.6.2",
|
||||||
"jsdom": "^16.4.0"
|
"jsdom": "^16.4.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-environment-node": {
|
"jest-environment-node": {
|
||||||
|
@ -26040,11 +25902,6 @@
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"jest-mock": "^26.6.2",
|
"jest-mock": "^26.6.2",
|
||||||
"jest-util": "^26.6.2"
|
"jest-util": "^26.6.2"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-get-type": {
|
"jest-get-type": {
|
||||||
|
@ -26069,9 +25926,6 @@
|
||||||
"walker": "^1.0.7"
|
"walker": "^1.0.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
},
|
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -26102,11 +25956,6 @@
|
||||||
"jest-util": "^26.6.2",
|
"jest-util": "^26.6.2",
|
||||||
"pretty-format": "^26.6.2",
|
"pretty-format": "^26.6.2",
|
||||||
"throat": "^5.0.0"
|
"throat": "^5.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-leak-detector": {
|
"jest-leak-detector": {
|
||||||
|
@ -26144,11 +25993,6 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"@jest/types": "^26.6.2",
|
"@jest/types": "^26.6.2",
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-pnp-resolver": {
|
"jest-pnp-resolver": {
|
||||||
|
@ -26202,11 +26046,6 @@
|
||||||
"jest-worker": "^26.6.2",
|
"jest-worker": "^26.6.2",
|
||||||
"source-map-support": "^0.5.6",
|
"source-map-support": "^0.5.6",
|
||||||
"throat": "^5.0.0"
|
"throat": "^5.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-runtime": {
|
"jest-runtime": {
|
||||||
|
@ -26251,11 +26090,6 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"graceful-fs": "^4.2.4"
|
"graceful-fs": "^4.2.4"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-snapshot": {
|
"jest-snapshot": {
|
||||||
|
@ -26296,11 +26130,6 @@
|
||||||
"graceful-fs": "^4.2.4",
|
"graceful-fs": "^4.2.4",
|
||||||
"is-ci": "^2.0.0",
|
"is-ci": "^2.0.0",
|
||||||
"micromatch": "^4.0.2"
|
"micromatch": "^4.0.2"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-validate": {
|
"jest-validate": {
|
||||||
|
@ -26341,11 +26170,6 @@
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"jest-util": "^26.6.2",
|
"jest-util": "^26.6.2",
|
||||||
"string-length": "^4.0.1"
|
"string-length": "^4.0.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-worker": {
|
"jest-worker": {
|
||||||
|
@ -26354,11 +26178,6 @@
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"merge-stream": "^2.0.0",
|
"merge-stream": "^2.0.0",
|
||||||
"supports-color": "^7.0.0"
|
"supports-color": "^7.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"js-sha3": {
|
"js-sha3": {
|
||||||
|
@ -29415,11 +29234,6 @@
|
||||||
"@types/estree": "*",
|
"@types/estree": "*",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"acorn": "^7.1.0"
|
"acorn": "^7.1.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "14.14.31"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rollup-plugin-babel": {
|
"rollup-plugin-babel": {
|
||||||
|
|
32
package.json
32
package.json
|
@ -4,11 +4,11 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@craco/craco": "^6.1.2",
|
"@craco/craco": "^6.2.0",
|
||||||
"@fontsource/fira-code": "^4.4.5",
|
"@fontsource/fira-code": "^4.5.0",
|
||||||
"@fontsource/roboto": "^4.4.5",
|
"@fontsource/roboto": "^4.5.0",
|
||||||
"@fontsource/roboto-mono": "^4.4.5",
|
"@fontsource/roboto-mono": "^4.5.0",
|
||||||
"@fontsource/space-grotesk": "^4.4.5",
|
"@fontsource/space-grotesk": "^4.5.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
||||||
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
||||||
|
@ -17,13 +17,13 @@
|
||||||
"@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.15",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/node": "^12.0.0",
|
"@types/node": "^12.20.16",
|
||||||
"@types/react": "^17.0.13",
|
"@types/react": "^17.0.14",
|
||||||
"@types/react-blockies": "^1.4.0",
|
"@types/react-blockies": "^1.4.1",
|
||||||
"@types/react-dom": "^17.0.8",
|
"@types/react-dom": "^17.0.9",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
"ethers": "^5.4.0",
|
"ethers": "^5.4.1",
|
||||||
"query-string": "^7.0.1",
|
"query-string": "^7.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-blockies": "^1.4.1",
|
"react-blockies": "^1.4.1",
|
||||||
|
@ -42,9 +42,11 @@
|
||||||
"build": "craco build",
|
"build": "craco build",
|
||||||
"test": "craco test",
|
"test": "craco test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"serve-4bytes": "serve -p 3001 -C -c serve-4bytes.json",
|
"start-assets": "docker run --rm -p 3001:80 --name otterscan-assets -d -v$(pwd)/4bytes/signatures:/usr/share/nginx/html/signatures/ -v$(pwd)/trustwallet/blockchains/ethereum/assets:/usr/share/nginx/html/assets -v$(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf nginx:1.21.1-alpine",
|
||||||
"serve-trustwallet-assets": "serve -p 3002 -C -c serve-trustwallet-assets.json",
|
"stop-assets": "docker stop otterscan-assets",
|
||||||
"serve": "serve -s build"
|
"build-docker": "docker build -t otterscan -f Dockerfile .",
|
||||||
|
"start-docker": "docker run --rm -p 5000:80 --name otterscan -d otterscan",
|
||||||
|
"stop-docker": "docker stop otterscan"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"erigonURL": "http://localhost:8545",
|
||||||
|
"assetsURLPrefix": "http://localhost:3001"
|
||||||
|
}
|
|
@ -6,10 +6,10 @@
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
"type": "image/x-icon"
|
"type": "image/x-icon"
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"background_color": "#ffffff"
|
"background_color": "#ffffff"
|
||||||
}
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
PARAMS="{\"erigonURL\": $(echo $ERIGON_URL | jq -aR .)}"
|
||||||
|
echo $PARAMS > /usr/share/nginx/html/config.json
|
||||||
|
nginx -g "daemon off;"
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"public": "4bytes/signatures",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"source": "**",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "Cache-Control",
|
|
||||||
"value": "max-age=600"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directoryListing": false
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"public": "trustwallet/blockchains/ethereum/assets",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"source": "**",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "Cache-Control",
|
|
||||||
"value": "max-age=600"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directoryListing": false
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useState, useEffect, useMemo } 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 { ethers } from "ethers";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import Blockies from "react-blockies";
|
import Blockies from "react-blockies";
|
||||||
import StandardFrame from "./StandardFrame";
|
import StandardFrame from "./StandardFrame";
|
||||||
|
@ -11,11 +12,12 @@ import ResultHeader from "./search/ResultHeader";
|
||||||
import PendingResults from "./search/PendingResults";
|
import PendingResults from "./search/PendingResults";
|
||||||
import TransactionItem from "./search/TransactionItem";
|
import TransactionItem from "./search/TransactionItem";
|
||||||
import { SearchController } from "./search/search";
|
import { SearchController } from "./search/search";
|
||||||
|
import { RuntimeContext } from "./useRuntime";
|
||||||
|
import { useENSCache } from "./useReverseCache";
|
||||||
import { useFeeToggler } from "./search/useFeeToggler";
|
import { useFeeToggler } from "./search/useFeeToggler";
|
||||||
import { ethers } from "ethers";
|
|
||||||
|
|
||||||
type BlockParams = {
|
type BlockParams = {
|
||||||
address: string;
|
addressOrName: string;
|
||||||
direction?: string;
|
direction?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,6 +26,7 @@ type PageParams = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddressTransactions: React.FC = () => {
|
const AddressTransactions: React.FC = () => {
|
||||||
|
const { provider } = useContext(RuntimeContext);
|
||||||
const params = useParams<BlockParams>();
|
const params = useParams<BlockParams>();
|
||||||
const location = useLocation<PageParams>();
|
const location = useLocation<PageParams>();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -33,28 +36,70 @@ const AddressTransactions: React.FC = () => {
|
||||||
hash = qs.h as string;
|
hash = qs.h as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize to checksummed address
|
const [checksummedAddress, setChecksummedAddress] = useState<string>();
|
||||||
const checksummedAddress = useMemo(
|
const [isENS, setENS] = useState<boolean>();
|
||||||
() => ethers.utils.getAddress(params.address),
|
const [error, setError] = useState<boolean>();
|
||||||
[params.address]
|
|
||||||
);
|
// If it looks like it is an ENS name, try to resolve it
|
||||||
if (params.address !== checksummedAddress) {
|
useEffect(() => {
|
||||||
console.log("NORMALIZE");
|
if (ethers.utils.isAddress(params.addressOrName)) {
|
||||||
history.replace(
|
setENS(false);
|
||||||
`/address/${checksummedAddress}${
|
setError(false);
|
||||||
params.direction ? "/" + params.direction : ""
|
|
||||||
}${location.search}`
|
// Normalize to checksummed address
|
||||||
);
|
const _checksummedAddress = ethers.utils.getAddress(params.addressOrName);
|
||||||
}
|
if (_checksummedAddress !== params.addressOrName) {
|
||||||
|
// Request came with a non-checksummed address; fix the URL
|
||||||
|
history.replace(
|
||||||
|
`/address/${_checksummedAddress}${
|
||||||
|
params.direction ? "/" + params.direction : ""
|
||||||
|
}${location.search}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setChecksummedAddress(_checksummedAddress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resolveName = async () => {
|
||||||
|
const resolvedAddress = await provider.resolveName(params.addressOrName);
|
||||||
|
if (resolvedAddress !== null) {
|
||||||
|
setENS(true);
|
||||||
|
setError(false);
|
||||||
|
setChecksummedAddress(resolvedAddress);
|
||||||
|
} else {
|
||||||
|
setENS(false);
|
||||||
|
setError(true);
|
||||||
|
setChecksummedAddress(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
resolveName();
|
||||||
|
}, [
|
||||||
|
provider,
|
||||||
|
params.addressOrName,
|
||||||
|
history,
|
||||||
|
params.direction,
|
||||||
|
location.search,
|
||||||
|
]);
|
||||||
|
|
||||||
const [controller, setController] = useState<SearchController>();
|
const [controller, setController] = useState<SearchController>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!provider || !checksummedAddress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const readFirstPage = async () => {
|
const readFirstPage = async () => {
|
||||||
const _controller = await SearchController.firstPage(checksummedAddress);
|
const _controller = await SearchController.firstPage(
|
||||||
|
provider,
|
||||||
|
checksummedAddress
|
||||||
|
);
|
||||||
setController(_controller);
|
setController(_controller);
|
||||||
};
|
};
|
||||||
const readMiddlePage = async (next: boolean) => {
|
const readMiddlePage = async (next: boolean) => {
|
||||||
const _controller = await SearchController.middlePage(
|
const _controller = await SearchController.middlePage(
|
||||||
|
provider,
|
||||||
checksummedAddress,
|
checksummedAddress,
|
||||||
hash!,
|
hash!,
|
||||||
next
|
next
|
||||||
|
@ -62,15 +107,18 @@ const AddressTransactions: React.FC = () => {
|
||||||
setController(_controller);
|
setController(_controller);
|
||||||
};
|
};
|
||||||
const readLastPage = async () => {
|
const readLastPage = async () => {
|
||||||
const _controller = await SearchController.lastPage(checksummedAddress);
|
const _controller = await SearchController.lastPage(
|
||||||
|
provider,
|
||||||
|
checksummedAddress
|
||||||
|
);
|
||||||
setController(_controller);
|
setController(_controller);
|
||||||
};
|
};
|
||||||
const prevPage = async () => {
|
const prevPage = async () => {
|
||||||
const _controller = await controller!.prevPage(hash!);
|
const _controller = await controller!.prevPage(provider, hash!);
|
||||||
setController(_controller);
|
setController(_controller);
|
||||||
};
|
};
|
||||||
const nextPage = async () => {
|
const nextPage = async () => {
|
||||||
const _controller = await controller!.nextPage(hash!);
|
const _controller = await controller!.nextPage(provider, hash!);
|
||||||
setController(_controller);
|
setController(_controller);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,81 +144,98 @@ const AddressTransactions: React.FC = () => {
|
||||||
readLastPage();
|
readLastPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [checksummedAddress, params.direction, hash, controller]);
|
}, [provider, checksummedAddress, params.direction, hash, controller]);
|
||||||
|
|
||||||
const page = useMemo(() => controller?.getPage(), [controller]);
|
const page = useMemo(() => controller?.getPage(), [controller]);
|
||||||
|
const reverseCache = useENSCache(provider, page);
|
||||||
|
|
||||||
document.title = `Address ${params.address} | Otterscan`;
|
document.title = `Address ${params.addressOrName} | Otterscan`;
|
||||||
|
|
||||||
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StandardFrame>
|
<StandardFrame>
|
||||||
<StandardSubtitle>
|
{error ? (
|
||||||
<div className="flex space-x-2 items-baseline">
|
<span className="text-base">
|
||||||
<Blockies
|
"{params.addressOrName}" is not an ETH address or ENS name.
|
||||||
className="self-center rounded"
|
</span>
|
||||||
seed={params.address.toLowerCase()}
|
) : (
|
||||||
scale={3}
|
checksummedAddress && (
|
||||||
/>
|
|
||||||
<span>Address</span>
|
|
||||||
<span className="font-address text-base text-gray-500">
|
|
||||||
{params.address}
|
|
||||||
</span>
|
|
||||||
<Copy value={params.address} rounded />
|
|
||||||
</div>
|
|
||||||
</StandardSubtitle>
|
|
||||||
<ContentFrame>
|
|
||||||
<div className="flex justify-between items-baseline py-3">
|
|
||||||
<div className="text-sm text-gray-500">
|
|
||||||
{page === undefined ? (
|
|
||||||
<>Waiting for search results...</>
|
|
||||||
) : (
|
|
||||||
<>{page.length} transactions on this page</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<UndefinedPageControl
|
|
||||||
address={params.address}
|
|
||||||
isFirst={controller?.isFirst}
|
|
||||||
isLast={controller?.isLast}
|
|
||||||
prevHash={page ? page[0].hash : ""}
|
|
||||||
nextHash={page ? page[page.length - 1].hash : ""}
|
|
||||||
disabled={controller === undefined}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ResultHeader
|
|
||||||
feeDisplay={feeDisplay}
|
|
||||||
feeDisplayToggler={feeDisplayToggler}
|
|
||||||
/>
|
|
||||||
{controller ? (
|
|
||||||
<>
|
<>
|
||||||
{controller.getPage().map((tx) => (
|
<StandardSubtitle>
|
||||||
<TransactionItem
|
<div className="flex space-x-2 items-baseline">
|
||||||
key={tx.hash}
|
<Blockies
|
||||||
tx={tx}
|
className="self-center rounded"
|
||||||
selectedAddress={params.address}
|
seed={checksummedAddress.toLowerCase()}
|
||||||
feeDisplay={feeDisplay}
|
scale={3}
|
||||||
/>
|
/>
|
||||||
))}
|
<span>Address</span>
|
||||||
<div className="flex justify-between items-baseline py-3">
|
<span className="font-address text-base text-gray-500">
|
||||||
<div className="text-sm text-gray-500">
|
{checksummedAddress}
|
||||||
{page !== undefined && (
|
</span>
|
||||||
<>{page.length} transactions on this page</>
|
<Copy value={checksummedAddress} rounded />
|
||||||
|
{isENS && (
|
||||||
|
<span className="rounded-lg px-2 py-1 bg-gray-200 text-gray-500 text-xs">
|
||||||
|
ENS: {params.addressOrName}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<UndefinedPageControl
|
</StandardSubtitle>
|
||||||
address={params.address}
|
<ContentFrame>
|
||||||
isFirst={controller.isFirst}
|
<div className="flex justify-between items-baseline py-3">
|
||||||
isLast={controller.isLast}
|
<div className="text-sm text-gray-500">
|
||||||
prevHash={page ? page[0].hash : ""}
|
{page === undefined ? (
|
||||||
nextHash={page ? page[page.length - 1].hash : ""}
|
<>Waiting for search results...</>
|
||||||
|
) : (
|
||||||
|
<>{page.length} transactions on this page</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<UndefinedPageControl
|
||||||
|
address={params.addressOrName}
|
||||||
|
isFirst={controller?.isFirst}
|
||||||
|
isLast={controller?.isLast}
|
||||||
|
prevHash={page ? page[0].hash : ""}
|
||||||
|
nextHash={page ? page[page.length - 1].hash : ""}
|
||||||
|
disabled={controller === undefined}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ResultHeader
|
||||||
|
feeDisplay={feeDisplay}
|
||||||
|
feeDisplayToggler={feeDisplayToggler}
|
||||||
/>
|
/>
|
||||||
</div>
|
{controller ? (
|
||||||
|
<>
|
||||||
|
{controller.getPage().map((tx) => (
|
||||||
|
<TransactionItem
|
||||||
|
key={tx.hash}
|
||||||
|
tx={tx}
|
||||||
|
ensCache={reverseCache}
|
||||||
|
selectedAddress={checksummedAddress}
|
||||||
|
feeDisplay={feeDisplay}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<div className="flex justify-between items-baseline py-3">
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
{page !== undefined && (
|
||||||
|
<>{page.length} transactions on this page</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<UndefinedPageControl
|
||||||
|
address={params.addressOrName}
|
||||||
|
isFirst={controller.isFirst}
|
||||||
|
isLast={controller.isLast}
|
||||||
|
prevHash={page ? page[0].hash : ""}
|
||||||
|
nextHash={page ? page[page.length - 1].hash : ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<PendingResults />
|
||||||
|
)}
|
||||||
|
</ContentFrame>
|
||||||
</>
|
</>
|
||||||
) : (
|
)
|
||||||
<PendingResults />
|
)}
|
||||||
)}
|
|
||||||
</ContentFrame>
|
|
||||||
</StandardFrame>
|
</StandardFrame>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
65
src/App.tsx
65
src/App.tsx
|
@ -3,40 +3,47 @@ import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
||||||
import Home from "./Home";
|
import Home from "./Home";
|
||||||
import Search from "./Search";
|
import Search from "./Search";
|
||||||
import Title from "./Title";
|
import Title from "./Title";
|
||||||
|
import { RuntimeContext, useRuntime } from "./useRuntime";
|
||||||
|
|
||||||
const Block = React.lazy(() => import("./Block"));
|
const Block = React.lazy(() => import("./Block"));
|
||||||
const BlockTransactions = React.lazy(() => import("./BlockTransactions"));
|
const BlockTransactions = React.lazy(() => import("./BlockTransactions"));
|
||||||
const AddressTransactions = React.lazy(() => import("./AddressTransactions"));
|
const AddressTransactions = React.lazy(() => import("./AddressTransactions"));
|
||||||
const Transaction = React.lazy(() => import("./Transaction"));
|
const Transaction = React.lazy(() => import("./Transaction"));
|
||||||
|
|
||||||
const App = () => (
|
const App = () => {
|
||||||
<Suspense fallback={<>LOADING</>}>
|
const runtime = useRuntime();
|
||||||
<Router>
|
|
||||||
<Switch>
|
return (
|
||||||
<Route path="/" exact>
|
<Suspense fallback={<>LOADING</>}>
|
||||||
<Home />
|
<RuntimeContext.Provider value={runtime}>
|
||||||
</Route>
|
<Router>
|
||||||
<Route path="/search" exact>
|
<Switch>
|
||||||
<Search />
|
<Route path="/" exact>
|
||||||
</Route>
|
<Home />
|
||||||
<Route>
|
</Route>
|
||||||
<Title />
|
<Route path="/search" exact>
|
||||||
<Route path="/block/:blockNumberOrHash" exact>
|
<Search />
|
||||||
<Block />
|
</Route>
|
||||||
</Route>
|
<Route>
|
||||||
<Route path="/block/:blockNumber/txs" exact>
|
<Title />
|
||||||
<BlockTransactions />
|
<Route path="/block/:blockNumberOrHash" exact>
|
||||||
</Route>
|
<Block />
|
||||||
<Route path="/tx/:txhash">
|
</Route>
|
||||||
<Transaction />
|
<Route path="/block/:blockNumber/txs" exact>
|
||||||
</Route>
|
<BlockTransactions />
|
||||||
<Route path="/address/:address/:direction?">
|
</Route>
|
||||||
<AddressTransactions />
|
<Route path="/tx/:txhash">
|
||||||
</Route>
|
<Transaction />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
<Route path="/address/:addressOrName/:direction?">
|
||||||
</Router>
|
<AddressTransactions />
|
||||||
</Suspense>
|
</Route>
|
||||||
);
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</Router>
|
||||||
|
</RuntimeContext.Provider>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default React.memo(App);
|
export default React.memo(App);
|
||||||
|
|
110
src/Block.tsx
110
src/Block.tsx
|
@ -1,20 +1,32 @@
|
||||||
import React, { useEffect, useState, useMemo } from "react";
|
import React, { useEffect, useState, useMemo, useContext } from "react";
|
||||||
import { useParams, NavLink } from "react-router-dom";
|
import { useParams, NavLink } from "react-router-dom";
|
||||||
import { ethers, BigNumber } from "ethers";
|
import { ethers, BigNumber } from "ethers";
|
||||||
import { provider } from "./ethersconfig";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import {
|
||||||
|
faChevronLeft,
|
||||||
|
faChevronRight,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import StandardFrame from "./StandardFrame";
|
import StandardFrame from "./StandardFrame";
|
||||||
import StandardSubtitle from "./StandardSubtitle";
|
import StandardSubtitle from "./StandardSubtitle";
|
||||||
import ContentFrame from "./ContentFrame";
|
import ContentFrame from "./ContentFrame";
|
||||||
|
import NavButton from "./components/NavButton";
|
||||||
import Timestamp from "./components/Timestamp";
|
import Timestamp from "./components/Timestamp";
|
||||||
import GasValue from "./components/GasValue";
|
import GasValue from "./components/GasValue";
|
||||||
import BlockLink from "./components/BlockLink";
|
import BlockLink from "./components/BlockLink";
|
||||||
import AddressLink from "./components/AddressLink";
|
import AddressOrENSName from "./components/AddressOrENSName";
|
||||||
|
import TransactionValue from "./components/TransactionValue";
|
||||||
|
import HexValue from "./components/HexValue";
|
||||||
|
import { RuntimeContext } from "./useRuntime";
|
||||||
|
import { useLatestBlockNumber } from "./useLatestBlock";
|
||||||
|
|
||||||
type BlockParams = {
|
type BlockParams = {
|
||||||
blockNumberOrHash: string;
|
blockNumberOrHash: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ExtendedBlock extends ethers.providers.Block {
|
interface ExtendedBlock extends ethers.providers.Block {
|
||||||
|
blockReward: BigNumber;
|
||||||
|
unclesReward: BigNumber;
|
||||||
|
feeReward: BigNumber;
|
||||||
size: number;
|
size: number;
|
||||||
sha3Uncles: string;
|
sha3Uncles: string;
|
||||||
stateRoot: string;
|
stateRoot: string;
|
||||||
|
@ -22,26 +34,46 @@ interface ExtendedBlock extends ethers.providers.Block {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Block: React.FC = () => {
|
const Block: React.FC = () => {
|
||||||
|
const { provider } = useContext(RuntimeContext);
|
||||||
const params = useParams<BlockParams>();
|
const params = useParams<BlockParams>();
|
||||||
|
|
||||||
const [block, setBlock] = useState<ExtendedBlock>();
|
const [block, setBlock] = useState<ExtendedBlock>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const readBlock = async () => {
|
const readBlock = async () => {
|
||||||
let _rawBlock: any;
|
let blockPromise: Promise<any>;
|
||||||
if (ethers.utils.isHexString(params.blockNumberOrHash, 32)) {
|
if (ethers.utils.isHexString(params.blockNumberOrHash, 32)) {
|
||||||
_rawBlock = await provider.send("eth_getBlockByHash", [
|
blockPromise = provider.send("eth_getBlockByHash", [
|
||||||
params.blockNumberOrHash,
|
params.blockNumberOrHash,
|
||||||
false,
|
false,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
_rawBlock = await provider.send("eth_getBlockByNumber", [
|
blockPromise = provider.send("eth_getBlockByNumber", [
|
||||||
params.blockNumberOrHash,
|
params.blockNumberOrHash,
|
||||||
false,
|
false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
const [_rawBlock, _rawIssuance, _rawReceipts] = await Promise.all([
|
||||||
|
blockPromise,
|
||||||
|
provider.send("erigon_issuance", [params.blockNumberOrHash]),
|
||||||
|
provider.send("eth_getBlockReceipts", [params.blockNumberOrHash]),
|
||||||
|
]);
|
||||||
|
const receipts = (_rawReceipts as any[]).map((r) =>
|
||||||
|
provider.formatter.receipt(r)
|
||||||
|
);
|
||||||
|
const fees = receipts.reduce(
|
||||||
|
(acc, r) => acc.add(r.effectiveGasPrice.mul(r.gasUsed)),
|
||||||
|
BigNumber.from(0)
|
||||||
|
);
|
||||||
|
|
||||||
const _block = provider.formatter.block(_rawBlock);
|
const _block = provider.formatter.block(_rawBlock);
|
||||||
const extBlock: ExtendedBlock = {
|
const extBlock: ExtendedBlock = {
|
||||||
|
blockReward: provider.formatter.bigNumber(_rawIssuance.blockReward),
|
||||||
|
unclesReward: provider.formatter.bigNumber(_rawIssuance.uncleReward),
|
||||||
|
feeReward: fees,
|
||||||
size: provider.formatter.number(_rawBlock.size),
|
size: provider.formatter.number(_rawBlock.size),
|
||||||
sha3Uncles: _rawBlock.sha3Uncles,
|
sha3Uncles: _rawBlock.sha3Uncles,
|
||||||
stateRoot: _rawBlock.stateRoot,
|
stateRoot: _rawBlock.stateRoot,
|
||||||
|
@ -53,7 +85,7 @@ const Block: React.FC = () => {
|
||||||
setBlock(extBlock);
|
setBlock(extBlock);
|
||||||
};
|
};
|
||||||
readBlock();
|
readBlock();
|
||||||
}, [params.blockNumberOrHash]);
|
}, [provider, params.blockNumberOrHash]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (block) {
|
if (block) {
|
||||||
|
@ -65,10 +97,13 @@ const Block: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
return block && ethers.utils.toUtf8String(block.extraData);
|
return block && ethers.utils.toUtf8String(block.extraData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("Error while converting block extra data to string");
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}, [block]);
|
}, [block]);
|
||||||
|
|
||||||
|
const latestBlockNumber = useLatestBlockNumber(provider);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StandardFrame>
|
<StandardFrame>
|
||||||
<StandardSubtitle>
|
<StandardSubtitle>
|
||||||
|
@ -80,9 +115,36 @@ const Block: React.FC = () => {
|
||||||
{block && (
|
{block && (
|
||||||
<ContentFrame>
|
<ContentFrame>
|
||||||
<InfoRow title="Block Height">
|
<InfoRow title="Block Height">
|
||||||
<span className="font-bold">
|
<div className="flex space-x-1 items-baseline">
|
||||||
{ethers.utils.commify(block.number)}
|
<span className="font-bold mr-1">
|
||||||
</span>
|
{ethers.utils.commify(block.number)}
|
||||||
|
</span>
|
||||||
|
<NavButton
|
||||||
|
blockNum={block.number - 1}
|
||||||
|
disabled={block.number === 0}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faChevronLeft} />
|
||||||
|
</NavButton>
|
||||||
|
<NavButton
|
||||||
|
blockNum={block.number + 1}
|
||||||
|
disabled={
|
||||||
|
latestBlockNumber === undefined ||
|
||||||
|
block.number >= latestBlockNumber
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faChevronRight} />
|
||||||
|
</NavButton>
|
||||||
|
<NavButton
|
||||||
|
blockNum={latestBlockNumber!}
|
||||||
|
disabled={
|
||||||
|
latestBlockNumber === undefined ||
|
||||||
|
block.number >= latestBlockNumber
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faChevronRight} />
|
||||||
|
<FontAwesomeIcon icon={faChevronRight} />
|
||||||
|
</NavButton>
|
||||||
|
</div>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Timestamp">
|
<InfoRow title="Timestamp">
|
||||||
<Timestamp value={block.timestamp} />
|
<Timestamp value={block.timestamp} />
|
||||||
|
@ -97,12 +159,24 @@ const Block: React.FC = () => {
|
||||||
in this block
|
in this block
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Mined by">
|
<InfoRow title="Mined by">
|
||||||
<div className="flex">
|
<AddressOrENSName
|
||||||
<AddressLink address={block.miner} />
|
address={block.miner}
|
||||||
</div>
|
minerAddress={block.miner}
|
||||||
|
/>
|
||||||
|
</InfoRow>
|
||||||
|
<InfoRow title="Block Reward">
|
||||||
|
<TransactionValue value={block.blockReward.add(block.feeReward)} />
|
||||||
|
{!block.feeReward.isZero() && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
(<TransactionValue value={block.blockReward} hideUnit /> +{" "}
|
||||||
|
<TransactionValue value={block.feeReward} hideUnit />)
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</InfoRow>
|
||||||
|
<InfoRow title="Uncles Reward">
|
||||||
|
<TransactionValue value={block.unclesReward} />
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Block Reward">N/A</InfoRow>
|
|
||||||
<InfoRow title="Uncles Reward">N/A</InfoRow>
|
|
||||||
<InfoRow title="Difficult">
|
<InfoRow title="Difficult">
|
||||||
{ethers.utils.commify(block.difficulty)}
|
{ethers.utils.commify(block.difficulty)}
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
|
@ -124,16 +198,16 @@ const Block: React.FC = () => {
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Ether Price">N/A</InfoRow>
|
<InfoRow title="Ether Price">N/A</InfoRow>
|
||||||
<InfoRow title="Hash">
|
<InfoRow title="Hash">
|
||||||
<span className="font-hash">{block.hash}</span>
|
<HexValue value={block.hash} />
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Parent Hash">
|
<InfoRow title="Parent Hash">
|
||||||
<BlockLink blockTag={block.parentHash} />
|
<BlockLink blockTag={block.parentHash} />
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Sha3Uncles">
|
<InfoRow title="Sha3Uncles">
|
||||||
<span className="font-hash">{block.sha3Uncles}</span>
|
<HexValue value={block.sha3Uncles} />
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="StateRoot">
|
<InfoRow title="StateRoot">
|
||||||
<span className="font-hash">{block.stateRoot}</span>
|
<HexValue value={block.stateRoot} />
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Nonce">
|
<InfoRow title="Nonce">
|
||||||
<span className="font-data">{block.nonce}</span>
|
<span className="font-data">{block.nonce}</span>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import React, { useEffect, useState, useMemo } from "react";
|
import React, { useEffect, useState, useMemo, useContext } from "react";
|
||||||
import { useParams, useLocation } from "react-router";
|
import { useParams, useLocation } from "react-router";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { provider } from "./ethersconfig";
|
|
||||||
import StandardFrame from "./StandardFrame";
|
import StandardFrame from "./StandardFrame";
|
||||||
import StandardSubtitle from "./StandardSubtitle";
|
import StandardSubtitle from "./StandardSubtitle";
|
||||||
import ContentFrame from "./ContentFrame";
|
import ContentFrame from "./ContentFrame";
|
||||||
|
@ -14,6 +13,8 @@ import BlockLink from "./components/BlockLink";
|
||||||
import { ProcessedTransaction } from "./types";
|
import { ProcessedTransaction } from "./types";
|
||||||
import { PAGE_SIZE } from "./params";
|
import { PAGE_SIZE } from "./params";
|
||||||
import { useFeeToggler } from "./search/useFeeToggler";
|
import { useFeeToggler } from "./search/useFeeToggler";
|
||||||
|
import { RuntimeContext } from "./useRuntime";
|
||||||
|
import { useENSCache } from "./useReverseCache";
|
||||||
|
|
||||||
type BlockParams = {
|
type BlockParams = {
|
||||||
blockNumber: string;
|
blockNumber: string;
|
||||||
|
@ -24,6 +25,7 @@ type PageParams = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const BlockTransactions: React.FC = () => {
|
const BlockTransactions: React.FC = () => {
|
||||||
|
const { provider } = useContext(RuntimeContext);
|
||||||
const params = useParams<BlockParams>();
|
const params = useParams<BlockParams>();
|
||||||
const location = useLocation<PageParams>();
|
const location = useLocation<PageParams>();
|
||||||
const qs = queryString.parse(location.search);
|
const qs = queryString.parse(location.search);
|
||||||
|
@ -41,6 +43,10 @@ const BlockTransactions: React.FC = () => {
|
||||||
|
|
||||||
const [txs, setTxs] = useState<ProcessedTransaction[]>();
|
const [txs, setTxs] = useState<ProcessedTransaction[]>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const readBlock = async () => {
|
const readBlock = async () => {
|
||||||
const [_block, _receipts] = await Promise.all([
|
const [_block, _receipts] = await Promise.all([
|
||||||
provider.getBlockWithTransactions(blockNumber.toNumber()),
|
provider.getBlockWithTransactions(blockNumber.toNumber()),
|
||||||
|
@ -48,28 +54,52 @@ const BlockTransactions: React.FC = () => {
|
||||||
]);
|
]);
|
||||||
document.title = `Block #${_block.number} Transactions | Otterscan`;
|
document.title = `Block #${_block.number} Transactions | Otterscan`;
|
||||||
|
|
||||||
setTxs(
|
const responses = _block.transactions
|
||||||
_block.transactions
|
.map((t, i): ProcessedTransaction => {
|
||||||
.map((t, i) => {
|
return {
|
||||||
return {
|
blockNumber: blockNumber.toNumber(),
|
||||||
blockNumber: blockNumber.toNumber(),
|
timestamp: _block.timestamp,
|
||||||
timestamp: _block.timestamp,
|
miner: _block.miner,
|
||||||
idx: i,
|
idx: i,
|
||||||
hash: t.hash,
|
hash: t.hash,
|
||||||
from: t.from,
|
from: t.from,
|
||||||
to: t.to,
|
to: t.to,
|
||||||
value: t.value,
|
value: t.value,
|
||||||
fee: t.gasLimit.mul(t.gasPrice!),
|
fee: provider.formatter
|
||||||
gasPrice: t.gasPrice!,
|
.bigNumber(_receipts[i].gasUsed)
|
||||||
data: t.data,
|
.mul(t.gasPrice!),
|
||||||
status: _receipts[i].status,
|
gasPrice: t.gasPrice!,
|
||||||
};
|
data: t.data,
|
||||||
})
|
status: provider.formatter.number(_receipts[i].status),
|
||||||
.reverse()
|
};
|
||||||
|
})
|
||||||
|
.reverse();
|
||||||
|
setTxs(responses);
|
||||||
|
|
||||||
|
const internalChecks = await Promise.all(
|
||||||
|
responses.map(async (res) => {
|
||||||
|
const r = await provider.send("ots_getTransactionTransfers", [
|
||||||
|
res.hash,
|
||||||
|
]);
|
||||||
|
for (const t of r) {
|
||||||
|
if (
|
||||||
|
res.miner &&
|
||||||
|
(res.miner === ethers.utils.getAddress(t.from) ||
|
||||||
|
res.miner === ethers.utils.getAddress(t.to))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
const processedResponses = responses.map((r, i): ProcessedTransaction => {
|
||||||
|
return { ...r, internalMinerInteraction: internalChecks[i] };
|
||||||
|
});
|
||||||
|
setTxs(processedResponses);
|
||||||
};
|
};
|
||||||
readBlock();
|
readBlock();
|
||||||
}, [blockNumber]);
|
}, [provider, blockNumber]);
|
||||||
|
|
||||||
const page = useMemo(() => {
|
const page = useMemo(() => {
|
||||||
if (!txs) {
|
if (!txs) {
|
||||||
|
@ -80,6 +110,8 @@ const BlockTransactions: React.FC = () => {
|
||||||
}, [txs, pageNumber]);
|
}, [txs, pageNumber]);
|
||||||
const total = useMemo(() => txs?.length ?? 0, [txs]);
|
const total = useMemo(() => txs?.length ?? 0, [txs]);
|
||||||
|
|
||||||
|
const reverseCache = useENSCache(provider, page);
|
||||||
|
|
||||||
document.title = `Block #${blockNumber} Txns | Otterscan`;
|
document.title = `Block #${blockNumber} Txns | Otterscan`;
|
||||||
|
|
||||||
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
||||||
|
@ -112,7 +144,12 @@ const BlockTransactions: React.FC = () => {
|
||||||
{page ? (
|
{page ? (
|
||||||
<>
|
<>
|
||||||
{page.map((tx) => (
|
{page.map((tx) => (
|
||||||
<TransactionItem key={tx.hash} tx={tx} feeDisplay={feeDisplay} />
|
<TransactionItem
|
||||||
|
key={tx.hash}
|
||||||
|
tx={tx}
|
||||||
|
ensCache={reverseCache}
|
||||||
|
feeDisplay={feeDisplay}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<div className="flex justify-between items-baseline py-3">
|
<div className="flex justify-between items-baseline py-3">
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
|
|
40
src/Home.tsx
40
src/Home.tsx
|
@ -1,11 +1,13 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useContext } from "react";
|
||||||
import { NavLink, useHistory } from "react-router-dom";
|
import { NavLink, useHistory } from "react-router-dom";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import Logo from "./Logo";
|
import Logo from "./Logo";
|
||||||
import Timestamp from "./components/Timestamp";
|
import Timestamp from "./components/Timestamp";
|
||||||
import { provider } from "./ethersconfig";
|
import { RuntimeContext } from "./useRuntime";
|
||||||
|
import { useLatestBlock } from "./useLatestBlock";
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
|
const { provider } = useContext(RuntimeContext);
|
||||||
const [search, setSearch] = useState<string>();
|
const [search, setSearch] = useState<string>();
|
||||||
const [canSubmit, setCanSubmit] = useState<boolean>(false);
|
const [canSubmit, setCanSubmit] = useState<boolean>(false);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -24,29 +26,7 @@ const Home: React.FC = () => {
|
||||||
history.push(`/search?q=${search}`);
|
history.push(`/search?q=${search}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [latestBlock, setLatestBlock] = useState<ethers.providers.Block>();
|
const latestBlock = useLatestBlock(provider);
|
||||||
useEffect(() => {
|
|
||||||
const readLatestBlock = async () => {
|
|
||||||
const blockNum = await provider.getBlockNumber();
|
|
||||||
const _raw = await provider.send("erigon_getHeaderByNumber", [blockNum]);
|
|
||||||
const _block = provider.formatter.block(_raw);
|
|
||||||
setLatestBlock(_block);
|
|
||||||
};
|
|
||||||
readLatestBlock();
|
|
||||||
|
|
||||||
const listener = async (blockNumber: number) => {
|
|
||||||
const _raw = await provider.send("erigon_getHeaderByNumber", [
|
|
||||||
blockNumber,
|
|
||||||
]);
|
|
||||||
const _block = provider.formatter.block(_raw);
|
|
||||||
setLatestBlock(_block);
|
|
||||||
};
|
|
||||||
|
|
||||||
provider.on("block", listener);
|
|
||||||
return () => {
|
|
||||||
provider.removeListener("block", listener);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
document.title = "Home | Otterscan";
|
document.title = "Home | Otterscan";
|
||||||
|
|
||||||
|
@ -64,8 +44,9 @@ const Home: React.FC = () => {
|
||||||
className="w-full border rounded focus:outline-none px-2 py-1 mb-10"
|
className="w-full border rounded focus:outline-none px-2 py-1 mb-10"
|
||||||
type="text"
|
type="text"
|
||||||
size={50}
|
size={50}
|
||||||
placeholder="Search by address / txn hash / block number"
|
placeholder="Search by address / txn hash / block number / ENS name"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
autoFocus
|
||||||
></input>
|
></input>
|
||||||
<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-gray-100 hover:bg-gray-200 focus:outline-none"
|
||||||
|
@ -84,6 +65,13 @@ const Home: React.FC = () => {
|
||||||
<Timestamp value={latestBlock.timestamp} />
|
<Timestamp value={latestBlock.timestamp} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)}
|
)}
|
||||||
|
<span className="mx-auto mt-5 text-xs text-gray-500">
|
||||||
|
{provider ? (
|
||||||
|
<>Using Erigon node at {provider.connection.url}</>
|
||||||
|
) : (
|
||||||
|
<>Waiting for the provider...</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,7 +27,8 @@ const Search: React.FC = () => {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
history.replace("/");
|
// Assume it is an ENS name
|
||||||
|
history.replace(`/address/${q}`);
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -50,8 +50,8 @@ const Title: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm"
|
className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm"
|
||||||
type="text"
|
type="text"
|
||||||
size={50}
|
size={60}
|
||||||
placeholder='Type "/" to search by address / txn hash / block number'
|
placeholder='Type "/" to search by address / txn hash / block number / ENS name'
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
ref={searchRef}
|
ref={searchRef}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faCaretRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import AddressLink from "./components/AddressLink";
|
||||||
|
import AddressOrENSName from "./components/AddressOrENSName";
|
||||||
|
import TokenLogo from "./components/TokenLogo";
|
||||||
|
import FormattedBalance from "./components/FormattedBalance";
|
||||||
|
import { TokenMetas, TokenTransfer } from "./types";
|
||||||
|
|
||||||
|
type TokenTransferItemProps = {
|
||||||
|
t: TokenTransfer;
|
||||||
|
tokenMetas: TokenMetas;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
||||||
|
t,
|
||||||
|
tokenMetas,
|
||||||
|
}) => (
|
||||||
|
<div className="flex items-baseline space-x-2 truncate">
|
||||||
|
<span className="text-gray-500">
|
||||||
|
<FontAwesomeIcon icon={faCaretRight} size="1x" />
|
||||||
|
</span>
|
||||||
|
<div className="grid grid-cols-5">
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<span className="font-bold">From</span>
|
||||||
|
<AddressOrENSName address={t.from} />
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<span className="font-bold">To</span>
|
||||||
|
<AddressOrENSName address={t.to} />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-3 flex space-x-2">
|
||||||
|
<span className="font-bold">For</span>
|
||||||
|
<span>
|
||||||
|
<FormattedBalance
|
||||||
|
value={t.value}
|
||||||
|
decimals={tokenMetas[t.token].decimals}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="flex space-x-1 items-baseline truncate">
|
||||||
|
{tokenMetas[t.token] ? (
|
||||||
|
<>
|
||||||
|
<div className="self-center">
|
||||||
|
<TokenLogo address={t.token} name={tokenMetas[t.token].name} />
|
||||||
|
</div>
|
||||||
|
<AddressLink
|
||||||
|
address={t.token}
|
||||||
|
text={`${tokenMetas[t.token].name} (${
|
||||||
|
tokenMetas[t.token].symbol
|
||||||
|
})`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<AddressOrENSName address={t.token} />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default React.memo(TokenTransferItem);
|
|
@ -1,28 +1,33 @@
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useContext,
|
||||||
|
} from "react";
|
||||||
import { Route, Switch, useParams } from "react-router-dom";
|
import { Route, Switch, useParams } from "react-router-dom";
|
||||||
import { BigNumber, ethers } from "ethers";
|
import { BigNumber, ethers } from "ethers";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import {
|
import {
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faTimesCircle,
|
faTimesCircle,
|
||||||
faAngleRight,
|
|
||||||
faCaretRight,
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { provider } from "./ethersconfig";
|
|
||||||
import StandardFrame from "./StandardFrame";
|
import StandardFrame from "./StandardFrame";
|
||||||
import StandardSubtitle from "./StandardSubtitle";
|
import StandardSubtitle from "./StandardSubtitle";
|
||||||
import Tab from "./components/Tab";
|
import Tab from "./components/Tab";
|
||||||
import ContentFrame from "./ContentFrame";
|
import ContentFrame from "./ContentFrame";
|
||||||
import BlockLink from "./components/BlockLink";
|
import BlockLink from "./components/BlockLink";
|
||||||
|
import AddressOrENSName from "./components/AddressOrENSName";
|
||||||
import AddressLink from "./components/AddressLink";
|
import AddressLink from "./components/AddressLink";
|
||||||
import Copy from "./components/Copy";
|
import Copy from "./components/Copy";
|
||||||
import Timestamp from "./components/Timestamp";
|
import Timestamp from "./components/Timestamp";
|
||||||
import TokenLogo from "./components/TokenLogo";
|
import InternalTransfer from "./components/InternalTransfer";
|
||||||
import GasValue from "./components/GasValue";
|
import GasValue from "./components/GasValue";
|
||||||
import FormattedBalance from "./components/FormattedBalance";
|
import FormattedBalance from "./components/FormattedBalance";
|
||||||
|
import TokenTransferItem from "./TokenTransferItem";
|
||||||
import erc20 from "./erc20.json";
|
import erc20 from "./erc20.json";
|
||||||
|
import { TokenMetas, TokenTransfer, TransactionData, Transfer } from "./types";
|
||||||
const USE_OTS = true;
|
import { RuntimeContext } from "./useRuntime";
|
||||||
|
|
||||||
const TRANSFER_TOPIC =
|
const TRANSFER_TOPIC =
|
||||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
||||||
|
@ -31,62 +36,17 @@ type TransactionParams = {
|
||||||
txhash: string;
|
txhash: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TransactionData = {
|
|
||||||
transactionHash: string;
|
|
||||||
status: boolean;
|
|
||||||
blockNumber: number;
|
|
||||||
transactionIndex: number;
|
|
||||||
confirmations: number;
|
|
||||||
timestamp: number;
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
value: BigNumber;
|
|
||||||
tokenTransfers: TokenTransfer[];
|
|
||||||
tokenMetas: TokenMetas;
|
|
||||||
fee: BigNumber;
|
|
||||||
gasPrice: BigNumber;
|
|
||||||
gasLimit: BigNumber;
|
|
||||||
gasUsed: BigNumber;
|
|
||||||
gasUsedPerc: number;
|
|
||||||
nonce: number;
|
|
||||||
data: string;
|
|
||||||
logs: ethers.providers.Log[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type From = {
|
|
||||||
current: string;
|
|
||||||
depth: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Transfer = {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
value: BigNumber;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TokenTransfer = {
|
|
||||||
token: string;
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
value: BigNumber;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TokenMeta = {
|
|
||||||
name: string;
|
|
||||||
symbol: string;
|
|
||||||
decimals: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TokenMetas = {
|
|
||||||
[tokenAddress: string]: TokenMeta;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Transaction: React.FC = () => {
|
const Transaction: React.FC = () => {
|
||||||
|
const { provider } = useContext(RuntimeContext);
|
||||||
const params = useParams<TransactionParams>();
|
const params = useParams<TransactionParams>();
|
||||||
const { txhash } = params;
|
const { txhash } = params;
|
||||||
|
|
||||||
const [txData, setTxData] = useState<TransactionData>();
|
const [txData, setTxData] = useState<TransactionData>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const readBlock = async () => {
|
const readBlock = async () => {
|
||||||
const [_response, _receipt] = await Promise.all([
|
const [_response, _receipt] = await Promise.all([
|
||||||
provider.getTransaction(txhash),
|
provider.getTransaction(txhash),
|
||||||
|
@ -106,11 +66,12 @@ const Transaction: React.FC = () => {
|
||||||
}
|
}
|
||||||
tokenTransfers.push({
|
tokenTransfers.push({
|
||||||
token: l.address,
|
token: l.address,
|
||||||
from: ethers.utils.hexDataSlice(
|
from: ethers.utils.getAddress(
|
||||||
ethers.utils.arrayify(l.topics[1]),
|
ethers.utils.hexDataSlice(ethers.utils.arrayify(l.topics[1]), 12)
|
||||||
12
|
),
|
||||||
|
to: ethers.utils.getAddress(
|
||||||
|
ethers.utils.hexDataSlice(ethers.utils.arrayify(l.topics[2]), 12)
|
||||||
),
|
),
|
||||||
to: ethers.utils.hexDataSlice(ethers.utils.arrayify(l.topics[2]), 12),
|
|
||||||
value: BigNumber.from(l.data),
|
value: BigNumber.from(l.data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -141,6 +102,7 @@ const Transaction: React.FC = () => {
|
||||||
transactionIndex: _receipt.transactionIndex,
|
transactionIndex: _receipt.transactionIndex,
|
||||||
confirmations: _receipt.confirmations,
|
confirmations: _receipt.confirmations,
|
||||||
timestamp: _block.timestamp,
|
timestamp: _block.timestamp,
|
||||||
|
miner: _block.miner,
|
||||||
from: _receipt.from,
|
from: _receipt.from,
|
||||||
to: _receipt.to,
|
to: _receipt.to,
|
||||||
value: _response.value,
|
value: _response.value,
|
||||||
|
@ -158,55 +120,24 @@ const Transaction: React.FC = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
readBlock();
|
readBlock();
|
||||||
}, [txhash]);
|
}, [provider, txhash]);
|
||||||
|
|
||||||
const [transfers, setTransfers] = useState<Transfer[]>();
|
const [transfers, setTransfers] = useState<Transfer[]>();
|
||||||
|
const sendsEthToMiner = useMemo(() => {
|
||||||
const traceTransfersUsingDebugTrace = async () => {
|
if (!txData || !transfers) {
|
||||||
const r = await provider.send("debug_traceTransaction", [
|
return false;
|
||||||
txData?.transactionHash,
|
|
||||||
{ disableStorage: true, disableMemory: true },
|
|
||||||
]);
|
|
||||||
const fromStack: From[] = [
|
|
||||||
{
|
|
||||||
current: txData!.to,
|
|
||||||
depth: 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const _transfers: Transfer[] = [];
|
|
||||||
for (const l of r.structLogs) {
|
|
||||||
if (l.op !== "CALL") {
|
|
||||||
if (parseInt(l.depth) === fromStack[fromStack.length - 1].depth) {
|
|
||||||
fromStack.pop();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { stack } = l;
|
|
||||||
const addr = stack[stack.length - 2].slice(24);
|
|
||||||
const value = BigNumber.from("0x" + stack[stack.length - 3]);
|
|
||||||
if (!value.isZero()) {
|
|
||||||
const t: Transfer = {
|
|
||||||
from: ethers.utils.getAddress(
|
|
||||||
fromStack[fromStack.length - 1].current
|
|
||||||
),
|
|
||||||
to: ethers.utils.getAddress(addr),
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
_transfers.push(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
fromStack.push({
|
|
||||||
current: addr,
|
|
||||||
depth: parseInt(l.depth),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTransfers(_transfers);
|
for (const t of transfers) {
|
||||||
};
|
if (t.to === txData.miner) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [txData, transfers]);
|
||||||
|
|
||||||
const traceTransfersUsingOtsTrace = useCallback(async () => {
|
const traceTransfersUsingOtsTrace = useCallback(async () => {
|
||||||
if (!txData) {
|
if (!provider || !txData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,18 +147,16 @@ const Transaction: React.FC = () => {
|
||||||
const _transfers: Transfer[] = [];
|
const _transfers: Transfer[] = [];
|
||||||
for (const t of r) {
|
for (const t of r) {
|
||||||
_transfers.push({
|
_transfers.push({
|
||||||
from: t.from,
|
from: ethers.utils.getAddress(t.from),
|
||||||
to: t.to,
|
to: ethers.utils.getAddress(t.to),
|
||||||
value: t.value,
|
value: t.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setTransfers(_transfers);
|
setTransfers(_transfers);
|
||||||
}, [txData]);
|
}, [provider, txData]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (USE_OTS) {
|
traceTransfersUsingOtsTrace();
|
||||||
traceTransfersUsingOtsTrace();
|
|
||||||
}
|
|
||||||
}, [traceTransfersUsingOtsTrace]);
|
}, [traceTransfersUsingOtsTrace]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -276,40 +205,31 @@ const Transaction: React.FC = () => {
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="From">
|
<InfoRow title="From">
|
||||||
<div className="flex items-baseline space-x-2">
|
<div className="flex items-baseline space-x-2">
|
||||||
<AddressLink address={txData.from} />
|
<AddressOrENSName
|
||||||
|
address={txData.from}
|
||||||
|
minerAddress={txData.miner}
|
||||||
|
/>
|
||||||
<Copy value={txData.from} />
|
<Copy value={txData.from} />
|
||||||
</div>
|
</div>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Interacted With (To)">
|
<InfoRow title="Interacted With (To)">
|
||||||
<div className="flex items-baseline space-x-2">
|
<div className="flex items-baseline space-x-2">
|
||||||
<AddressLink address={txData.to} />
|
<AddressOrENSName
|
||||||
|
address={txData.to}
|
||||||
|
minerAddress={txData.miner}
|
||||||
|
/>
|
||||||
<Copy value={txData.to} />
|
<Copy value={txData.to} />
|
||||||
</div>
|
</div>
|
||||||
{transfers ? (
|
{transfers && (
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
{transfers.map((t, i) => (
|
{transfers.map((t, i) => (
|
||||||
<div key={i} className="flex space-x-1 text-xs">
|
<InternalTransfer
|
||||||
<span className="text-gray-500">
|
key={i}
|
||||||
<FontAwesomeIcon icon={faAngleRight} size="1x" />{" "}
|
txData={txData}
|
||||||
TRANSFER
|
transfer={t}
|
||||||
</span>
|
/>
|
||||||
<span>{ethers.utils.formatEther(t.value)} Ether</span>
|
|
||||||
<span className="text-gray-500">From</span>
|
|
||||||
<AddressLink address={t.from} />
|
|
||||||
<span className="text-gray-500">To</span>
|
|
||||||
<AddressLink address={t.to} />
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
!USE_OTS && (
|
|
||||||
<button
|
|
||||||
className="rounded focus:outline-none bg-gray-100 mt-2 px-3 py-2"
|
|
||||||
onClick={traceTransfersUsingDebugTrace}
|
|
||||||
>
|
|
||||||
Trace transfers
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Transaction Action"></InfoRow>
|
<InfoRow title="Transaction Action"></InfoRow>
|
||||||
|
@ -318,48 +238,13 @@ const Transaction: React.FC = () => {
|
||||||
title={`Tokens Transferred (${txData.tokenTransfers.length})`}
|
title={`Tokens Transferred (${txData.tokenTransfers.length})`}
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{txData.tokenTransfers &&
|
{txData.tokenTransfers.map((t, i) => (
|
||||||
txData.tokenTransfers.map((t, i) => (
|
<TokenTransferItem
|
||||||
<div
|
key={i}
|
||||||
className="flex items-baseline space-x-2 truncate"
|
t={t}
|
||||||
key={i}
|
tokenMetas={txData.tokenMetas}
|
||||||
>
|
/>
|
||||||
<span className="text-gray-500">
|
))}
|
||||||
<FontAwesomeIcon icon={faCaretRight} size="1x" />
|
|
||||||
</span>
|
|
||||||
<span className="font-bold">From</span>
|
|
||||||
<AddressLink address={t.from} />
|
|
||||||
<span className="font-bold">To</span>
|
|
||||||
<AddressLink address={t.to} />
|
|
||||||
<span className="font-bold">For</span>
|
|
||||||
<span>
|
|
||||||
<FormattedBalance
|
|
||||||
value={t.value}
|
|
||||||
decimals={txData.tokenMetas[t.token].decimals}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className="flex space-x-1 items-baseline truncate">
|
|
||||||
{txData.tokenMetas[t.token] ? (
|
|
||||||
<>
|
|
||||||
<div className="self-center">
|
|
||||||
<TokenLogo
|
|
||||||
address={t.token}
|
|
||||||
name={txData.tokenMetas[t.token].name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<AddressLink
|
|
||||||
address={t.token}
|
|
||||||
text={`${
|
|
||||||
txData.tokenMetas[t.token].name
|
|
||||||
} (${txData.tokenMetas[t.token].symbol})`}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<AddressLink address={t.token} />
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
)}
|
)}
|
||||||
|
@ -372,9 +257,21 @@ const Transaction: React.FC = () => {
|
||||||
<FormattedBalance value={txData.fee} /> Ether
|
<FormattedBalance value={txData.fee} /> Ether
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Gas Price">
|
<InfoRow title="Gas Price">
|
||||||
<FormattedBalance value={txData.gasPrice} /> Ether (
|
<div className="flex items-baseline space-x-1">
|
||||||
<FormattedBalance value={txData.gasPrice} decimals={9} />{" "}
|
<span>
|
||||||
Gwei)
|
<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
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
<InfoRow title="Ether Price">N/A</InfoRow>
|
<InfoRow title="Ether Price">N/A</InfoRow>
|
||||||
<InfoRow title="Gas Limit">
|
<InfoRow title="Gas Limit">
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const Address: React.FC = ({ children }) => (
|
type AddressProps = {
|
||||||
<span className="font-address text-gray-400 truncate">
|
address: string;
|
||||||
<p className="truncate">{children}</p>
|
};
|
||||||
|
|
||||||
|
const Address: React.FC<AddressProps> = ({ address }) => (
|
||||||
|
<span className="font-address text-gray-400 truncate" title={address}>
|
||||||
|
<p className="truncate">{address}</p>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Address;
|
export default React.memo(Address);
|
||||||
|
|
|
@ -11,7 +11,7 @@ const AddressLink: React.FC<AddressLinkProps> = ({ address, text }) => (
|
||||||
className="text-link-blue hover:text-link-blue-hover font-address truncate"
|
className="text-link-blue hover:text-link-blue-hover font-address truncate"
|
||||||
to={`/address/${address}`}
|
to={`/address/${address}`}
|
||||||
>
|
>
|
||||||
<p className="truncate" title={text}>
|
<p className="truncate" title={text ?? address}>
|
||||||
{text ?? address}
|
{text ?? address}
|
||||||
</p>
|
</p>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faCoins } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import Address from "./Address";
|
||||||
|
import AddressLink from "./AddressLink";
|
||||||
|
import ENSName from "./ENSName";
|
||||||
|
import ENSNameLink from "./ENSNameLink";
|
||||||
|
|
||||||
|
type AddressOrENSNameProps = {
|
||||||
|
address: string;
|
||||||
|
ensName?: string;
|
||||||
|
selectedAddress?: string;
|
||||||
|
minerAddress?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddressOrENSName: React.FC<AddressOrENSNameProps> = ({
|
||||||
|
address,
|
||||||
|
ensName,
|
||||||
|
selectedAddress,
|
||||||
|
minerAddress,
|
||||||
|
}) => (
|
||||||
|
<div className="flex items-baseline space-x-1 truncate">
|
||||||
|
{minerAddress !== undefined && minerAddress === address && (
|
||||||
|
<span className="text-yellow-400" title="Miner address">
|
||||||
|
<FontAwesomeIcon icon={faCoins} size="1x" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{address === selectedAddress ? (
|
||||||
|
<>
|
||||||
|
{ensName ? (
|
||||||
|
<ENSName name={ensName} address={address} />
|
||||||
|
) : (
|
||||||
|
<Address address={address} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{ensName ? (
|
||||||
|
<ENSNameLink name={ensName} address={address} />
|
||||||
|
) : (
|
||||||
|
<AddressLink address={address} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default React.memo(AddressOrENSName);
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react";
|
||||||
|
import ENSLogo from "./ensLogo.svg";
|
||||||
|
|
||||||
|
type ENSNameProps = {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENSName: React.FC<ENSNameProps> = ({ name, address }) => (
|
||||||
|
<div
|
||||||
|
className="flex items-baseline space-x-1 font-sans text-gray-700 truncate"
|
||||||
|
title={`${name}: ${address}`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="self-center filter grayscale"
|
||||||
|
src={ENSLogo}
|
||||||
|
alt="ENS Logo"
|
||||||
|
width={12}
|
||||||
|
height={12}
|
||||||
|
/>
|
||||||
|
<p className="truncate">{name}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default React.memo(ENSName);
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from "react";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
import ENSLogo from "./ensLogo.svg";
|
||||||
|
|
||||||
|
type ENSNameLinkProps = {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENSNameLink: React.FC<ENSNameLinkProps> = ({ name, address }) => (
|
||||||
|
<NavLink
|
||||||
|
className="flex items-baseline space-x-1 font-sans text-link-blue hover:text-link-blue-hover truncate"
|
||||||
|
to={`/address/${name}`}
|
||||||
|
title={`${name}: ${address}`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="self-center"
|
||||||
|
src={ENSLogo}
|
||||||
|
alt="ENS Logo"
|
||||||
|
width={12}
|
||||||
|
height={12}
|
||||||
|
/>
|
||||||
|
<p className="truncate">{name}</p>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default React.memo(ENSNameLink);
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type HexValueProps = {
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const HexValue: React.FC<HexValueProps> = ({ value }) => {
|
||||||
|
const shards: string[] = [value.slice(0, 10)];
|
||||||
|
for (let i = 10; i < value.length; i += 8) {
|
||||||
|
shards.push(value.slice(i, i + 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{shards.map((s, i) => (
|
||||||
|
<span
|
||||||
|
className={`font-hash ${
|
||||||
|
i % 2 === 0 ? "text-black" : "text-gray-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{s}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(HexValue);
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React from "react";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faAngleRight, faCoins } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import AddressLink from "./AddressLink";
|
||||||
|
import { TransactionData, Transfer } from "../types";
|
||||||
|
|
||||||
|
type InternalTransferProps = {
|
||||||
|
txData: TransactionData;
|
||||||
|
transfer: Transfer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const InternalTransfer: React.FC<InternalTransferProps> = ({
|
||||||
|
txData,
|
||||||
|
transfer,
|
||||||
|
}) => {
|
||||||
|
const fromMiner =
|
||||||
|
txData.miner !== undefined && transfer.from === txData.miner;
|
||||||
|
const toMiner = txData.miner !== undefined && transfer.to === txData.miner;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-baseline space-x-1 text-xs">
|
||||||
|
<span className="text-gray-500">
|
||||||
|
<FontAwesomeIcon icon={faAngleRight} size="1x" /> TRANSFER
|
||||||
|
</span>
|
||||||
|
<span>{ethers.utils.formatEther(transfer.value)} Ether</span>
|
||||||
|
<span className="text-gray-500">From</span>
|
||||||
|
<div
|
||||||
|
className={`flex items-baseline space-x-1 ${
|
||||||
|
fromMiner ? "rounded px-2 py-1 bg-yellow-100" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{fromMiner && (
|
||||||
|
<span className="text-yellow-400" title="Miner address">
|
||||||
|
<FontAwesomeIcon icon={faCoins} size="1x" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<AddressLink address={transfer.from} />
|
||||||
|
</div>
|
||||||
|
<span className="text-gray-500">To</span>
|
||||||
|
<div
|
||||||
|
className={`flex items-baseline space-x-1 px-2 py-1 ${
|
||||||
|
toMiner ? "rounded px-2 py-1 bg-yellow-100" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{toMiner && (
|
||||||
|
<span className="text-yellow-400" title="Miner address">
|
||||||
|
<FontAwesomeIcon icon={faCoins} size="1x" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<AddressLink address={transfer.to} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(InternalTransfer);
|
|
@ -1,10 +1,14 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useContext } from "react";
|
||||||
|
import { fourBytesURL } from "../url";
|
||||||
|
import { RuntimeContext } from "../useRuntime";
|
||||||
|
|
||||||
type MethodNameProps = {
|
type MethodNameProps = {
|
||||||
data: string;
|
data: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MethodName: React.FC<MethodNameProps> = ({ data }) => {
|
const MethodName: React.FC<MethodNameProps> = ({ data }) => {
|
||||||
|
const runtime = useContext(RuntimeContext);
|
||||||
|
|
||||||
const [name, setName] = useState<string>();
|
const [name, setName] = useState<string>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data === "0x") {
|
if (data === "0x") {
|
||||||
|
@ -16,7 +20,13 @@ const MethodName: React.FC<MethodNameProps> = ({ data }) => {
|
||||||
|
|
||||||
// Try to resolve 4bytes name
|
// Try to resolve 4bytes name
|
||||||
const fourBytes = _name.slice(2);
|
const fourBytes = _name.slice(2);
|
||||||
const signatureURL = `http://localhost:3001/${fourBytes}`;
|
const { config } = runtime;
|
||||||
|
if (!config) {
|
||||||
|
setName(_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signatureURL = fourBytesURL(config.assetsURLPrefix ?? "", fourBytes);
|
||||||
fetch(signatureURL)
|
fetch(signatureURL)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
@ -37,7 +47,7 @@ const MethodName: React.FC<MethodNameProps> = ({ data }) => {
|
||||||
|
|
||||||
// Use the default 4 bytes as name
|
// Use the default 4 bytes as name
|
||||||
setName(_name);
|
setName(_name);
|
||||||
}, [data]);
|
}, [runtime, data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-blue-50 rounded-lg px-3 py-1 min-h-full flex items-baseline text-xs max-w-max">
|
<div className="bg-blue-50 rounded-lg px-3 py-1 min-h-full flex items-baseline text-xs max-w-max">
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
|
||||||
|
type NavButtonProps = {
|
||||||
|
blockNum: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NavButton: React.FC<NavButtonProps> = ({
|
||||||
|
blockNum,
|
||||||
|
disabled,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
if (disabled) {
|
||||||
|
return (
|
||||||
|
<span className="bg-link-blue bg-opacity-10 text-gray-400 rounded px-2 py-1 text-xs">
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
className="transition-colors bg-link-blue bg-opacity-10 text-link-blue hover:bg-opacity-100 hover:text-white disabled:bg-link-blue disabled:text-gray-400 disabled:cursor-default rounded px-2 py-1 text-xs"
|
||||||
|
to={`/block/${blockNum}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavButton;
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense, useContext } from "react";
|
||||||
import { useImage } from "react-image";
|
import { useImage } from "react-image";
|
||||||
|
import { tokenLogoURL } from "../url";
|
||||||
|
import { RuntimeContext } from "../useRuntime";
|
||||||
|
|
||||||
type TokenLogoProps = {
|
type TokenLogoProps = {
|
||||||
address: string;
|
address: string;
|
||||||
|
@ -13,12 +15,14 @@ const TokenLogo: React.FC<TokenLogoProps> = (props) => (
|
||||||
);
|
);
|
||||||
|
|
||||||
const InternalTokenLogo: React.FC<TokenLogoProps> = ({ address, name }) => {
|
const InternalTokenLogo: React.FC<TokenLogoProps> = ({ address, name }) => {
|
||||||
const { src } = useImage({
|
const { config } = useContext(RuntimeContext);
|
||||||
srcList: [
|
|
||||||
`http://localhost:3002/${address}/logo.png`,
|
const srcList: string[] = [];
|
||||||
"/eth-diamond-black.png",
|
if (config) {
|
||||||
],
|
srcList.push(tokenLogoURL(config.assetsURLPrefix ?? "", address));
|
||||||
});
|
}
|
||||||
|
srcList.push("/eth-diamond-black.png");
|
||||||
|
const { src } = useImage({ srcList });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center w-5 h-5">
|
<div className="flex items-center justify-center w-5 h-5">
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faLongArrowAltRight } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faCoins,
|
||||||
|
faLongArrowAltRight,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
export enum Direction {
|
export enum Direction {
|
||||||
IN,
|
IN,
|
||||||
|
@ -9,12 +12,18 @@ export enum Direction {
|
||||||
INTERNAL,
|
INTERNAL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Flags {
|
||||||
|
MINER,
|
||||||
|
}
|
||||||
|
|
||||||
type TransactionDirectionProps = {
|
type TransactionDirectionProps = {
|
||||||
direction?: Direction;
|
direction?: Direction;
|
||||||
|
flags?: Flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TransactionDirection: React.FC<TransactionDirectionProps> = ({
|
const TransactionDirection: React.FC<TransactionDirectionProps> = ({
|
||||||
direction,
|
direction,
|
||||||
|
flags,
|
||||||
}) => {
|
}) => {
|
||||||
let bgColor = "bg-green-50";
|
let bgColor = "bg-green-50";
|
||||||
let fgColor = "text-green-500";
|
let fgColor = "text-green-500";
|
||||||
|
@ -32,6 +41,12 @@ const TransactionDirection: React.FC<TransactionDirectionProps> = ({
|
||||||
msg = "SELF";
|
msg = "SELF";
|
||||||
} else if (direction === Direction.INTERNAL) {
|
} else if (direction === Direction.INTERNAL) {
|
||||||
msg = "INT";
|
msg = "INT";
|
||||||
|
bgColor = "bg-green-100"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags === Flags.MINER) {
|
||||||
|
bgColor = "bg-yellow-50";
|
||||||
|
fgColor = "text-yellow-400";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -42,10 +57,14 @@ const TransactionDirection: React.FC<TransactionDirectionProps> = ({
|
||||||
: "w-5 h-5 rounded-full flex justify-center items-center"
|
: "w-5 h-5 rounded-full flex justify-center items-center"
|
||||||
} text-xs font-bold`}
|
} text-xs font-bold`}
|
||||||
>
|
>
|
||||||
{msg ?? (
|
{flags === Flags.MINER ? (
|
||||||
<span>
|
<FontAwesomeIcon icon={faCoins} size="1x" />
|
||||||
<FontAwesomeIcon icon={faLongArrowAltRight} />
|
) : (
|
||||||
</span>
|
msg ?? (
|
||||||
|
<span>
|
||||||
|
<FontAwesomeIcon icon={faLongArrowAltRight} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,11 +5,13 @@ import { formatValue } from "./formatter";
|
||||||
type TransactionValueProps = {
|
type TransactionValueProps = {
|
||||||
value: BigNumber;
|
value: BigNumber;
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
|
hideUnit?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TransactionValue: React.FC<TransactionValueProps> = ({
|
const TransactionValue: React.FC<TransactionValueProps> = ({
|
||||||
value,
|
value,
|
||||||
decimals = 18,
|
decimals = 18,
|
||||||
|
hideUnit,
|
||||||
}) => {
|
}) => {
|
||||||
const formattedValue = formatValue(value, decimals);
|
const formattedValue = formatValue(value, decimals);
|
||||||
|
|
||||||
|
@ -18,7 +20,8 @@ const TransactionValue: React.FC<TransactionValueProps> = ({
|
||||||
className={`text-sm ${value.isZero() ? "text-gray-400" : ""}`}
|
className={`text-sm ${value.isZero() ? "text-gray-400" : ""}`}
|
||||||
title={`${formattedValue} Ether`}
|
title={`${formattedValue} Ether`}
|
||||||
>
|
>
|
||||||
<span className={`font-balance`}>{formattedValue}</span> Ether
|
<span className={`font-balance`}>{formattedValue}</span>
|
||||||
|
{!hideUnit && " Ether"}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 72.52 80.95"><defs><style>.cls-3{fill:#a0a8d4}</style><linearGradient id="linear-gradient" x1="41.95" y1="2.57" x2="12.57" y2="34.42" gradientUnits="userSpaceOnUse"><stop offset=".58" stop-color="#a0a8d4"/><stop offset=".73" stop-color="#8791c7"/><stop offset=".91" stop-color="#6470b4"/></linearGradient><linearGradient id="linear-gradient-2" x1="42.57" y1="81.66" x2="71.96" y2="49.81" xlink:href="#linear-gradient"/><linearGradient id="linear-gradient-3" x1="42.26" y1="1.24" x2="42.26" y2="82.84" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#513eff"/><stop offset=".18" stop-color="#5157ff"/><stop offset=".57" stop-color="#5298ff"/><stop offset="1" stop-color="#52e5ff"/></linearGradient></defs><g style="isolation:isolate"><g id="Layer_1" data-name="Layer 1"><path d="M15.28 34.39c.8 1.71 2.78 5.09 2.78 5.09L40.95 1.64l-22.34 15.6a9.75 9.75 0 0 0-3.18 3.5 16.19 16.19 0 0 0-.15 13.65z" transform="translate(-6 -1.64)" fill="url(#linear-gradient)"/><path class="cls-3" d="M6.21 46.85a25.47 25.47 0 0 0 10 18.51l24.71 17.23s-15.46-22.28-28.5-44.45a22.39 22.39 0 0 1-2.62-7.56 12.1 12.1 0 0 1 0-3.63c-.34.63-1 1.92-1 1.92a29.35 29.35 0 0 0-2.67 8.55 52.28 52.28 0 0 0 .08 9.43z" transform="translate(-6 -1.64)"/><path d="M69.25 49.84c-.8-1.71-2.78-5.09-2.78-5.09L43.58 82.59 65.92 67a9.75 9.75 0 0 0 3.18-3.5 16.19 16.19 0 0 0 .15-13.66z" transform="translate(-6 -1.64)" fill="url(#linear-gradient-2)"/><path class="cls-3" d="M78.32 37.38a25.47 25.47 0 0 0-10-18.51L43.61 1.64s15.45 22.28 28.5 44.45a22.39 22.39 0 0 1 2.61 7.56 12.1 12.1 0 0 1 0 3.63c.34-.63 1-1.92 1-1.92a29.35 29.35 0 0 0 2.67-8.55 52.28 52.28 0 0 0-.07-9.43z" transform="translate(-6 -1.64)"/><path d="M15.43 20.74a9.75 9.75 0 0 1 3.18-3.5l22.34-15.6-22.89 37.85s-2-3.38-2.78-5.09a16.19 16.19 0 0 1 .15-13.66zM6.21 46.85a25.47 25.47 0 0 0 10 18.51l24.71 17.23s-15.46-22.28-28.5-44.45a22.39 22.39 0 0 1-2.62-7.56 12.1 12.1 0 0 1 0-3.63c-.34.63-1 1.92-1 1.92a29.35 29.35 0 0 0-2.67 8.55 52.28 52.28 0 0 0 .08 9.43zm63 3c-.8-1.71-2.78-5.09-2.78-5.09L43.58 82.59 65.92 67a9.75 9.75 0 0 0 3.18-3.5 16.19 16.19 0 0 0 .15-13.66zm9.07-12.46a25.47 25.47 0 0 0-10-18.51L43.61 1.64s15.45 22.28 28.5 44.45a22.39 22.39 0 0 1 2.61 7.56 12.1 12.1 0 0 1 0 3.63c.34-.63 1-1.92 1-1.92a29.35 29.35 0 0 0 2.67-8.55 52.28 52.28 0 0 0-.07-9.43z" transform="translate(-6 -1.64)" style="mix-blend-mode:color" fill="url(#linear-gradient-3)"/></g></g></svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -1,6 +0,0 @@
|
||||||
import { ethers } from "ethers";
|
|
||||||
|
|
||||||
export const provider = new ethers.providers.JsonRpcProvider(
|
|
||||||
"http://127.0.0.1:8545",
|
|
||||||
"mainnet"
|
|
||||||
);
|
|
|
@ -4,25 +4,27 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
import MethodName from "../components/MethodName";
|
import MethodName from "../components/MethodName";
|
||||||
import BlockLink from "../components/BlockLink";
|
import BlockLink from "../components/BlockLink";
|
||||||
import TransactionLink from "../components/TransactionLink";
|
import TransactionLink from "../components/TransactionLink";
|
||||||
import Address from "../components/Address";
|
import AddressOrENSName from "../components/AddressOrENSName";
|
||||||
import AddressLink from "../components/AddressLink";
|
|
||||||
import TimestampAge from "../components/TimestampAge";
|
import TimestampAge from "../components/TimestampAge";
|
||||||
import TransactionDirection, {
|
import TransactionDirection, {
|
||||||
Direction,
|
Direction,
|
||||||
|
Flags,
|
||||||
} from "../components/TransactionDirection";
|
} from "../components/TransactionDirection";
|
||||||
import TransactionValue from "../components/TransactionValue";
|
import TransactionValue from "../components/TransactionValue";
|
||||||
import { 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";
|
||||||
|
|
||||||
type TransactionItemProps = {
|
type TransactionItemProps = {
|
||||||
tx: ProcessedTransaction;
|
tx: ProcessedTransaction;
|
||||||
|
ensCache?: ENSReverseCache;
|
||||||
selectedAddress?: string;
|
selectedAddress?: string;
|
||||||
feeDisplay: FeeDisplay;
|
feeDisplay: FeeDisplay;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TransactionItem: React.FC<TransactionItemProps> = ({
|
const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
tx,
|
tx,
|
||||||
|
ensCache,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
feeDisplay,
|
feeDisplay,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -39,8 +41,16 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ensFrom = ensCache && tx.from && ensCache[tx.from];
|
||||||
|
const ensTo = ensCache && tx.to && ensCache[tx.to];
|
||||||
|
const flash = tx.gasPrice.isZero() && tx.internalMinerInteraction;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-12 gap-x-1 items-baseline text-sm border-t border-gray-200 hover:bg-gray-100 px-2 py-3">
|
<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"
|
||||||
|
} 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">
|
||||||
{tx.status === 0 && (
|
{tx.status === 0 && (
|
||||||
<span className="text-red-600" title="Transaction reverted">
|
<span className="text-red-600" title="Transaction reverted">
|
||||||
|
@ -58,24 +68,31 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
<TimestampAge timestamp={tx.timestamp} />
|
<TimestampAge timestamp={tx.timestamp} />
|
||||||
<span className="col-span-2 flex justify-between items-baseline space-x-2 pr-2">
|
<span className="col-span-2 flex justify-between items-baseline space-x-2 pr-2">
|
||||||
<span className="truncate" title={tx.from}>
|
<span className="truncate" title={tx.from}>
|
||||||
{tx.from &&
|
{tx.from && (
|
||||||
(tx.from === selectedAddress ? (
|
<AddressOrENSName
|
||||||
<Address>{tx.from}</Address>
|
address={tx.from}
|
||||||
) : (
|
ensName={ensFrom}
|
||||||
<AddressLink address={tx.from} />
|
selectedAddress={selectedAddress}
|
||||||
))}
|
minerAddress={tx.miner}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<TransactionDirection direction={direction} />
|
<TransactionDirection
|
||||||
|
direction={direction}
|
||||||
|
flags={tx.internalMinerInteraction ? Flags.MINER : undefined}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="col-span-2 truncate" title={tx.to}>
|
<span className="col-span-2 truncate" title={tx.to}>
|
||||||
{tx.to &&
|
{tx.to && (
|
||||||
(tx.to === selectedAddress ? (
|
<AddressOrENSName
|
||||||
<Address>{tx.to}</Address>
|
address={tx.to}
|
||||||
) : (
|
ensName={ensTo}
|
||||||
<AddressLink address={tx.to} />
|
selectedAddress={selectedAddress}
|
||||||
))}
|
minerAddress={tx.miner}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="col-span-2 truncate">
|
<span className="col-span-2 truncate">
|
||||||
<TransactionValue value={tx.value} />
|
<TransactionValue value={tx.value} />
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import { provider } from "../ethersconfig";
|
|
||||||
import { PAGE_SIZE } from "../params";
|
import { PAGE_SIZE } from "../params";
|
||||||
import { ProcessedTransaction, TransactionChunk } from "../types";
|
import { ProcessedTransaction, TransactionChunk } from "../types";
|
||||||
|
|
||||||
|
@ -27,7 +26,10 @@ export class SearchController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static rawToProcessed = (_rawRes: any) => {
|
private static rawToProcessed = (
|
||||||
|
provider: ethers.providers.JsonRpcProvider,
|
||||||
|
_rawRes: any
|
||||||
|
) => {
|
||||||
const _res: ethers.providers.TransactionResponse[] = _rawRes.txs.map(
|
const _res: ethers.providers.TransactionResponse[] = _rawRes.txs.map(
|
||||||
(t: any) => provider.formatter.transactionResponse(t)
|
(t: any) => provider.formatter.transactionResponse(t)
|
||||||
);
|
);
|
||||||
|
@ -56,6 +58,7 @@ export class SearchController {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static async readBackPage(
|
private static async readBackPage(
|
||||||
|
provider: ethers.providers.JsonRpcProvider,
|
||||||
address: string,
|
address: string,
|
||||||
baseBlock: number
|
baseBlock: number
|
||||||
): Promise<TransactionChunk> {
|
): Promise<TransactionChunk> {
|
||||||
|
@ -64,10 +67,11 @@ export class SearchController {
|
||||||
baseBlock,
|
baseBlock,
|
||||||
PAGE_SIZE,
|
PAGE_SIZE,
|
||||||
]);
|
]);
|
||||||
return this.rawToProcessed(_rawRes);
|
return this.rawToProcessed(provider, _rawRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async readForwardPage(
|
private static async readForwardPage(
|
||||||
|
provider: ethers.providers.JsonRpcProvider,
|
||||||
address: string,
|
address: string,
|
||||||
baseBlock: number
|
baseBlock: number
|
||||||
): Promise<TransactionChunk> {
|
): Promise<TransactionChunk> {
|
||||||
|
@ -76,11 +80,14 @@ export class SearchController {
|
||||||
baseBlock,
|
baseBlock,
|
||||||
PAGE_SIZE,
|
PAGE_SIZE,
|
||||||
]);
|
]);
|
||||||
return this.rawToProcessed(_rawRes);
|
return this.rawToProcessed(provider, _rawRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async firstPage(address: string): Promise<SearchController> {
|
static async firstPage(
|
||||||
const newTxs = await SearchController.readBackPage(address, 0);
|
provider: ethers.providers.JsonRpcProvider,
|
||||||
|
address: string
|
||||||
|
): Promise<SearchController> {
|
||||||
|
const newTxs = await SearchController.readBackPage(provider, address, 0);
|
||||||
return new SearchController(
|
return new SearchController(
|
||||||
address,
|
address,
|
||||||
newTxs.txs,
|
newTxs.txs,
|
||||||
|
@ -91,14 +98,19 @@ export class SearchController {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async middlePage(
|
static async middlePage(
|
||||||
|
provider: ethers.providers.JsonRpcProvider,
|
||||||
address: string,
|
address: string,
|
||||||
hash: string,
|
hash: string,
|
||||||
next: boolean
|
next: boolean
|
||||||
): Promise<SearchController> {
|
): Promise<SearchController> {
|
||||||
const tx = await provider.getTransaction(hash);
|
const tx = await provider.getTransaction(hash);
|
||||||
const newTxs = next
|
const newTxs = next
|
||||||
? await SearchController.readBackPage(address, tx.blockNumber!)
|
? await SearchController.readBackPage(provider, address, tx.blockNumber!)
|
||||||
: await SearchController.readForwardPage(address, tx.blockNumber!);
|
: await SearchController.readForwardPage(
|
||||||
|
provider,
|
||||||
|
address,
|
||||||
|
tx.blockNumber!
|
||||||
|
);
|
||||||
return new SearchController(
|
return new SearchController(
|
||||||
address,
|
address,
|
||||||
newTxs.txs,
|
newTxs.txs,
|
||||||
|
@ -108,8 +120,11 @@ export class SearchController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async lastPage(address: string): Promise<SearchController> {
|
static async lastPage(
|
||||||
const newTxs = await SearchController.readForwardPage(address, 0);
|
provider: ethers.providers.JsonRpcProvider,
|
||||||
|
address: string
|
||||||
|
): Promise<SearchController> {
|
||||||
|
const newTxs = await SearchController.readForwardPage(provider, address, 0);
|
||||||
return new SearchController(
|
return new SearchController(
|
||||||
address,
|
address,
|
||||||
newTxs.txs,
|
newTxs.txs,
|
||||||
|
@ -123,7 +138,10 @@ export class SearchController {
|
||||||
return this.txs.slice(this.pageStart, this.pageEnd);
|
return this.txs.slice(this.pageStart, this.pageEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
async prevPage(hash: string): Promise<SearchController> {
|
async prevPage(
|
||||||
|
provider: ethers.providers.JsonRpcProvider,
|
||||||
|
hash: string
|
||||||
|
): Promise<SearchController> {
|
||||||
// Already on this page
|
// Already on this page
|
||||||
if (this.txs[this.pageEnd - 1].hash === hash) {
|
if (this.txs[this.pageEnd - 1].hash === hash) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -133,6 +151,7 @@ export class SearchController {
|
||||||
const overflowPage = this.txs.slice(0, this.pageStart);
|
const overflowPage = this.txs.slice(0, this.pageStart);
|
||||||
const baseBlock = this.txs[0].blockNumber;
|
const baseBlock = this.txs[0].blockNumber;
|
||||||
const prevPage = await SearchController.readForwardPage(
|
const prevPage = await SearchController.readForwardPage(
|
||||||
|
provider,
|
||||||
this.address,
|
this.address,
|
||||||
baseBlock
|
baseBlock
|
||||||
);
|
);
|
||||||
|
@ -148,7 +167,10 @@ export class SearchController {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async nextPage(hash: string): Promise<SearchController> {
|
async nextPage(
|
||||||
|
provider: ethers.providers.JsonRpcProvider,
|
||||||
|
hash: string
|
||||||
|
): Promise<SearchController> {
|
||||||
// Already on this page
|
// Already on this page
|
||||||
if (this.txs[this.pageStart].hash === hash) {
|
if (this.txs[this.pageStart].hash === hash) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -158,6 +180,7 @@ export class SearchController {
|
||||||
const overflowPage = this.txs.slice(this.pageEnd);
|
const overflowPage = this.txs.slice(this.pageEnd);
|
||||||
const baseBlock = this.txs[this.txs.length - 1].blockNumber;
|
const baseBlock = this.txs[this.txs.length - 1].blockNumber;
|
||||||
const nextPage = await SearchController.readBackPage(
|
const nextPage = await SearchController.readBackPage(
|
||||||
|
provider,
|
||||||
this.address,
|
this.address,
|
||||||
baseBlock
|
baseBlock
|
||||||
);
|
);
|
||||||
|
|
59
src/types.ts
59
src/types.ts
|
@ -1,12 +1,14 @@
|
||||||
import { BigNumber } from "ethers";
|
import { ethers, BigNumber } from "ethers";
|
||||||
|
|
||||||
export type ProcessedTransaction = {
|
export type ProcessedTransaction = {
|
||||||
blockNumber: number;
|
blockNumber: number;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
miner?: string;
|
||||||
idx: number;
|
idx: number;
|
||||||
hash: string;
|
hash: string;
|
||||||
from?: string;
|
from?: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
|
internalMinerInteraction?: boolean;
|
||||||
value: BigNumber;
|
value: BigNumber;
|
||||||
fee: BigNumber;
|
fee: BigNumber;
|
||||||
gasPrice: BigNumber;
|
gasPrice: BigNumber;
|
||||||
|
@ -19,3 +21,58 @@ export type TransactionChunk = {
|
||||||
firstPage: boolean;
|
firstPage: boolean;
|
||||||
lastPage: boolean;
|
lastPage: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ENSReverseCache = {
|
||||||
|
[address: string]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TransactionData = {
|
||||||
|
transactionHash: string;
|
||||||
|
status: boolean;
|
||||||
|
blockNumber: number;
|
||||||
|
transactionIndex: number;
|
||||||
|
confirmations: number;
|
||||||
|
timestamp: number;
|
||||||
|
miner?: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
value: BigNumber;
|
||||||
|
tokenTransfers: TokenTransfer[];
|
||||||
|
tokenMetas: TokenMetas;
|
||||||
|
fee: BigNumber;
|
||||||
|
gasPrice: BigNumber;
|
||||||
|
gasLimit: BigNumber;
|
||||||
|
gasUsed: BigNumber;
|
||||||
|
gasUsedPerc: number;
|
||||||
|
nonce: number;
|
||||||
|
data: string;
|
||||||
|
logs: ethers.providers.Log[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type From = {
|
||||||
|
current: string;
|
||||||
|
depth: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Transfer = {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
value: BigNumber;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TokenTransfer = {
|
||||||
|
token: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
value: BigNumber;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TokenMeta = {
|
||||||
|
name: string;
|
||||||
|
symbol: string;
|
||||||
|
decimals: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TokenMetas = {
|
||||||
|
[tokenAddress: string]: TokenMeta;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
export const fourBytesURL = (
|
||||||
|
assetsURLPrefix: string,
|
||||||
|
fourBytes: string
|
||||||
|
): string => `${assetsURLPrefix}/signatures/${fourBytes}`;
|
||||||
|
|
||||||
|
export const tokenLogoURL = (
|
||||||
|
assetsURLPrefix: string,
|
||||||
|
address: string
|
||||||
|
): string => `${assetsURLPrefix}/assets/${address}/logo.png`;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export type OtterscanConfig = {
|
||||||
|
erigonURL?: string;
|
||||||
|
assetsURLPrefix?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useConfig = (): [boolean?, OtterscanConfig?] => {
|
||||||
|
const [configOK, setConfigOK] = useState<boolean>();
|
||||||
|
const [config, setConfig] = useState<OtterscanConfig>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const readConfig = async () => {
|
||||||
|
const res = await fetch("/config.json");
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const _config: OtterscanConfig = await res.json();
|
||||||
|
console.info("Loaded app config");
|
||||||
|
console.info(_config);
|
||||||
|
setConfig(_config);
|
||||||
|
setConfigOK(res.ok);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
readConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [configOK, config];
|
||||||
|
};
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
|
||||||
|
export const useLatestBlock = (provider?: ethers.providers.JsonRpcProvider) => {
|
||||||
|
const [latestBlock, setLatestBlock] = useState<ethers.providers.Block>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readLatestBlock = async () => {
|
||||||
|
const blockNum = await provider.getBlockNumber();
|
||||||
|
const _raw = await provider.send("erigon_getHeaderByNumber", [blockNum]);
|
||||||
|
const _block = provider.formatter.block(_raw);
|
||||||
|
setLatestBlock(_block);
|
||||||
|
};
|
||||||
|
readLatestBlock();
|
||||||
|
|
||||||
|
const listener = async (blockNumber: number) => {
|
||||||
|
const _raw = await provider.send("erigon_getHeaderByNumber", [
|
||||||
|
blockNumber,
|
||||||
|
]);
|
||||||
|
const _block = provider.formatter.block(_raw);
|
||||||
|
setLatestBlock(_block);
|
||||||
|
};
|
||||||
|
|
||||||
|
provider.on("block", listener);
|
||||||
|
return () => {
|
||||||
|
provider.removeListener("block", listener);
|
||||||
|
};
|
||||||
|
}, [provider]);
|
||||||
|
|
||||||
|
return latestBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLatestBlockNumber = (
|
||||||
|
provider?: ethers.providers.JsonRpcProvider
|
||||||
|
) => {
|
||||||
|
const [latestBlock, setLatestBlock] = useState<number>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readLatestBlock = async () => {
|
||||||
|
const blockNum = await provider.getBlockNumber();
|
||||||
|
setLatestBlock(blockNum);
|
||||||
|
};
|
||||||
|
readLatestBlock();
|
||||||
|
|
||||||
|
const listener = async (blockNumber: number) => {
|
||||||
|
setLatestBlock(blockNumber);
|
||||||
|
};
|
||||||
|
|
||||||
|
provider.on("block", listener);
|
||||||
|
return () => {
|
||||||
|
provider.removeListener("block", listener);
|
||||||
|
};
|
||||||
|
}, [provider]);
|
||||||
|
|
||||||
|
return latestBlock;
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
|
||||||
|
export const DEFAULT_ERIGON_URL = "http://127.0.0.1:8545";
|
||||||
|
|
||||||
|
export const useProvider = (
|
||||||
|
erigonURL?: string
|
||||||
|
): ethers.providers.JsonRpcProvider | undefined => {
|
||||||
|
if (erigonURL === "") {
|
||||||
|
console.info(`Using default erigon URL: ${DEFAULT_ERIGON_URL}`);
|
||||||
|
erigonURL = DEFAULT_ERIGON_URL;
|
||||||
|
} else {
|
||||||
|
console.log(`Using configured erigon URL: ${erigonURL}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = useMemo(
|
||||||
|
() => new ethers.providers.JsonRpcProvider(erigonURL, "mainnet"),
|
||||||
|
[erigonURL]
|
||||||
|
);
|
||||||
|
if (!erigonURL) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
};
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { ENSReverseCache, ProcessedTransaction } from "./types";
|
||||||
|
|
||||||
|
export const useENSCache = (
|
||||||
|
provider?: ethers.providers.JsonRpcProvider,
|
||||||
|
page?: ProcessedTransaction[]
|
||||||
|
) => {
|
||||||
|
const [reverseCache, setReverseCache] = useState<ENSReverseCache>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!provider || !page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addrSet = new Set<string>();
|
||||||
|
for (const tx of page) {
|
||||||
|
if (tx.from) {
|
||||||
|
addrSet.add(tx.from);
|
||||||
|
}
|
||||||
|
if (tx.to) {
|
||||||
|
addrSet.add(tx.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const addresses = Array.from(addrSet);
|
||||||
|
|
||||||
|
const reverseResolve = async () => {
|
||||||
|
const solvers: Promise<string>[] = [];
|
||||||
|
for (const a of addresses) {
|
||||||
|
solvers.push(provider.lookupAddress(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await Promise.all(solvers);
|
||||||
|
const cache: ENSReverseCache = {};
|
||||||
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
if (results[i] === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cache[addresses[i]] = results[i];
|
||||||
|
}
|
||||||
|
setReverseCache(cache);
|
||||||
|
};
|
||||||
|
reverseResolve();
|
||||||
|
}, [provider, page]);
|
||||||
|
|
||||||
|
return reverseCache;
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { OtterscanConfig, useConfig } from "./useConfig";
|
||||||
|
import { useProvider } from "./useProvider";
|
||||||
|
|
||||||
|
export type OtterscanRuntime = {
|
||||||
|
config?: OtterscanConfig;
|
||||||
|
provider?: ethers.providers.JsonRpcProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRuntime = (): OtterscanRuntime => {
|
||||||
|
const [configOK, config] = useConfig();
|
||||||
|
const provider = useProvider(configOK ? config?.erigonURL : undefined);
|
||||||
|
|
||||||
|
const runtime = useMemo(
|
||||||
|
(): OtterscanRuntime => ({ config, provider }),
|
||||||
|
[config, provider]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!configOK) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return runtime;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RuntimeContext = React.createContext<OtterscanRuntime>(null!);
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8cc7396ae100eefb9e179240e2503af9df2c7051
|
Subproject commit 011ccb88adc70c27b0d4fc0a10564199efd0a81f
|
|
@ -1,11 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
@ -20,7 +16,5 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"]
|
||||||
"src"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue