From 5d5eaddd6e98e7b3c1d8555e59b3384a8a878d72 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Thu, 8 Jul 2021 18:53:08 -0300 Subject: [PATCH 1/7] Add official docker hub link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e03faf..f9398a7 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ Now you should have an Erigon node with Otterscan jsonrpc APIs enabled, running ### Run Otterscan docker image from Docker Hub -TODO: publish Otterscan official images as soon as it is validated. +The Otterscan official repo on Docker Hub is [here](https://hub.docker.com/orgs/otterscan/repositories). ``` docker run --rm -p 5000:80 --name otterscan -d otterscan/otterscan: From 20b6b98881953b09337110c9c9d56fea4551eecd Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Thu, 8 Jul 2021 21:00:15 -0300 Subject: [PATCH 2/7] nginx config fixes for production image --- nginx.conf | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/nginx.conf b/nginx.conf index 6060f2b..43ebd4a 100644 --- a/nginx.conf +++ b/nginx.conf @@ -4,10 +4,9 @@ server { #access_log /var/log/nginx/host.access.log main; - location / { + location /signatures { root /usr/share/nginx/html; - index index.html index.htm; - + # Base on: https://michielkalkman.com/snippets/nginx-cors-open-configuration/ if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; @@ -38,6 +37,45 @@ server { } } + location /assets { + root /usr/share/nginx/html; + + # 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' '*'; + add_header 'Access-Control-Allow-Credentials' 'true'; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + } + } + + 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 From 83d64fc5221d50dbcc0ba8837787b985d272e011 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 9 Jul 2021 02:07:20 -0300 Subject: [PATCH 3/7] Extract runtime context object --- src/AddressTransactions.tsx | 4 ++-- src/App.tsx | 8 ++++---- src/Block.tsx | 4 ++-- src/BlockTransactions.tsx | 4 ++-- src/Home.tsx | 4 ++-- src/Transaction.tsx | 4 ++-- src/{useErigon.ts => useConfig.ts} | 2 +- src/useProvider.ts | 14 +++++--------- src/useRuntime.ts | 22 ++++++++++++++++++++++ 9 files changed, 42 insertions(+), 24 deletions(-) rename src/{useErigon.ts => useConfig.ts} (89%) create mode 100644 src/useRuntime.ts diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index f080b96..e671087 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -12,7 +12,7 @@ import ResultHeader from "./search/ResultHeader"; import PendingResults from "./search/PendingResults"; import TransactionItem from "./search/TransactionItem"; import { SearchController } from "./search/search"; -import { ProviderContext } from "./useProvider"; +import { RuntimeContext } from "./useRuntime"; import { useENSCache } from "./useReverseCache"; import { useFeeToggler } from "./search/useFeeToggler"; @@ -26,7 +26,7 @@ type PageParams = { }; const AddressTransactions: React.FC = () => { - const provider = useContext(ProviderContext); + const { provider } = useContext(RuntimeContext); const params = useParams(); const location = useLocation(); const history = useHistory(); diff --git a/src/App.tsx b/src/App.tsx index 122081c..2bf5e2a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,7 @@ import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Home from "./Home"; import Search from "./Search"; import Title from "./Title"; -import { useProvider, ProviderContext } from "./useProvider"; +import { RuntimeContext, useRuntime } from "./useRuntime"; const Block = React.lazy(() => import("./Block")); const BlockTransactions = React.lazy(() => import("./BlockTransactions")); @@ -11,11 +11,11 @@ const AddressTransactions = React.lazy(() => import("./AddressTransactions")); const Transaction = React.lazy(() => import("./Transaction")); const App = () => { - const provider = useProvider(); + const runtime = useRuntime(); return ( LOADING}> - + @@ -41,7 +41,7 @@ const App = () => { - + ); }; diff --git a/src/Block.tsx b/src/Block.tsx index 6f9ab22..963f2f9 100644 --- a/src/Block.tsx +++ b/src/Block.tsx @@ -16,7 +16,7 @@ import BlockLink from "./components/BlockLink"; import AddressOrENSName from "./components/AddressOrENSName"; import TransactionValue from "./components/TransactionValue"; import HexValue from "./components/HexValue"; -import { ProviderContext } from "./useProvider"; +import { RuntimeContext } from "./useRuntime"; import { useLatestBlockNumber } from "./useLatestBlock"; type BlockParams = { @@ -34,7 +34,7 @@ interface ExtendedBlock extends ethers.providers.Block { } const Block: React.FC = () => { - const provider = useContext(ProviderContext); + const { provider } = useContext(RuntimeContext); const params = useParams(); const [block, setBlock] = useState(); diff --git a/src/BlockTransactions.tsx b/src/BlockTransactions.tsx index 5e6d4ad..8f8f474 100644 --- a/src/BlockTransactions.tsx +++ b/src/BlockTransactions.tsx @@ -13,7 +13,7 @@ import BlockLink from "./components/BlockLink"; import { ProcessedTransaction } from "./types"; import { PAGE_SIZE } from "./params"; import { useFeeToggler } from "./search/useFeeToggler"; -import { ProviderContext } from "./useProvider"; +import { RuntimeContext } from "./useRuntime"; import { useENSCache } from "./useReverseCache"; type BlockParams = { @@ -25,7 +25,7 @@ type PageParams = { }; const BlockTransactions: React.FC = () => { - const provider = useContext(ProviderContext); + const { provider } = useContext(RuntimeContext); const params = useParams(); const location = useLocation(); const qs = queryString.parse(location.search); diff --git a/src/Home.tsx b/src/Home.tsx index 9cc40fc..8626dce 100644 --- a/src/Home.tsx +++ b/src/Home.tsx @@ -3,11 +3,11 @@ import { NavLink, useHistory } from "react-router-dom"; import { ethers } from "ethers"; import Logo from "./Logo"; import Timestamp from "./components/Timestamp"; -import { ProviderContext } from "./useProvider"; +import { RuntimeContext } from "./useRuntime"; import { useLatestBlock } from "./useLatestBlock"; const Home: React.FC = () => { - const provider = useContext(ProviderContext); + const { provider } = useContext(RuntimeContext); const [search, setSearch] = useState(); const [canSubmit, setCanSubmit] = useState(false); const history = useHistory(); diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 724fb31..4f8c312 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -27,7 +27,7 @@ import FormattedBalance from "./components/FormattedBalance"; import TokenTransferItem from "./TokenTransferItem"; import erc20 from "./erc20.json"; import { TokenMetas, TokenTransfer, TransactionData, Transfer } from "./types"; -import { ProviderContext } from "./useProvider"; +import { RuntimeContext } from "./useRuntime"; const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; @@ -37,7 +37,7 @@ type TransactionParams = { }; const Transaction: React.FC = () => { - const provider = useContext(ProviderContext); + const { provider } = useContext(RuntimeContext); const params = useParams(); const { txhash } = params; diff --git a/src/useErigon.ts b/src/useConfig.ts similarity index 89% rename from src/useErigon.ts rename to src/useConfig.ts index bd64364..df8deb2 100644 --- a/src/useErigon.ts +++ b/src/useConfig.ts @@ -4,7 +4,7 @@ export type OtterscanConfig = { erigonURL: string; }; -export const useErigon = (): [boolean?, OtterscanConfig?] => { +export const useConfig = (): [boolean?, OtterscanConfig?] => { const [configOK, setConfigOK] = useState(); const [config, setConfig] = useState(); diff --git a/src/useProvider.ts b/src/useProvider.ts index 895c16d..08208cf 100644 --- a/src/useProvider.ts +++ b/src/useProvider.ts @@ -1,12 +1,12 @@ -import React from "react"; import { ethers } from "ethers"; -import { useErigon } from "./useErigon"; +import { OtterscanConfig } from "./useConfig"; export const DEFAULT_ERIGON_URL = "http://127.0.0.1:8545"; -export const useProvider = (): ethers.providers.JsonRpcProvider | undefined => { - const [configOK, config] = useErigon(); - if (!configOK) { +export const useProvider = ( + config?: OtterscanConfig +): ethers.providers.JsonRpcProvider | undefined => { + if (!config) { return undefined; } @@ -20,7 +20,3 @@ export const useProvider = (): ethers.providers.JsonRpcProvider | undefined => { return new ethers.providers.JsonRpcProvider(erigonURL, "mainnet"); }; - -export const ProviderContext = React.createContext< - ethers.providers.JsonRpcProvider | undefined ->(undefined); diff --git a/src/useRuntime.ts b/src/useRuntime.ts new file mode 100644 index 0000000..2ebb766 --- /dev/null +++ b/src/useRuntime.ts @@ -0,0 +1,22 @@ +import React 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(config); + + if (!configOK) { + return {}; + } + + return { config, provider }; +}; + +export const RuntimeContext = React.createContext(null!); From ea7b7a9f75d877d2ecd2e127e0dae9fcb7a3a547 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 9 Jul 2021 13:54:59 -0300 Subject: [PATCH 4/7] Externalize url prefixes --- public/config.json | 3 ++- src/components/MethodName.tsx | 14 ++++++++++++-- src/components/TokenLogo.tsx | 16 ++++++++++------ src/url.ts | 9 +++++++++ src/useConfig.ts | 3 +++ src/useProvider.ts | 18 ++++++++++-------- src/useRuntime.ts | 12 ++++++++---- 7 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 src/url.ts diff --git a/public/config.json b/public/config.json index d602ced..1db5aa7 100644 --- a/public/config.json +++ b/public/config.json @@ -1,3 +1,4 @@ { - "erigonURL": "http://localhost:8545" + "erigonURL": "http://localhost:8545", + "assetsURLPrefix": "http://localhost:3001" } \ No newline at end of file diff --git a/src/components/MethodName.tsx b/src/components/MethodName.tsx index 27ad8ca..974e69e 100644 --- a/src/components/MethodName.tsx +++ b/src/components/MethodName.tsx @@ -1,10 +1,14 @@ import React, { useState, useEffect } from "react"; +import { fourBytesURL } from "../url"; +import { useRuntime } from "../useRuntime"; type MethodNameProps = { data: string; }; const MethodName: React.FC = ({ data }) => { + const runtime = useRuntime(); + const [name, setName] = useState(); useEffect(() => { if (data === "0x") { @@ -16,7 +20,13 @@ const MethodName: React.FC = ({ data }) => { // Try to resolve 4bytes name const fourBytes = _name.slice(2); - const signatureURL = `http://localhost:3001/signatures/${fourBytes}`; + const { config } = runtime; + if (!config) { + setName(_name); + return; + } + + const signatureURL = fourBytesURL(config.assetsURLPrefix, fourBytes); fetch(signatureURL) .then(async (res) => { if (!res.ok) { @@ -37,7 +47,7 @@ const MethodName: React.FC = ({ data }) => { // Use the default 4 bytes as name setName(_name); - }, [data]); + }, [runtime, data]); return (
diff --git a/src/components/TokenLogo.tsx b/src/components/TokenLogo.tsx index 738de1c..fab9e2c 100644 --- a/src/components/TokenLogo.tsx +++ b/src/components/TokenLogo.tsx @@ -1,5 +1,7 @@ import React, { Suspense } from "react"; import { useImage } from "react-image"; +import { tokenLogoURL } from "../url"; +import { useRuntime } from "../useRuntime"; type TokenLogoProps = { address: string; @@ -13,12 +15,14 @@ const TokenLogo: React.FC = (props) => ( ); const InternalTokenLogo: React.FC = ({ address, name }) => { - const { src } = useImage({ - srcList: [ - `http://localhost:3001/assets/${address}/logo.png`, - "/eth-diamond-black.png", - ], - }); + const { config } = useRuntime(); + + const srcList: string[] = []; + if (config) { + srcList.push(tokenLogoURL(config.assetsURLPrefix, address)); + } + srcList.push("/eth-diamond-black.png"); + const { src } = useImage({ srcList }); return (
diff --git a/src/url.ts b/src/url.ts new file mode 100644 index 0000000..0586dbe --- /dev/null +++ b/src/url.ts @@ -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`; diff --git a/src/useConfig.ts b/src/useConfig.ts index df8deb2..4e53fb6 100644 --- a/src/useConfig.ts +++ b/src/useConfig.ts @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; export type OtterscanConfig = { erigonURL: string; + assetsURLPrefix: string; }; export const useConfig = (): [boolean?, OtterscanConfig?] => { @@ -14,6 +15,8 @@ export const useConfig = (): [boolean?, OtterscanConfig?] => { if (res.ok) { const _config: OtterscanConfig = await res.json(); + console.info("Loaded app config"); + console.info(_config); setConfig(_config); setConfigOK(res.ok); } diff --git a/src/useProvider.ts b/src/useProvider.ts index 08208cf..cd4024d 100644 --- a/src/useProvider.ts +++ b/src/useProvider.ts @@ -1,16 +1,11 @@ +import { useMemo } from "react"; import { ethers } from "ethers"; -import { OtterscanConfig } from "./useConfig"; export const DEFAULT_ERIGON_URL = "http://127.0.0.1:8545"; export const useProvider = ( - config?: OtterscanConfig + erigonURL?: string ): ethers.providers.JsonRpcProvider | undefined => { - if (!config) { - return undefined; - } - - let erigonURL = config?.erigonURL; if (erigonURL === "") { console.info(`Using default erigon URL: ${DEFAULT_ERIGON_URL}`); erigonURL = DEFAULT_ERIGON_URL; @@ -18,5 +13,12 @@ export const useProvider = ( console.log(`Using configured erigon URL: ${erigonURL}`); } - return new ethers.providers.JsonRpcProvider(erigonURL, "mainnet"); + const provider = useMemo( + () => new ethers.providers.JsonRpcProvider(erigonURL, "mainnet"), + [erigonURL] + ); + if (!erigonURL) { + return undefined; + } + return provider; }; diff --git a/src/useRuntime.ts b/src/useRuntime.ts index 2ebb766..ed2bcc9 100644 --- a/src/useRuntime.ts +++ b/src/useRuntime.ts @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import { ethers } from "ethers"; import { OtterscanConfig, useConfig } from "./useConfig"; import { useProvider } from "./useProvider"; @@ -10,13 +10,17 @@ export type OtterscanRuntime = { export const useRuntime = (): OtterscanRuntime => { const [configOK, config] = useConfig(); - const provider = useProvider(config); + const provider = useProvider(configOK ? config?.erigonURL : undefined); + + const runtime = useMemo( + (): OtterscanRuntime => ({ config, provider }), + [config, provider] + ); if (!configOK) { return {}; } - - return { config, provider }; + return runtime; }; export const RuntimeContext = React.createContext(null!); From 052b2f61e0196f558dbbba23a4119ef595632b3b Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 9 Jul 2021 14:13:31 -0300 Subject: [PATCH 5/7] Fixes for the production docker image --- src/components/MethodName.tsx | 2 +- src/components/TokenLogo.tsx | 2 +- src/useConfig.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MethodName.tsx b/src/components/MethodName.tsx index 974e69e..3a4d710 100644 --- a/src/components/MethodName.tsx +++ b/src/components/MethodName.tsx @@ -26,7 +26,7 @@ const MethodName: React.FC = ({ data }) => { return; } - const signatureURL = fourBytesURL(config.assetsURLPrefix, fourBytes); + const signatureURL = fourBytesURL(config.assetsURLPrefix ?? "", fourBytes); fetch(signatureURL) .then(async (res) => { if (!res.ok) { diff --git a/src/components/TokenLogo.tsx b/src/components/TokenLogo.tsx index fab9e2c..cc2e756 100644 --- a/src/components/TokenLogo.tsx +++ b/src/components/TokenLogo.tsx @@ -19,7 +19,7 @@ const InternalTokenLogo: React.FC = ({ address, name }) => { const srcList: string[] = []; if (config) { - srcList.push(tokenLogoURL(config.assetsURLPrefix, address)); + srcList.push(tokenLogoURL(config.assetsURLPrefix ?? "", address)); } srcList.push("/eth-diamond-black.png"); const { src } = useImage({ srcList }); diff --git a/src/useConfig.ts b/src/useConfig.ts index 4e53fb6..3bb4858 100644 --- a/src/useConfig.ts +++ b/src/useConfig.ts @@ -1,8 +1,8 @@ import { useState, useEffect } from "react"; export type OtterscanConfig = { - erigonURL: string; - assetsURLPrefix: string; + erigonURL?: string; + assetsURLPrefix?: string; }; export const useConfig = (): [boolean?, OtterscanConfig?] => { From 7b2bdce2105c2eb52c3c9687d712d8e449a52ddd Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 9 Jul 2021 14:22:25 -0300 Subject: [PATCH 6/7] Fix context use --- src/components/MethodName.tsx | 6 +++--- src/components/TokenLogo.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/MethodName.tsx b/src/components/MethodName.tsx index 3a4d710..55ff909 100644 --- a/src/components/MethodName.tsx +++ b/src/components/MethodName.tsx @@ -1,13 +1,13 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { fourBytesURL } from "../url"; -import { useRuntime } from "../useRuntime"; +import { RuntimeContext } from "../useRuntime"; type MethodNameProps = { data: string; }; const MethodName: React.FC = ({ data }) => { - const runtime = useRuntime(); + const runtime = useContext(RuntimeContext); const [name, setName] = useState(); useEffect(() => { diff --git a/src/components/TokenLogo.tsx b/src/components/TokenLogo.tsx index cc2e756..c6f5b8b 100644 --- a/src/components/TokenLogo.tsx +++ b/src/components/TokenLogo.tsx @@ -1,7 +1,7 @@ -import React, { Suspense } from "react"; +import React, { Suspense, useContext } from "react"; import { useImage } from "react-image"; import { tokenLogoURL } from "../url"; -import { useRuntime } from "../useRuntime"; +import { RuntimeContext } from "../useRuntime"; type TokenLogoProps = { address: string; @@ -15,7 +15,7 @@ const TokenLogo: React.FC = (props) => ( ); const InternalTokenLogo: React.FC = ({ address, name }) => { - const { config } = useRuntime(); + const { config } = useContext(RuntimeContext); const srcList: string[] = []; if (config) { From 38b6988d103f7bd64e95db16c0ed9c1226ea0979 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 9 Jul 2021 14:41:48 -0300 Subject: [PATCH 7/7] Caching fixes --- nginx.conf | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/nginx.conf b/nginx.conf index 43ebd4a..029fb69 100644 --- a/nginx.conf +++ b/nginx.conf @@ -6,6 +6,7 @@ server { location /signatures { root /usr/share/nginx/html; + expires 30d; # Base on: https://michielkalkman.com/snippets/nginx-cors-open-configuration/ if ($request_method = 'OPTIONS') { @@ -30,15 +31,16 @@ server { return 204; } if ($request_method = 'GET') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Credentials' 'true'; - add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + 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') { @@ -63,10 +65,10 @@ server { return 204; } if ($request_method = 'GET') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Credentials' 'true'; - add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + 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; } }