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
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>

View File

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

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 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<BlockParams>();
const location = useLocation<PageParams>();
const history = useHistory();

View File

@ -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 (
<Suspense fallback={<>LOADING</>}>
<ProviderContext.Provider value={provider}>
<RuntimeContext.Provider value={runtime}>
<Router>
<Switch>
<Route path="/" exact>
@ -41,7 +41,7 @@ const App = () => {
</Route>
</Switch>
</Router>
</ProviderContext.Provider>
</RuntimeContext.Provider>
</Suspense>
);
};

View File

@ -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<BlockParams>();
const [block, setBlock] = useState<ExtendedBlock>();

View File

@ -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<BlockParams>();
const location = useLocation<PageParams>();
const qs = queryString.parse(location.search);

View File

@ -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<string>();
const [canSubmit, setCanSubmit] = useState<boolean>(false);
const history = useHistory();

View File

@ -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<TransactionParams>();
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 = {
data: string;
};
const MethodName: React.FC<MethodNameProps> = ({ data }) => {
const runtime = useContext(RuntimeContext);
const [name, setName] = useState<string>();
useEffect(() => {
if (data === "0x") {
@ -16,7 +20,13 @@ const MethodName: React.FC<MethodNameProps> = ({ 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<MethodNameProps> = ({ data }) => {
// Use the default 4 bytes as name
setName(_name);
}, [data]);
}, [runtime, data]);
return (
<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 { tokenLogoURL } from "../url";
import { RuntimeContext } from "../useRuntime";
type TokenLogoProps = {
address: string;
@ -13,12 +15,14 @@ const TokenLogo: React.FC<TokenLogoProps> = (props) => (
);
const InternalTokenLogo: React.FC<TokenLogoProps> = ({ address, name }) => {
const { src } = useImage({
srcList: [
`http://localhost:3001/assets/${address}/logo.png`,
"/eth-diamond-black.png",
],
});
const { config } = useContext(RuntimeContext);
const srcList: string[] = [];
if (config) {
srcList.push(tokenLogoURL(config.assetsURLPrefix ?? "", address));
}
srcList.push("/eth-diamond-black.png");
const { src } = useImage({ srcList });
return (
<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";
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 [config, setConfig] = useState<OtterscanConfig>();
@ -14,6 +15,8 @@ export const useErigon = (): [boolean?, OtterscanConfig?] => {
if (res.ok) {
const _config: OtterscanConfig = await res.json();
console.info("Loaded app config");
console.info(_config);
setConfig(_config);
setConfigOK(res.ok);
}

View File

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