Merge branch 'feature/docker-fixes' into develop

This commit is contained in:
Willian Mitsuda 2021-07-09 15:14:08 -03:00
commit c34af4481d
15 changed files with 136 additions and 45 deletions

View File

@ -111,7 +111,7 @@ Now you should have an Erigon node with Otterscan jsonrpc APIs enabled, running
### Run Otterscan docker image from Docker Hub ### 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:<versiontag> docker run --rm -p 5000:80 --name otterscan -d otterscan/otterscan:<versiontag>

View File

@ -4,9 +4,9 @@ server {
#access_log /var/log/nginx/host.access.log main; #access_log /var/log/nginx/host.access.log main;
location / { location /signatures {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html index.htm; expires 30d;
# Base on: https://michielkalkman.com/snippets/nginx-cors-open-configuration/ # Base on: https://michielkalkman.com/snippets/nginx-cors-open-configuration/
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
@ -31,11 +31,51 @@ server {
return 204; return 204;
} }
if ($request_method = 'GET') { 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' '*'; add_header 'Access-Control-Allow-Origin' '*';
#
# Om nom nom cookies
#
add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; 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'; 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; #error_page 404 /404.html;

View File

@ -1,3 +1,4 @@
{ {
"erigonURL": "http://localhost:8545" "erigonURL": "http://localhost:8545",
"assetsURLPrefix": "http://localhost:3001"
} }

View File

@ -12,7 +12,7 @@ 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 { ProviderContext } from "./useProvider"; import { RuntimeContext } from "./useRuntime";
import { useENSCache } from "./useReverseCache"; import { useENSCache } from "./useReverseCache";
import { useFeeToggler } from "./search/useFeeToggler"; import { useFeeToggler } from "./search/useFeeToggler";
@ -26,7 +26,7 @@ type PageParams = {
}; };
const AddressTransactions: React.FC = () => { const AddressTransactions: React.FC = () => {
const provider = useContext(ProviderContext); 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();

View File

@ -3,7 +3,7 @@ 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 { useProvider, ProviderContext } from "./useProvider"; 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"));
@ -11,11 +11,11 @@ const AddressTransactions = React.lazy(() => import("./AddressTransactions"));
const Transaction = React.lazy(() => import("./Transaction")); const Transaction = React.lazy(() => import("./Transaction"));
const App = () => { const App = () => {
const provider = useProvider(); const runtime = useRuntime();
return ( return (
<Suspense fallback={<>LOADING</>}> <Suspense fallback={<>LOADING</>}>
<ProviderContext.Provider value={provider}> <RuntimeContext.Provider value={runtime}>
<Router> <Router>
<Switch> <Switch>
<Route path="/" exact> <Route path="/" exact>
@ -41,7 +41,7 @@ const App = () => {
</Route> </Route>
</Switch> </Switch>
</Router> </Router>
</ProviderContext.Provider> </RuntimeContext.Provider>
</Suspense> </Suspense>
); );
}; };

View File

@ -16,7 +16,7 @@ import BlockLink from "./components/BlockLink";
import AddressOrENSName from "./components/AddressOrENSName"; import AddressOrENSName from "./components/AddressOrENSName";
import TransactionValue from "./components/TransactionValue"; import TransactionValue from "./components/TransactionValue";
import HexValue from "./components/HexValue"; import HexValue from "./components/HexValue";
import { ProviderContext } from "./useProvider"; import { RuntimeContext } from "./useRuntime";
import { useLatestBlockNumber } from "./useLatestBlock"; import { useLatestBlockNumber } from "./useLatestBlock";
type BlockParams = { type BlockParams = {
@ -34,7 +34,7 @@ interface ExtendedBlock extends ethers.providers.Block {
} }
const Block: React.FC = () => { const Block: React.FC = () => {
const provider = useContext(ProviderContext); const { provider } = useContext(RuntimeContext);
const params = useParams<BlockParams>(); const params = useParams<BlockParams>();
const [block, setBlock] = useState<ExtendedBlock>(); const [block, setBlock] = useState<ExtendedBlock>();

View File

@ -13,7 +13,7 @@ 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 { ProviderContext } from "./useProvider"; import { RuntimeContext } from "./useRuntime";
import { useENSCache } from "./useReverseCache"; import { useENSCache } from "./useReverseCache";
type BlockParams = { type BlockParams = {
@ -25,7 +25,7 @@ type PageParams = {
}; };
const BlockTransactions: React.FC = () => { const BlockTransactions: React.FC = () => {
const provider = useContext(ProviderContext); 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);

View File

@ -3,11 +3,11 @@ 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 { ProviderContext } from "./useProvider"; import { RuntimeContext } from "./useRuntime";
import { useLatestBlock } from "./useLatestBlock"; import { useLatestBlock } from "./useLatestBlock";
const Home: React.FC = () => { const Home: React.FC = () => {
const provider = useContext(ProviderContext); 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();

View File

@ -27,7 +27,7 @@ import FormattedBalance from "./components/FormattedBalance";
import TokenTransferItem from "./TokenTransferItem"; import TokenTransferItem from "./TokenTransferItem";
import erc20 from "./erc20.json"; import erc20 from "./erc20.json";
import { TokenMetas, TokenTransfer, TransactionData, Transfer } from "./types"; import { TokenMetas, TokenTransfer, TransactionData, Transfer } from "./types";
import { ProviderContext } from "./useProvider"; import { RuntimeContext } from "./useRuntime";
const TRANSFER_TOPIC = const TRANSFER_TOPIC =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
@ -37,7 +37,7 @@ type TransactionParams = {
}; };
const Transaction: React.FC = () => { const Transaction: React.FC = () => {
const provider = useContext(ProviderContext); const { provider } = useContext(RuntimeContext);
const params = useParams<TransactionParams>(); const params = useParams<TransactionParams>();
const { txhash } = params; const { txhash } = params;

View File

@ -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/signatures/${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">

View File

@ -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:3001/assets/${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">

9
src/url.ts Normal file
View File

@ -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`;

View File

@ -1,10 +1,11 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
export type OtterscanConfig = { export type OtterscanConfig = {
erigonURL: string; erigonURL?: string;
assetsURLPrefix?: string;
}; };
export const useErigon = (): [boolean?, OtterscanConfig?] => { export const useConfig = (): [boolean?, OtterscanConfig?] => {
const [configOK, setConfigOK] = useState<boolean>(); const [configOK, setConfigOK] = useState<boolean>();
const [config, setConfig] = useState<OtterscanConfig>(); const [config, setConfig] = useState<OtterscanConfig>();
@ -14,6 +15,8 @@ export const useErigon = (): [boolean?, OtterscanConfig?] => {
if (res.ok) { if (res.ok) {
const _config: OtterscanConfig = await res.json(); const _config: OtterscanConfig = await res.json();
console.info("Loaded app config");
console.info(_config);
setConfig(_config); setConfig(_config);
setConfigOK(res.ok); setConfigOK(res.ok);
} }

View File

@ -1,16 +1,11 @@
import React from "react"; import { useMemo } from "react";
import { ethers } from "ethers"; import { ethers } from "ethers";
import { useErigon } from "./useErigon";
export const DEFAULT_ERIGON_URL = "http://127.0.0.1:8545"; export const DEFAULT_ERIGON_URL = "http://127.0.0.1:8545";
export const useProvider = (): ethers.providers.JsonRpcProvider | undefined => { export const useProvider = (
const [configOK, config] = useErigon(); erigonURL?: string
if (!configOK) { ): ethers.providers.JsonRpcProvider | undefined => {
return undefined;
}
let erigonURL = config?.erigonURL;
if (erigonURL === "") { if (erigonURL === "") {
console.info(`Using default erigon URL: ${DEFAULT_ERIGON_URL}`); console.info(`Using default erigon URL: ${DEFAULT_ERIGON_URL}`);
erigonURL = DEFAULT_ERIGON_URL; erigonURL = DEFAULT_ERIGON_URL;
@ -18,9 +13,12 @@ export const useProvider = (): ethers.providers.JsonRpcProvider | undefined => {
console.log(`Using configured erigon URL: ${erigonURL}`); 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;
}; };
export const ProviderContext = React.createContext<
ethers.providers.JsonRpcProvider | undefined
>(undefined);

26
src/useRuntime.ts Normal file
View File

@ -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!);