setFilter(evt.target.value)}
38 | InputProps={{
39 | placeholder: "Filter changed files",
40 | startAdornment: (
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | ),
49 | }}
50 | />
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/src/components/HighlightSyntaxSelector.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { setHighlightSyntax } from "../store/options.js";
3 | import { useHighlightSyntax } from "../hooks";
4 |
5 | export default () => {
6 | const dispatch = useDispatch();
7 | const highlightSyntax = useHighlightSyntax();
8 |
9 | const onChange = () => {
10 | if (highlightSyntax) {
11 | dispatch(setHighlightSyntax(false));
12 | } else {
13 | dispatch(setHighlightSyntax(true));
14 | }
15 | };
16 | return (
17 | <>
18 |
22 | >
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/Layout.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { useTheme } from "../hooks";
3 | import Navigation from "./Navigation";
4 | import ChainLoader from "./ChainLoader";
5 |
6 | const Wrapper = styled.div``;
7 |
8 | const Content = styled.div`
9 | display: flex;
10 | justify-content: center;
11 | `;
12 |
13 | export default ({ children }) => {
14 | const theme = useTheme();
15 | return (
16 |
17 |
18 | {/* */}
19 | {children}
20 |
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/components/Logo.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Wrapper = styled.div`
4 | text-align: center;
5 | margin-top: 100px;
6 | color: #555;
7 | scale: 86%;
8 | width: 100%;
9 | display: inline;
10 | div {
11 | display: inline;
12 | font-family: "Courier New", monospace;
13 | color: #3bf0a1;
14 | }
15 | @media (max-width: 990px) {
16 | pre {
17 | font-size: 7px;
18 | }
19 | }
20 |
21 | @media (max-width: 600px) {
22 | pre {
23 | font-size: 4px;
24 | }
25 | }
26 | `;
27 |
28 | export default function Logo() {
29 | return (
30 |
31 |
32 | ________________________/\\\_
_______/
33 | \\\\\\\\\_
34 | ____________________________________________________/
35 | \\\\\\
__________________
36 |
37 |
38 | _______________________/\\\\\_
_____/
39 | \\\/
//////\\\_
40 | ___________________________________________________
41 | \/
///\\\
42 | _______________
43 |
44 |
45 | _____________________/\\\/\\\_
____
46 | \/\\\_
____\/\\\_
47 | ___________/\\\
48 | _____________________________________\/\\\
49 | _________________
50 |
51 |
52 | __/\\\_
___/\\\_
_____/
53 | \\\/\/\\\_
____
54 | \///\\\\\\\\\/
__________/
55 | \\\\\\\\\\\_
____/
56 | \\\\\_
_______/\\\\\_
57 | ______\/\\\
_____/
58 | \\\\\\\\\\
_
59 |
60 |
61 | _\/
//\\\/\\\/
_____/
62 | \\\/
__\/\\\_
_____/
63 | \\\/
//////\\\_
_______
64 | \/
///\\\/
///____/
65 | \\\/
//\\\_
___/
66 | \\\/
//\\\_
____
67 | \/\\\
____\/\\\/
/////__
68 |
69 |
70 | ___\/
//\\\/
_____/
71 | \\\\\\\\\\\\\\\\
__/
72 | \\\_
_____\/
/
73 | \\\_
_________\/\\\_
74 | ______/\\\_
_\/
/
75 | \\\_
_/\\\_
_
76 | \/
/\\\_
___
77 | \/\\\
____\/\\\\\\\\\\
_
78 |
79 |
80 | ____/\\\/\\\_
__\/
81 | //////////\\\/
/__\/
/
82 | \\\_
_____/\\\_
83 | __________\/\\\_
/\\
__
84 | \/
/\\\_
_/
85 | \\\_
_\/
/
86 | \\\_
_/\\\_
____
87 | \/\\\
____\/
///////
88 | \\\
_
89 |
90 |
91 | __/\\\/\/
//\\\_
92 | __________\/\\\_
____\/
93 | //\\\\\\\\\/
____/\\\_
94 | ___\/
/\\\\\_
___
95 | \/
//\\\\\/
____
96 | \/
//\\\\\/_
___
97 | /\\\\\\\\\
__/
98 | \\\\\\\\\\
_
99 |
100 |
101 | _\/
//____\/
102 | //____________\/
//________
103 | \/
////////_____\/
104 | //______\/
////_______
105 | \/
////________\/
106 | ////_____\/
////////__
107 | \/
/////////__
108 |
109 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/src/components/Navigation.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import ThemeSelector from "./ThemeSelector";
3 | import HighlightSyntaxSelector from "./HighlightSyntaxSelector";
4 | import ActiveLink from "./ActiveLink";
5 | import AutoExpandSelector from "./AutoExpandSelector";
6 |
7 | const Wrapper = styled.div`
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | position: relative;
12 | `;
13 |
14 | const NavItems = styled.div`
15 | display: flex;
16 | grid-gap: 10px;
17 | `;
18 |
19 | const NavItem = styled.div``;
20 |
21 | const routes = ["swaps", "stats"];
22 | const rootRoute = "swaps";
23 |
24 | const Right = styled.div`
25 | display: flex;
26 | grid-gap: 5px;
27 | position: absolute;
28 | right: 5px;
29 | `;
30 |
31 | const Options = styled.div`
32 | display: flex;
33 | grid-gap: 10px;
34 | flex-direction: row;
35 | `;
36 | export default () => {
37 | const navItems = routes.map((route) => {
38 | let routePath = `/${route}`;
39 | if (route === rootRoute) {
40 | routePath = "/";
41 | }
42 | return (
43 |
44 |
45 | {route}
46 |
47 |
48 | );
49 | });
50 | return (
51 |
52 |
53 | {/* {navItems} */}
54 |
55 | {" "}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
64 | };
65 |
--------------------------------------------------------------------------------
/src/components/ThemeSelector.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { setTheme } from "../store/options.js";
3 | import { useTheme } from "../hooks";
4 |
5 | export default () => {
6 | const dispatch = useDispatch();
7 | const theme = useTheme();
8 |
9 | const onChange = () => {
10 | if (theme === "dark") {
11 | dispatch(setTheme("light"));
12 | } else {
13 | dispatch(setTheme("dark"));
14 | }
15 | };
16 | return (
17 | <>
18 |
26 | >
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/hooks.js:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux";
2 | import {
3 | selectTheme,
4 | selectHideFiles,
5 | selectSplitView,
6 | selectNetwork1,
7 | selectNetwork2,
8 | selectExplorer1,
9 | selectExplorer2,
10 | selectChain1,
11 | selectChain2,
12 | selectSelectedFile,
13 | } from "./store/options";
14 |
15 | import { selectChains } from "./store/chains";
16 |
17 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
18 | export const useAppDispatch = () => useDispatch();
19 | export const useAppSelector = useSelector;
20 | export const useTheme = () => useSelector(selectTheme);
21 | export const useSplitView = () => useSelector(selectSplitView);
22 | export const useHideFiles = () => useSelector(selectHideFiles);
23 | export const useSelectChains = () => useSelector(selectChains);
24 | export const useSelectNetwork1 = () => useSelector(selectNetwork1);
25 | export const useSelectNetwork2 = () => useSelector(selectNetwork2);
26 | export const useSelectExplorer1 = () => useSelector(selectExplorer1);
27 | export const useSelectExplorer2 = () => useSelector(selectExplorer2);
28 | export const useSelectChain1 = () => useSelector(selectChain1);
29 | export const useSelectChain2 = () => useSelector(selectChain2);
30 | export const useSelectSelectedFile = () => useSelector(selectSelectedFile);
31 |
--------------------------------------------------------------------------------
/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import "../vendor/prism.css";
3 | import "../vendor/prism.js";
4 | import { ThemeProvider, createTheme } from "@mui/material/styles";
5 | import CssBaseline from "@mui/material/CssBaseline";
6 | const darkTheme = createTheme({
7 | palette: {
8 | mode: "dark",
9 | },
10 | });
11 |
12 | import Layout from "../components/Layout";
13 | import { Provider } from "react-redux";
14 | import { PersistGate } from "redux-persist/integration/react";
15 | import Head from "next/head";
16 |
17 | import { store, persistor } from "../store";
18 |
19 | export default function MyApp({ Component, pageProps }) {
20 | return (
21 |
22 |
23 |
24 |
25 | x48.tools
26 |
27 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/pages/contract.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import ReactDiffViewer from "react-diff-viewer-continued";
3 | import prettier from "prettier";
4 | import prettierPluginSolidity from "prettier-plugin-solidity";
5 | import styled from "styled-components";
6 | import { useTheme, useHighlightSyntax, useAutoExpand } from "../hooks";
7 | import {
8 | ContentCopy,
9 | OpenInNew,
10 | Search,
11 | ExpandMore,
12 | ExpandLess,
13 | UnfoldMore,
14 | } from "@mui/icons-material";
15 | import {
16 | TextField,
17 | FormControl,
18 | InputLabel,
19 | Select,
20 | MenuItem,
21 | ToggleButtonGroup,
22 | SvgIcon,
23 | ToggleButton,
24 | IconButton,
25 | InputAdornment,
26 | } from "@mui/material";
27 |
28 | const prettierPlugins = [prettierPluginSolidity];
29 |
30 | const FileList = styled.div`
31 | margin-top: 20px;
32 | display: flex;
33 | grid-gap: 0px;
34 | flex-direction: column;
35 | `;
36 |
37 | const File = styled.div`
38 | display: flex;
39 | justify-content: space-between;
40 | `;
41 |
42 | const EditedShift = styled.div`
43 | position: relative;
44 | left: 11px;
45 | `;
46 |
47 | const EditedIcon = (
48 |
49 |
50 |
51 |
52 |
53 | );
54 |
55 | const Wrapper = styled.div`
56 | border-radius: 6px;
57 | overflow: hidden;
58 | border: 1px solid #30363d;
59 | left: 0px;
60 | right: 0px;
61 | margin-bottom: 30px;
62 | `;
63 |
64 | const CollapseWrap = styled.div`
65 | cursor: pointer;
66 | display: inline-flex;
67 | position: relative;
68 | top: 13px;
69 | margin-right: 5px;
70 | `;
71 |
72 | const Collapse = (
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 |
81 | const TitleWrapper = styled.div`
82 | position: relative;
83 | left: -15px;
84 | display: inline;
85 | `;
86 |
87 | const ShiftRight = styled.div`
88 | position: relative;
89 | left: 10px;
90 | `;
91 |
92 | const Summary = styled.div`
93 | margin-bottom: 20px;
94 | display: flex;
95 | justify-content: space-between;
96 | `;
97 | const SearchField = styled.div`
98 | margin-bottom: 40px;
99 | display: grid;
100 | grid-gap: 20px;
101 | grid-template-columns: 1fr 1fr;
102 | `;
103 |
104 | const SearchIconWrapper = styled.div`
105 | position: relative;
106 | width: 15px;
107 | left: -7px;
108 | `;
109 |
110 | const LineChanges = styled.div`
111 | display: inline-flex;
112 | align-items: center;
113 | `;
114 |
115 | const Contract = styled.div`
116 | display: grid;
117 | grid-gap: 5px;
118 | grid-template-columns: auto 150px;
119 | width: 100%;
120 | `;
121 |
122 | const Padding = styled.div`
123 | margin: 30px;
124 | padding-botom: 20px;
125 | position: absolute;
126 | left: 0px;
127 | right: 0px;
128 | `;
129 |
130 | const Layout = styled.div`
131 | display: grid;
132 | grid-template-columns: 300px auto;
133 | grid-gap: 20px;
134 | `;
135 |
136 | const LeftNav = styled.div``;
137 |
138 | const SourceHeader = styled.div`
139 | background-color: #121519;
140 | width: 100%;
141 | line-height: 32px;
142 | color: white;
143 | padding: 10px 20px;
144 | font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
145 | Liberation Mono, monospace;
146 | display: flex;
147 | justify-content: space-between;
148 | `;
149 |
150 | let oldCode = `
151 | // SPDX-License-Identifier: GPLv3
152 | pragma solidity 0.8.4;
153 |
154 | import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
155 | import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
156 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
157 | import "./LERC20Upgradable.sol";
158 |
159 | /// @title DEI stablecoin
160 | /// @author DEUS Finance
161 | contract DEIStablecoin is
162 | Initializable,
163 | LERC20Upgradable,
164 | AccessControlUpgradeable
165 | {
166 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
167 | bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
168 |
169 | function initialize(
170 | uint256 totalSupply,
171 | address admin,
172 | address recoveryAdmin,
173 | uint256 timelockPeriod,
174 | address lossless
175 | ) public initializer {
176 | __LERC20_init(
177 | totalSupply,
178 | "DEI",
179 | "DEI",
180 | admin,
181 | recoveryAdmin,
182 | timelockPeriod,
183 | lossless
184 | );
185 | __AccessControl_init();
186 | _grantRole(DEFAULT_ADMIN_ROLE, admin);
187 | }
188 |
189 | function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
190 | _mint(to, amount);
191 | }
192 |
193 | function burnFrom(address from, uint256 amount)
194 | public
195 | onlyRole(BURNER_ROLE)
196 | {
197 | _burn(from, amount);
198 | }
199 | }
200 | `;
201 |
202 | let newCode = `
203 | // SPDX-License-Identifier: GPLv3
204 | pragma solidity 0.8.4;
205 |
206 | import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
207 | import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
208 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
209 | import "./LERC20Upgradable.sol";
210 |
211 | /**
212 | * @title DEI Stablecoin
213 | * @author DEUS Finance
214 | * @notice Multichain stablecoin
215 | */
216 | contract DEIStablecoin is
217 | Initializable,
218 | LERC20Upgradable,
219 | AccessControlUpgradeable
220 | {
221 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
222 | bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
223 |
224 | function initialize(
225 | uint256 totalSupply,
226 | address admin,
227 | address recoveryAdmin,
228 | uint256 timelockPeriod,
229 | address lossless
230 | ) public initializer {
231 | __LERC20_init(
232 | totalSupply,
233 | "DEI",
234 | "DEI",
235 | admin,
236 | recoveryAdmin,
237 | timelockPeriod,
238 | lossless
239 | );
240 | __AccessControl_init();
241 | _grantRole(DEFAULT_ADMIN_ROLE, admin);
242 | }
243 |
244 | function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
245 | _mint(to, amount);
246 | }
247 | }
248 | `;
249 |
250 | const customStyles = {
251 | codeFoldGutter: {
252 | // display: "none",
253 | // display
254 | },
255 | codeFold: {
256 | // backgroundColor: "#0e1623",
257 | padding: "0px",
258 | margin: "0px",
259 | },
260 | };
261 |
262 | function App() {
263 | return sup
;
264 | }
265 |
266 | export default App;
267 |
--------------------------------------------------------------------------------
/src/pages/diff.js:
--------------------------------------------------------------------------------
1 | import stringSimilarity from "string-similarity";
2 | import { uuid as uuidV4 } from "uuidv4";
3 | import { useState, useEffect } from "react";
4 | import prettier from "prettier";
5 | import prettierPluginSolidity from "prettier-plugin-solidity";
6 | import styled from "styled-components";
7 | import { useDispatch } from "react-redux";
8 | import { mergeDeep } from "../utils/string";
9 | import Image from "next/image";
10 | import {
11 | useSplitView,
12 | useHideFiles,
13 | useSelectNetwork1,
14 | useSelectNetwork2,
15 | useSelectChains,
16 | useSelectChain1,
17 | useSelectChain2,
18 | } from "../hooks";
19 |
20 | import {
21 | ToggleButtonGroup,
22 | SvgIcon,
23 | ToggleButton,
24 | Tooltip,
25 | } from "@mui/material";
26 |
27 | import { GitHub, Twitter } from "@mui/icons-material";
28 |
29 | import { setSplitView, setHideFiles } from "../store/options";
30 | import ChainSelector from "../components/ChainSelector";
31 | import AddressInput from "../components/AddressInput";
32 | import FileList from "../components/FileList";
33 | import FileDiff from "../components/FileDiff";
34 |
35 | const prettierPlugins = [prettierPluginSolidity];
36 |
37 | const Flag = styled.img`
38 | height: 24px;
39 | `;
40 |
41 | const HeaderLeft = styled.div`
42 | display: flex;
43 | grid-gap: 5px;
44 | align-items: center;
45 | cursor: pointer;
46 | `;
47 |
48 | const Header = styled.div`
49 | display: flex;
50 | justify-content: space-between;
51 | padding: 10px 30px;
52 | margin-bottom: 20px;
53 | @media (max-width: 990px) {
54 | padding: 10px 10px;
55 | }
56 | `;
57 |
58 | const HeaderRight = styled.div`
59 | display: flex;
60 | grid-gap: 10px;
61 | cursor: pointer;
62 | `;
63 |
64 | const CollapseAndText = styled.div`
65 | display: flex;
66 | align-items: center;
67 | flex-direction: row;
68 | `;
69 | const CollapseWrap = styled.div`
70 | cursor: pointer;
71 | display: inline-flex;
72 | position: relative;
73 | top: -2px;
74 | margin-right: 15px;
75 | opacity: 0.5;
76 | &:hover {
77 | opacity: 1;
78 | }
79 | transform: ${(props) => (props.hidefiles === "true" ? "rotate(180deg)" : "")};
80 | `;
81 |
82 | const Summary = styled.div`
83 | height: 65px;
84 | padding-top: 15px;
85 | padding-bottom: 10px;
86 | z-index: 1;
87 | width: 100%;
88 | padding-left: 30px;
89 | padding-right: 30px;
90 | background-color: rgb(13, 17, 23);
91 | display: flex;
92 | justify-content: space-between;
93 | position: sticky;
94 | top: 0px;
95 | @media (max-width: 990px) {
96 | display: none;
97 | }
98 | `;
99 |
100 | const SearchField = styled.div`
101 | padding: 0px 30px;
102 | display: grid;
103 | grid-gap: 20px;
104 | grid-template-columns: 1fr 1fr;
105 | @media (max-width: 990px) {
106 | grid-template-rows: 1fr 1fr;
107 | grid-template-columns: unset;
108 | padding: 0px 10px;
109 | }
110 | `;
111 |
112 | const LineChanges = styled.div`
113 | display: inline-flex;
114 | align-items: center;
115 | `;
116 |
117 | const Contract = styled.div`
118 | display: grid;
119 | grid-gap: 5px;
120 | grid-template-columns: auto 150px;
121 | width: 100%;
122 | @media (max-width: 990px) {
123 | margin-bottom: 20px;
124 | }
125 | `;
126 |
127 | const Wrapper = styled.div`
128 | margin: 0px 0px;
129 | padding-botom: 20px;
130 | position: absolute;
131 | left: 0px;
132 | right: 0px;
133 | `;
134 |
135 | const Layout = styled.div`
136 | display: grid;
137 | grid-template-columns: ${(props) =>
138 | props.hidefiles === "true" ? "auto" : "300px auto"};
139 | margin: 0px 30px;
140 | @media (max-width: 990px) {
141 | grid-template-columns: auto;
142 | margin: 0px 10px;
143 | }
144 | grid-gap: 20px;
145 | `;
146 |
147 | const Results = styled.div`
148 | display: ${(props) => (props.hide === "true" ? "none" : "")};
149 | @media (max-width: 990px) {
150 | margin-top: 20px;
151 | overflow: auto;
152 | margin-right: 10px;
153 | }
154 | `;
155 |
156 | const HaventStarted = styled.div`
157 | width: 100%;
158 | display: flex;
159 | justify-content: center;
160 | top: 200px;
161 | @media (max-width: 990px) {
162 | top: 100px;
163 | }
164 | position: relative;
165 | display: ${(props) => (props.hide === "true" ? "none" : "")};
166 | `;
167 |
168 | const HaventStartedText = styled.div`
169 | font-size: 40px;
170 | @media (max-width: 990px) {
171 | font-size: 20px;
172 | }
173 | `;
174 |
175 | const openAddress = (url) => {
176 | window.open(url, "_blank").focus();
177 | };
178 |
179 | function App() {
180 | const hidefiles = useHideFiles();
181 | const splitView = useSplitView();
182 | const dispatch = useDispatch();
183 |
184 | const [addedText, setAddedText] = useState("");
185 | const [removedText, setRemovedText] = useState("");
186 | const [changedText, setChangedText] = useState("2 changed files");
187 |
188 | const [fileDiffCounts, setFileDiffCounts] = useState({});
189 | const [perfectMatch, setPerfectMatch] = useState(false);
190 |
191 | const [helperTextOverride1, setHelperTextOverride1] = useState(null);
192 | const [helperTextOverride2, setHelperTextOverride2] = useState(null);
193 | const [errorOverride1, setErrorOverride1] = useState(null);
194 | const [errorOverride2, setErrorOverride2] = useState(null);
195 |
196 | const [contracts, setContracts] = useState([]);
197 | const [initialLoad, setInitialLoad] = useState(true);
198 | const [filteredContracts, setFilteredContracts] = useState(contracts);
199 | const [code1, setCode1] = useState([]);
200 | const [code2, setCode2] = useState([]);
201 | const width =
202 | window.innerWidth ||
203 | document.documentElement.clientWidth ||
204 | document.body.clientWidth;
205 | const [mobileMode, setMobileMode] = useState(width <= 990);
206 |
207 | const [timeoutLeft, setTimeoutLeft] = useState();
208 | const [timeoutRight, setTimeoutRight] = useState();
209 |
210 | const [address1State, setAddress1State] = useState({
211 | valid: false,
212 | value: "",
213 | address: "",
214 | });
215 | const [address2State, setAddress2State] = useState({
216 | valid: false,
217 | value: "",
218 | address: "",
219 | });
220 |
221 | const network1 = useSelectNetwork1();
222 | const network2 = useSelectNetwork2();
223 |
224 | const [hasResults, setHasResults] = useState(false);
225 | const [previousAddress1, setPreviousAddress1] = useState("");
226 | const [previousAddress2, setPreviousAddress2] = useState("");
227 | const [previousNetwork1, setPreviousNetwork1] = useState(network1);
228 | const [previousNetwork2, setPreviousNetwork2] = useState(network2);
229 |
230 | const chains = useSelectChains();
231 | const chain1 = useSelectChain1();
232 | const chain2 = useSelectChain2();
233 |
234 | const hasChains = Object.keys(chains).length;
235 | const addressesValid = address1State.valid && address2State.valid;
236 |
237 | const handleScroll = () => {
238 | if (filteredContracts && !filteredContracts.length) {
239 | return;
240 | }
241 | const summaryBar = document.getElementById("summary-bar");
242 | const filelist = document.getElementById("filelist");
243 | const summaryBarRect = summaryBar.getBoundingClientRect();
244 | filelist.setAttribute(
245 | "style",
246 | `height: calc(100vh - 79px - 61px - ${summaryBarRect.top}px`
247 | );
248 | };
249 |
250 | const handleResize = () => {
251 | const width =
252 | window.innerWidth ||
253 | document.documentElement.clientWidth ||
254 | document.body.clientWidth;
255 | if (width <= 990) {
256 | setMobileMode(true);
257 | } else {
258 | setMobileMode(false);
259 | }
260 | };
261 |
262 | useEffect(() => {
263 | setInitialLoad(true);
264 | setTimeout(() => {
265 | setInitialLoad(false);
266 | }, 300);
267 | }, []);
268 |
269 | useEffect(() => {
270 | window.addEventListener("scroll", handleScroll);
271 | window.addEventListener("resize", handleResize);
272 | return () => {
273 | window.removeEventListener("scroll", handleScroll);
274 | window.removeEventListener("resize", handleResize);
275 | };
276 | }, []);
277 |
278 | useEffect(() => {
279 | handleScroll();
280 | });
281 |
282 | // Initial network state
283 | useEffect(() => {
284 | if (!previousNetwork1 && network1) {
285 | setPreviousNetwork1(network1);
286 | }
287 | if (!previousNetwork2 && network2) {
288 | setPreviousNetwork2(network2);
289 | }
290 | }, [network1, network2]);
291 |
292 | // Merge sources
293 | useEffect(() => {
294 | if (!(code1 && code1.length && code2 && code2.length)) {
295 | return;
296 | }
297 |
298 | const diffTree = {};
299 |
300 | for (const _code1 of code1) {
301 | let highestSimilarity = 0;
302 | let matchingFile;
303 | for (const _code2 of code2) {
304 | const similarity = stringSimilarity.compareTwoStrings(
305 | formatCode(_code1.source),
306 | formatCode(_code2.source)
307 | );
308 |
309 | if (similarity > highestSimilarity) {
310 | highestSimilarity = similarity;
311 | matchingFile = { ..._code2 };
312 | }
313 | }
314 | const uuid = uuidV4();
315 | if (highestSimilarity < 0.5) {
316 | matchingFile = null;
317 | highestSimilarity = 0;
318 | }
319 | const obj = {
320 | name: _code1.name,
321 | address1: _code1.address,
322 | address2: matchingFile && matchingFile.address,
323 | source1: _code1.source,
324 | source2: matchingFile && matchingFile.source,
325 | similarity: highestSimilarity,
326 | };
327 | diffTree[uuid] = obj;
328 | }
329 |
330 | for (const _code2 of code2) {
331 | let highestSimilarity = 0;
332 | for (const _code1 of code1) {
333 | const similarity = stringSimilarity.compareTwoStrings(
334 | formatCode(_code1.source),
335 | formatCode(_code2.source)
336 | );
337 |
338 | if (similarity > highestSimilarity) {
339 | highestSimilarity = similarity;
340 | }
341 | }
342 | const uuid = uuidV4();
343 | if (highestSimilarity < 0.5) {
344 | const obj = {
345 | name: _code2.name,
346 | address2: _code2.address,
347 | source2: _code2.source,
348 | similarity: 0,
349 | };
350 | diffTree[uuid] = obj;
351 | }
352 | }
353 |
354 | const merged = Object.values(diffTree);
355 |
356 | let mergedAndUnique = merged.filter((contracts) =>
357 | contracts.source1 && contracts.source2
358 | ? formatCode(contracts.source1) !== formatCode(contracts.source2)
359 | : contracts
360 | );
361 |
362 | setContracts(mergedAndUnique);
363 | if (Object.keys(mergedAndUnique).length === 0) {
364 | setPerfectMatch(true);
365 | } else {
366 | setPerfectMatch(false);
367 | }
368 | }, [code1, code2]);
369 |
370 | const delay = (time) => {
371 | return new Promise((resolve) => setTimeout(resolve, time));
372 | };
373 |
374 | const clearAddressHelper1 = () => {
375 | setHelperTextOverride1("");
376 | setErrorOverride1(false);
377 | };
378 |
379 | const clearAddressHelper2 = () => {
380 | setHelperTextOverride2("");
381 | setErrorOverride2(false);
382 | };
383 |
384 | const setHelperTextOverride1Fn = (msg) => {
385 | setHelperTextOverride1(msg);
386 | clearTimeout(timeoutLeft);
387 | };
388 |
389 | const setHelperTextOverride2Fn = (msg) => {
390 | setHelperTextOverride2(msg);
391 | clearTimeout(timeoutRight);
392 | };
393 |
394 | const getSourceCode = async (field, address) => {
395 | let explorerApi;
396 |
397 | let apiKey;
398 | if (field === 1) {
399 | apiKey = chain1.apiKey;
400 | setErrorOverride1(false);
401 | setHelperTextOverride1Fn("Loading...");
402 | explorerApi = chain1.explorerApiUrl;
403 | } else {
404 | apiKey = chain2.apiKey;
405 | setErrorOverride2(false);
406 | setHelperTextOverride2Fn("Loading...");
407 | explorerApi = chain2.explorerApiUrl;
408 | }
409 | const api = apiKey ? `&apiKey=${apiKey}` : "";
410 |
411 | const url = `${explorerApi}/api?module=contract&action=getsourcecode&address=${address}${api}`;
412 |
413 | let data = await fetch(url).then((res) => res.json());
414 | // console.log("raw resp", data);
415 | const notOk = data.status === "0";
416 | if (notOk) {
417 | await delay(1000);
418 | console.log("retry");
419 | getSourceCode(field, address);
420 | return;
421 | }
422 | const notVerified = "Source not verified";
423 | if (data.result[0].SourceCode === "") {
424 | if (field === 1) {
425 | setErrorOverride1(true);
426 | setHelperTextOverride1Fn(notVerified);
427 | } else {
428 | setErrorOverride2(true);
429 | setHelperTextOverride2Fn(notVerified);
430 | }
431 | return;
432 | }
433 | if (!(data.result && data.result[0] && data.result[0].SourceCode)) {
434 | if (field === 1) {
435 | setErrorOverride1(true);
436 | setHelperTextOverride1Fn(notVerified);
437 | } else {
438 | setErrorOverride2(true);
439 | setHelperTextOverride2Fn(notVerified);
440 | }
441 | return;
442 | }
443 |
444 | if (field === 1) {
445 | setErrorOverride1(false);
446 | setHelperTextOverride1("Successfully loaded contract");
447 | setTimeoutLeft(
448 | setTimeout(() => {
449 | setHelperTextOverride1(null);
450 | }, 3000)
451 | );
452 | } else {
453 | setErrorOverride2(false);
454 | setHelperTextOverride2("Successfully loaded contract");
455 | setTimeoutRight(
456 | setTimeout(() => {
457 | setHelperTextOverride2(null);
458 | }, 3000)
459 | );
460 | }
461 |
462 | let contractData = {};
463 | try {
464 | contractData = JSON.parse(data.result[0].SourceCode.slice(1, -1)).sources;
465 | } catch (e) {
466 | const firstResult = data.result[0];
467 | if (typeof firstResult.SourceCode === "string") {
468 | contractData[firstResult.ContractName] = {
469 | content: firstResult.SourceCode,
470 | };
471 | } else {
472 | contractData = JSON.parse(data.result[0].SourceCode);
473 | }
474 | }
475 |
476 | const sources = [];
477 | for (const [name, sourceObj] of Object.entries(contractData)) {
478 | const source = sourceObj.content;
479 | sources.push({ name, source, address });
480 | }
481 | if (field === 1) {
482 | setCode1(sources);
483 | } else {
484 | setCode2(sources);
485 | }
486 | };
487 |
488 | useEffect(() => {
489 | const address1Changed = address1State.value !== previousAddress1;
490 | const address2Changed = address2State.value !== previousAddress2;
491 | const network1Changed = network1 !== previousNetwork1;
492 | const network2Changed = network2 !== previousNetwork2;
493 |
494 | if (address1Changed && address1State.valid && hasChains) {
495 | getSourceCode(1, address1State.value);
496 | setPreviousAddress1(address1State.value);
497 | } else if (network1Changed) {
498 | setPreviousNetwork1(network1);
499 | if (address1State.valid) {
500 | getSourceCode(1, address1State.value);
501 | }
502 | }
503 | if (address2Changed && address2State.valid && hasChains) {
504 | getSourceCode(2, address2State.value);
505 | setPreviousAddress2(address2State.value);
506 | } else if (network2Changed) {
507 | setPreviousNetwork2(network2);
508 | if (address2State.valid) {
509 | getSourceCode(2, address2State.value);
510 | }
511 | }
512 |
513 | if (address1State.value && address2State.value)
514 | window.history.replaceState(
515 | {},
516 | "",
517 | `/diff?address1=${address1State.value}&chain1=${network1}&address2=${address2State.value}&chain2=${network2}`
518 | );
519 | const hasAddresses =
520 | address1State.value !== "" && address2State.value !== "";
521 | if (hasAddresses && hasChains) {
522 | const address1Changed = address1State.value !== previousAddress1;
523 | const address2Changed = address2State.value !== previousAddress2;
524 | const addressesChanged = address1Changed || address2Changed;
525 |
526 | if (addressesValid && addressesChanged) {
527 | setHasResults(true);
528 | }
529 | } else {
530 | if (hasAddresses) {
531 | setHasResults(false);
532 | }
533 | }
534 | }, [address1State.value, address2State.value, network1, network2, hasChains]);
535 |
536 | useEffect(() => {
537 | const added = document.querySelectorAll(
538 | "[class*='gutter'][class*='diff-added']"
539 | ).length;
540 | const removed = document.querySelectorAll(
541 | "[class*='gutter'][class*='diff-removed']"
542 | ).length;
543 |
544 | let addedRemoved = {};
545 | filteredContracts.forEach(({ name, source1, source2 }) => {
546 | const removedForFile = document
547 | .getElementById(name)
548 | .querySelectorAll("[class*='gutter'][class*='diff-removed']").length;
549 | const addedForFile = document
550 | .getElementById(name)
551 | .querySelectorAll("[class*='gutter'][class*='diff-added']").length;
552 | let modificationType;
553 | if (source1 && !source2) {
554 | modificationType = "removed";
555 | } else if (!source1 && source2) {
556 | modificationType = "added";
557 | } else if (source1 !== source2) {
558 | modificationType = "modified";
559 | }
560 | addedRemoved[name] = {
561 | added: addedForFile,
562 | removed: removedForFile,
563 | modificationType,
564 | };
565 | });
566 | setFileDiffCounts(addedRemoved);
567 |
568 | const changed = filteredContracts.length;
569 | const addedSuffix = added === 0 || added > 1 ? "s" : "";
570 | const removedSuffix = removed === 0 || removed > 1 ? "s" : "";
571 | const changedSuffix = changed === 0 || changed > 1 ? "s" : "";
572 | setChangedText(
573 |
574 | {changed} changed file{changedSuffix}
575 |
576 | );
577 | setAddedText(
578 |
579 | {added} addition{addedSuffix}
580 |
581 | );
582 | setRemovedText(
583 |
584 | {removed} deletion{removedSuffix}
585 |
586 | );
587 | }, [filteredContracts]);
588 |
589 | const toggleHideFiles = () => {
590 | dispatch(setHideFiles(hidefiles === "true" ? "fasle" : "true"));
591 | };
592 |
593 | const Collapse = (
594 |
598 |
599 |
603 |
604 |
605 |
606 |
607 |
608 | );
609 |
610 | const onViewChange = (evt) => {
611 | dispatch(setSplitView(evt.target.value === "split" ? true : false));
612 | };
613 |
614 | // eslint-disable-next-line
615 | const formatCode = (code) =>
616 | prettier.format(code, {
617 | parser: "solidity-parse",
618 | // eslint-disable-next-line
619 | plugins: prettierPlugins,
620 | });
621 |
622 | const diffs =
623 | filteredContracts &&
624 | filteredContracts.map((item) => (
625 |
635 | ));
636 |
637 | return (
638 |
639 |
640 | openAddress("/")}>
641 |
642 | x48.tools
643 |
644 |
645 |
648 | openAddress("https://github.com/x48115/contract-diff-tool")
649 | }
650 | />
651 | openAddress("https://discord.gg/KPhtdR7m2m")}
657 | />
658 | openAddress("https://twitter.com/x48_crypto")}
661 | />
662 |
663 |
664 |
665 |
666 |
675 |
676 |
677 |
678 |
687 |
688 |
689 |
690 |
700 |
701 | {(errorOverride1 && helperTextOverride1) ||
702 | (errorOverride2 && helperTextOverride2) ||
703 | "Enter contract addresses above"}
704 |
705 |
706 |
718 | Contracts are identical
719 |
720 |
730 |
731 |
732 |
733 |
734 |
747 |
748 |
749 | {Collapse}
750 |
751 |
752 | Showing {changedText} with {addedText} and {removedText}.
753 |
754 |
755 |
756 |
763 |
767 | Split
768 |
769 |
773 | Unified
774 |
775 |
776 |
777 |
778 |
779 |
787 | {diffs}
788 |
789 |
790 |
791 | );
792 | }
793 |
794 | export default App;
795 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import { styled } from "styled-components";
2 | import Logo from "../components/Logo";
3 | import { GitHub, Twitter } from "@mui/icons-material";
4 | import Image from "next/image";
5 |
6 | const Wrapper = styled.div`
7 | margin-top: 60px;
8 | > div {
9 | font-size: 12px;
10 | }
11 | min-height: 400px;
12 | `;
13 |
14 | const Description = styled.div`
15 | color: #888;
16 | font-size: 14px !important;
17 | font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
18 | Liberation Mono, monospace;
19 | width: 100%;
20 | display: flex;
21 | justify-content: center;
22 | margin-top: 20px;
23 | `;
24 |
25 | const Links = styled.div`
26 | > a {
27 | color: white;
28 | font-size: 28px !important;
29 | font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
30 | Liberation Mono, monospace;
31 | width: 100%;
32 | display: flex;
33 | justify-content: center;
34 | }
35 | margin-top: 80px;
36 | display: flex;
37 | grid-gap: 20px;
38 | flex-direction: column;
39 | `;
40 | const Icons = styled.div`
41 | display: flex;
42 | grid-gap: 30px;
43 | justify-content: center;
44 | width: 100%;
45 | margin-top: 120px;
46 | svg,
47 | img {
48 | cursor: pointer;
49 | }
50 | `;
51 |
52 | const openAddress = (url) => {
53 | window.open(url, "_blank").focus();
54 | };
55 |
56 | function App() {
57 | return (
58 |
59 |
60 | Building a collection of EVM tools
61 |
62 |
63 | Contract Diff tool
64 |
65 | {/*
69 | Blockchain University
70 | */}
71 |
72 |
73 |
76 | openAddress("https://github.com/x48115/contract-diff-tool")
77 | }
78 | />
79 | openAddress("https://discord.gg/KPhtdR7m2m")}
84 | />
85 | openAddress("https://twitter.com/x48_crypto")}
88 | />
89 |
90 |
91 | );
92 | }
93 |
94 | export default App;
95 |
--------------------------------------------------------------------------------
/src/store/chains.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | list: [],
5 | };
6 |
7 | const slice = createSlice({
8 | name: "chains",
9 | initialState,
10 | reducers: {
11 | setChains(state, action) {
12 | state.list = action.payload;
13 | },
14 | },
15 | });
16 |
17 | export const { setChains } = slice.actions;
18 | export const selectChains = (state) => state.chains.list;
19 |
20 | export default slice.reducer;
21 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import {
3 | persistStore,
4 | persistReducer,
5 | FLUSH,
6 | REHYDRATE,
7 | PAUSE,
8 | PERSIST,
9 | PURGE,
10 | REGISTER,
11 | } from "redux-persist";
12 |
13 | import storage from "redux-persist/lib/storage";
14 | import reduxWebsocket from "@giantmachines/redux-websocket";
15 |
16 | import rootReducer from "./reducers";
17 |
18 | const persistConfig = {
19 | key: "root",
20 | version: 1,
21 | storage,
22 | };
23 |
24 | const persistedReducer = persistReducer(persistConfig, rootReducer);
25 |
26 | // Create the middleware instance.
27 | const websocketMiddlewareOptions = {
28 | dateSerializer: (date) => date.getTime(),
29 | prefix: "websocket/REDUX_WEBSOCKET",
30 | serializer: (data) => {
31 | return JSON.stringify(data);
32 | },
33 | reconnectOnClose: true,
34 | onOpen: (ws) => {},
35 | };
36 | const reduxWebsocketMiddleware = reduxWebsocket(websocketMiddlewareOptions);
37 |
38 | const websocketOnOpenMiddleware = (store) => (next) => (action) => {
39 | if (action.type === "REDUX_WEBSOCKET::OPEN") {
40 | // store.dispatch({ type: "REDUX_WEBSOCKET/OPEN" });
41 | }
42 | let result = next(action);
43 | return result;
44 | };
45 |
46 | export const store = configureStore({
47 | reducer: persistedReducer,
48 | middleware: (getDefaultMiddleware) =>
49 | getDefaultMiddleware({
50 | serializableCheck: {
51 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
52 | ignoredActionPaths: ["payload", "payload.event"],
53 | },
54 | }).concat(reduxWebsocketMiddleware, websocketOnOpenMiddleware),
55 | });
56 |
57 | export const persistor = persistStore(store);
58 |
--------------------------------------------------------------------------------
/src/store/options.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | mode: "dark",
5 | hideFiles: "false",
6 | splitView: true,
7 | network1: 1,
8 | network2: 1,
9 | selectedFile: "",
10 | };
11 |
12 | const slice = createSlice({
13 | name: "options",
14 | initialState,
15 | reducers: {
16 | setTheme(state, action) {
17 | state.mode = action.payload;
18 | },
19 | setHideFiles(state, action) {
20 | state.hideFiles = action.payload;
21 | },
22 | setSplitView(state, action) {
23 | state.splitView = action.payload;
24 | },
25 | setNetwork1(state, action) {
26 | state.network1 = action.payload;
27 | },
28 | setNetwork2(state, action) {
29 | state.network2 = action.payload;
30 | },
31 | setSelectedFile(state, action) {
32 | state.selectedFile = action.payload;
33 | },
34 | },
35 | });
36 |
37 | export const {
38 | setTheme,
39 | setSplitView,
40 | setHideFiles,
41 | setNetwork1,
42 | setNetwork2,
43 | setSelectedFile,
44 | } = slice.actions;
45 | export const selectTheme = (state) => state.options.mode;
46 | export const selectNetwork1 = (state) => state.options.network1;
47 | export const selectNetwork2 = (state) => state.options.network2;
48 | export const selectHideFiles = (state) => state.options.hideFiles;
49 | export const selectSplitView = (state) => state.options.splitView;
50 | export const selectSelectedFile = (state) => state.options.selectedFile;
51 |
52 | // Explorers
53 | export const selectExplorer1 = (state) =>
54 | state.chains.list.length &&
55 | state.options.network1 &&
56 | state.chains.list.find(
57 | (chain) => chain.chainId === parseInt(state.options.network1)
58 | ).explorers[0].url;
59 | export const selectExplorer2 = (state) =>
60 | state.chains.list.length &&
61 | state.options.network2 &&
62 | state.chains.list.find(
63 | (chain) => chain.chainId === parseInt(state.options.network2)
64 | ).explorers[0].url;
65 |
66 | // Chains
67 | export const selectChain1 = (state) =>
68 | state.chains.list.length &&
69 | state.options.network1 &&
70 | state.chains.list.find(
71 | (chain) => chain.chainId === parseInt(state.options.network1)
72 | );
73 |
74 | export const selectChain2 = (state) =>
75 | state.chains.list.length &&
76 | state.options.network2 &&
77 | state.chains.list.find(
78 | (chain) => chain.chainId === parseInt(state.options.network2)
79 | );
80 |
81 | export default slice.reducer;
82 |
--------------------------------------------------------------------------------
/src/store/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import options from "./options";
3 | import swap from "./swap";
4 | import chains from "./chains";
5 |
6 | const rootReducer = combineReducers({
7 | options,
8 | swap,
9 | chains,
10 | });
11 |
12 | export default rootReducer;
13 |
--------------------------------------------------------------------------------
/src/store/swap.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | network: 1,
5 | };
6 |
7 | const slice = createSlice({
8 | name: "swap",
9 | initialState,
10 | reducers: {
11 | setNetwork(state, action) {
12 | state.network = action.payload;
13 | },
14 | },
15 | });
16 |
17 | export const { setNetwork } = slice.actions;
18 | export const selectNetwork = (state) => state.swap.network;
19 |
20 | export default slice.reducer;
21 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | top: 0px;
6 | bottom: 0px;
7 | left: 0px;
8 | right: 0px;
9 | position: absolute;
10 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans',
11 | Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
12 | background-color: rgb(13, 17, 23);
13 | }
14 |
15 | * {
16 | box-sizing: border-box;
17 | }
18 |
19 | .main-wrapper {
20 | top: 0px;
21 | bottom: 0px;
22 | left: 0px;
23 | right: 0px;
24 | position: absolute;
25 | }
26 |
27 | .dark.main-wrapper {
28 | color: white;
29 | }
30 |
31 | [class*='code-fold'] > td > a {
32 | font-size: 20px;
33 | text-decoration-line: none !important;
34 | position: relative;
35 | top: 0px;
36 | width: 50px;
37 | height: 40px;
38 | display: flex;
39 | justify-content: center;
40 | align-items: center;
41 | }
42 |
43 | [class*='code-fold'] > td > a > div {
44 | position: relative;
45 | top: -2px;
46 | left: -2px;
47 | }
48 |
49 | [class*='code-fold'] > td:nth-of-type(4) {
50 | position: relative;
51 | right: 0px;
52 | height: 40px;
53 | left: -106px;
54 | display: flex;
55 | align-items: center;
56 | }
57 |
58 | [class*='split-view'] [class*='code-fold'] > td:nth-of-type(3) {
59 | position: relative;
60 | right: 0px;
61 | height: 40px;
62 | left: -81px;
63 | display: flex;
64 | align-items: center;
65 | }
66 |
67 | [class*='split-view'] [class*='code-fold'] > td:nth-of-type(4) {
68 | display: table-cell;
69 | left: 0px;
70 | position: auto;
71 | height: auto;
72 | }
73 |
74 | .loader {
75 | width: 48px;
76 | height: 48px;
77 | border: 5px solid #FFF;
78 | border-bottom-color: transparent;
79 | border-radius: 50%;
80 | display: inline-block;
81 | box-sizing: border-box;
82 | animation: rotation .5s linear infinite;
83 | }
84 |
85 | @keyframes rotation {
86 | 0% {
87 | transform: rotate(0deg);
88 | }
89 | 100% {
90 | transform: rotate(360deg);
91 | }
92 | }
93 |
94 | a {
95 | color: white;
96 | }
97 |
98 | .MuiFormHelperText-root {
99 | position: absolute;
100 | top: 40px;
101 |
102 | }
--------------------------------------------------------------------------------
/src/utils/api.js:
--------------------------------------------------------------------------------
1 | const baseUrl = "http://localhost:80";
2 |
3 | export default {
4 | get: async (route) => {
5 | const url = `${baseUrl}${route}`;
6 | console.log("get", url);
7 | const response = await fetch(url, {
8 | method: "GET",
9 | headers: {
10 | "Content-Type": "application/json",
11 | },
12 | });
13 | const result = await response.json();
14 |
15 | return result;
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/src/utils/string.js:
--------------------------------------------------------------------------------
1 | const units = {
2 | year: 24 * 60 * 60 * 1000 * 365,
3 | month: (24 * 60 * 60 * 1000 * 365) / 12,
4 | day: 24 * 60 * 60 * 1000,
5 | hour: 60 * 60 * 1000,
6 | minute: 60 * 1000,
7 | second: 1000,
8 | };
9 |
10 | const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
11 |
12 | export const getRelativeTime = (d1, d2 = new Date()) => {
13 | const elapsed = d1 - d2;
14 |
15 | // "Math.abs" accounts for both "past" & "future" scenarios
16 | for (const u in units)
17 | if (Math.abs(elapsed) > units[u] || u == "second")
18 | return rtf.format(Math.round(elapsed / units[u]), u);
19 | };
20 |
21 | export function shortenAddress(address, chars = 4) {
22 | if (address === "") {
23 | return "";
24 | }
25 | if (address.endsWith(".eth")) {
26 | return address;
27 | }
28 | return `${address.substring(0, chars + 2)}...${address.substring(
29 | 42 - chars
30 | )}`;
31 | }
32 |
33 | export const mergeDeep = (target, ...sources) => {
34 | if (!sources.length) return target;
35 | const source = sources.shift();
36 |
37 | if (target instanceof Object && source instanceof Object) {
38 | for (const key in source) {
39 | if (source[key] instanceof Object) {
40 | if (!target[key]) Object.assign(target, { [key]: {} });
41 | mergeDeep(target[key], source[key]);
42 | } else {
43 | Object.assign(target, { [key]: source[key] });
44 | }
45 | }
46 | }
47 | return mergeDeep(target, ...sources);
48 | };
49 |
50 | export const getEndOfPath = (path) => {
51 | const parts = path.split("/");
52 | const name = parts[parts.length - 1];
53 | return name;
54 | };
55 |
56 | export const highlight = (needle, haystack) => {
57 | const reg = new RegExp(needle, "gi");
58 | return (
59 | "" + str + ""),
62 | }}
63 | />
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/src/vendor/prism.css:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.29.0
2 | https://prismjs.com/download.html#themes=prism-tomorrow&languages=clike+solidity */
3 | code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}
4 |
--------------------------------------------------------------------------------
/src/vendor/prism.js:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.29.0
2 | https://prismjs.com/download.html#themes=prism-tomorrow&languages=clike+solidity */
3 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
4 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
5 | Prism.languages.solidity=Prism.languages.extend("clike",{"class-name":{pattern:/(\b(?:contract|enum|interface|library|new|struct|using)\s+)(?!\d)[\w$]+/,lookbehind:!0},keyword:/\b(?:_|anonymous|as|assembly|assert|break|calldata|case|constant|constructor|continue|contract|default|delete|do|else|emit|enum|event|external|for|from|function|if|import|indexed|inherited|interface|internal|is|let|library|mapping|memory|modifier|new|payable|pragma|private|public|pure|require|returns?|revert|selfdestruct|solidity|storage|struct|suicide|switch|this|throw|using|var|view|while)\b/,operator:/=>|->|:=|=:|\*\*|\+\+|--|\|\||&&|<<=?|>>=?|[-+*/%^&|<>!=]=?|[~?]/}),Prism.languages.insertBefore("solidity","keyword",{builtin:/\b(?:address|bool|byte|u?int(?:8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?|string|bytes(?:[1-9]|[12]\d|3[0-2])?)\b/}),Prism.languages.insertBefore("solidity","number",{version:{pattern:/([<>]=?|\^)\d+\.\d+\.\d+\b/,lookbehind:!0,alias:"number"}}),Prism.languages.sol=Prism.languages.solidity;
6 |
--------------------------------------------------------------------------------