add etherscan plugin

pull/2723/head
yann300 2 years ago
parent 1d27a225ea
commit c944a1a162
  1. 13
      apps/etherscan/.babelrc
  2. 16
      apps/etherscan/.browserslistrc
  3. 34
      apps/etherscan/.eslintrc.json
  4. 7
      apps/etherscan/src/app/App.css
  5. 24
      apps/etherscan/src/app/AppContext.tsx
  6. 157
      apps/etherscan/src/app/app.tsx
  7. 159
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  8. 34
      apps/etherscan/src/app/components/SubmitButton.tsx
  9. 2
      apps/etherscan/src/app/components/index.ts
  10. 37
      apps/etherscan/src/app/hooks/useLocalStorage.tsx
  11. 19
      apps/etherscan/src/app/layouts/Default.tsx
  12. 1
      apps/etherscan/src/app/layouts/index.ts
  13. 17
      apps/etherscan/src/app/logo.svg
  14. 51
      apps/etherscan/src/app/routes.tsx
  15. 11
      apps/etherscan/src/app/star.svg
  16. 6
      apps/etherscan/src/app/types/Receipt.ts
  17. 1
      apps/etherscan/src/app/types/ThemeType.ts
  18. 2
      apps/etherscan/src/app/types/index.ts
  19. 1
      apps/etherscan/src/app/utils/index.ts
  20. 34
      apps/etherscan/src/app/utils/utilities.ts
  21. 59
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  22. 31
      apps/etherscan/src/app/views/ErrorView.tsx
  23. 36
      apps/etherscan/src/app/views/HomeView.tsx
  24. 135
      apps/etherscan/src/app/views/ReceiptsView.tsx
  25. 302
      apps/etherscan/src/app/views/VerifyView.tsx
  26. 4
      apps/etherscan/src/app/views/index.ts
  27. 0
      apps/etherscan/src/assets/.gitkeep
  28. 3
      apps/etherscan/src/environments/environment.prod.ts
  29. 6
      apps/etherscan/src/environments/environment.ts
  30. BIN
      apps/etherscan/src/favicon.ico
  31. 14
      apps/etherscan/src/index.html
  32. 7
      apps/etherscan/src/main.tsx
  33. 7
      apps/etherscan/src/polyfills.ts
  34. 1
      apps/etherscan/src/styles.css
  35. 14
      apps/etherscan/tsconfig.app.json
  36. 20
      apps/etherscan/tsconfig.json
  37. 3
      nx.json
  38. 2
      package.json
  39. 83
      workspace.json
  40. 62
      yarn.lock

@ -0,0 +1,13 @@
{
"presets": [
[
"@nrwl/react/babel", {
"runtime": "automatic"
}
]
],
"plugins": [
]
}

@ -0,0 +1,16 @@
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

@ -0,0 +1,34 @@
{
"extends": [
"plugin:@nrwl/nx/react",
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

@ -0,0 +1,7 @@
body {
margin: 0;
}
#root {
padding: 5px;
}

@ -0,0 +1,24 @@
import React from "react"
import { PluginClient } from "@remixproject/plugin"
import { Receipt, ThemeType } from "./types"
export const AppContext = React.createContext({
apiKey: "",
setAPIKey: (value: string) => {
console.log("Set API Key from Context")
},
clientInstance: {} as PluginClient,
receipts: [] as Receipt[],
setReceipts: (receipts: Receipt[]) => {
console.log("Calling Set Receipts")
},
contracts: [] as string[],
setContracts: (contracts: string[]) => {
console.log("Calling Set Contract Names")
},
themeType: "dark" as ThemeType,
setThemeType: (themeType: ThemeType) => {
console.log("Calling Set Theme Type")
},
})

@ -0,0 +1,157 @@
import React, { useState, useEffect, useRef } from "react"
import {
CompilationFileSources,
CompilationResult,
} from "@remixproject/plugin-api"
import { PluginClient } from "@remixproject/plugin";
import { createClient } from "@remixproject/plugin-webview";
import { AppContext } from "./AppContext"
import { DisplayRoutes } from "./routes"
import { useLocalStorage } from "./hooks/useLocalStorage"
import { getReceiptStatus, getEtherScanApi, getNetworkName } from "./utils"
import { Receipt, ThemeType } from "./types"
import "./App.css"
export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts
let result: string[] = []
for (const file of Object.keys(compiledContracts)) {
const newContractNames = Object.keys(compiledContracts[file])
result = [...result, ...newContractNames]
}
return result
}
const App = () => {
const [apiKey, setAPIKey] = useLocalStorage("apiKey", "")
const [clientInstance, setClientInstance] = useState(undefined as any)
const [receipts, setReceipts] = useLocalStorage("receipts", [])
const [contracts, setContracts] = useState([] as string[])
const [themeType, setThemeType] = useState("dark" as ThemeType)
const clientInstanceRef = useRef(clientInstance)
clientInstanceRef.current = clientInstance
const contractsRef = useRef(contracts)
contractsRef.current = contracts
useEffect(() => {
console.log("Remix Etherscan loading...")
const client = new PluginClient()
createClient(client)
const loadClient = async () => {
await client.onload()
setClientInstance(client)
console.log("Remix Etherscan Plugin has been loaded")
client.on("solidity",
"compilationFinished",
(
fileName: string,
source: CompilationFileSources,
languageVersion: string,
data: CompilationResult
) => {
console.log("New compilation received")
const newContractsNames = getNewContractNames(data)
const newContractsToSave: string[] = [
...contractsRef.current,
...newContractsNames,
]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts)
}
)
//const currentTheme = await client.call("theme", "currentTheme")
//setThemeType(currentTheme.quality)
//client.on("theme", "themeChanged", (theme) => {
// setThemeType(theme.quality)
//})
}
loadClient()
}, [])
useEffect(() => {
if (!clientInstance) {
return
}
const receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => {
return item.status !== "Verified"
})
if (receiptsNotVerified.length > 0) {
let timer1 = setInterval(() => {
for (const item in receiptsNotVerified) {
}
receiptsNotVerified.forEach(async (item) => {
if (!clientInstanceRef.current) {
return {}
}
const network = await getNetworkName(clientInstanceRef.current)
if (network === "vm") {
return {}
}
const status = await getReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(network)
)
if (status === "Pass - Verified") {
const newReceipts = receipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) {
return {
...currentReceipt,
status: "Verified",
}
}
return currentReceipt
})
clearInterval(timer1)
setReceipts(newReceipts)
return () => {
clearInterval(timer1)
}
}
return {}
})
}, 5000)
}
}, [receipts, clientInstance, apiKey, setReceipts])
return (
<AppContext.Provider
value={{
apiKey,
setAPIKey,
clientInstance,
receipts,
setReceipts,
contracts,
setContracts,
themeType,
setThemeType,
}}
>
<DisplayRoutes />
</AppContext.Provider>
)
}
export default App

@ -0,0 +1,159 @@
import React from "react"
import { NavLink } from "react-router-dom"
import { AppContext } from "../AppContext"
import { ThemeType } from "../types"
interface Props {
title?: string
showBackButton?: boolean
from: string
}
interface IconProps {
from: string
themeType: ThemeType
}
const HomeIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-toggle="tooltip"
data-placement="top"
title="Home"
to={{
pathname: "/"
}}
state={ from }
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-house-door-fill"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M6.5 10.995V14.5a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .146-.354l6-6a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 .146.354v7a.5.5 0 0 1-.5.5h-4a.5.5 0 0 1-.5-.5V11c0-.25-.25-.5-.5-.5H7c-.25 0-.5.25-.5.495z" />
<path
fillRule="evenodd"
d="M13 2.5V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"
/>
</svg>
</NavLink>
)
}
const SettingsIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-toggle="tooltip"
data-placement="top"
title="Settings"
to={{
pathname: "/settings",
}}
state= {from}
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-gear-fill"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 0 0-5.86 2.929 2.929 0 0 0 0 5.858z"
/>
<path
fillRule="evenodd"
d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 0 0-5.86 2.929 2.929 0 0 0 0 5.858z"
/>
</svg>
</NavLink>
)
}
const getStyleFilterIcon = (themeType: ThemeType) => {
const invert = themeType === "dark" ? 1 : 0
const brightness = themeType === "dark" ? "150" : "0" // should be >100 for icons with color
return {
filter: `invert(${invert}) grayscale(1) brightness(${brightness}%)`,
}
}
const ReceiptsIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-toggle="tooltip"
data-placement="top"
title="Receipts"
to={{
pathname: "/receipts",
}}
state= { from }
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-receipt"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M1.92.506a.5.5 0 0 1 .434.14L3 1.293l.646-.647a.5.5 0 0 1 .708 0L5 1.293l.646-.647a.5.5 0 0 1 .708 0L7 1.293l.646-.647a.5.5 0 0 1 .708 0L9 1.293l.646-.647a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .801.13l.5 1A.5.5 0 0 1 15 2v12a.5.5 0 0 1-.053.224l-.5 1a.5.5 0 0 1-.8.13L13 14.707l-.646.647a.5.5 0 0 1-.708 0L11 14.707l-.646.647a.5.5 0 0 1-.708 0L9 14.707l-.646.647a.5.5 0 0 1-.708 0L7 14.707l-.646.647a.5.5 0 0 1-.708 0L5 14.707l-.646.647a.5.5 0 0 1-.708 0L3 14.707l-.646.647a.5.5 0 0 1-.801-.13l-.5-1A.5.5 0 0 1 1 14V2a.5.5 0 0 1 .053-.224l.5-1a.5.5 0 0 1 .367-.27zm.217 1.338L2 2.118v11.764l.137.274.51-.51a.5.5 0 0 1 .707 0l.646.647.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.509.509.137-.274V2.118l-.137-.274-.51.51a.5.5 0 0 1-.707 0L12 1.707l-.646.647a.5.5 0 0 1-.708 0L10 1.707l-.646.647a.5.5 0 0 1-.708 0L8 1.707l-.646.647a.5.5 0 0 1-.708 0L6 1.707l-.646.647a.5.5 0 0 1-.708 0L4 1.707l-.646.647a.5.5 0 0 1-.708 0l-.509-.51z"
/>
<path
fillRule="evenodd"
d="M3 4.5a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm8-6a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5z"
/>
</svg>
</NavLink>
)
}
export const HeaderWithSettings: React.FC<Props> = ({
title = "",
showBackButton = false,
from,
}) => {
return (
<AppContext.Consumer>
{({ themeType }) => (
<div>
<h6>{title}</h6>
<div style={{ float: "right" }}>
<HomeIcon from={from} themeType={themeType} />
<ReceiptsIcon from={from} themeType={themeType} />
<SettingsIcon from={from} themeType={themeType} />
</div>
</div>
)}
</AppContext.Consumer>
)
}

@ -0,0 +1,34 @@
import React from "react"
interface Props {
text: string
isSubmitting?: boolean
}
export const SubmitButton: React.FC<Props> = ({
text,
isSubmitting = false,
}) => {
return (
<button
style={{ padding: "0.25rem 0.4rem", marginRight: "0.5em" }}
type="submit"
className="btn btn-primary"
disabled={isSubmitting}
>
{!isSubmitting && text}
{isSubmitting && (
<div>
<span
className="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
style={{ marginRight: "0.3em" }}
/>
Verifying...Please wait
</div>
)}
</button>
)
}

@ -0,0 +1,2 @@
export { HeaderWithSettings } from "./HeaderWithSettings"
export { SubmitButton } from "./SubmitButton"

@ -0,0 +1,37 @@
import { useState } from "react"
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue
} catch (error) {
// If error also return initialValue
console.log(error)
return initialValue
}
})
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value: any) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore)
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error)
}
}
return [storedValue, setValue]
}

@ -0,0 +1,19 @@
import React, { PropsWithChildren } from "react"
import { HeaderWithSettings } from "../components"
interface Props {
from: string
}
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({
children,
from,
}) => {
return (
<div>
<HeaderWithSettings from={from} />
{children}
</div>
)
}

@ -0,0 +1 @@
export { DefaultLayout } from "./Default"

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="262px" height="163px" viewBox="0 0 262 163" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Styles-&amp;-Quick-Wins" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Nx---Quick-Wins" transform="translate(-476.000000, -1284.000000)" fill-rule="nonzero">
<g id="Logos" transform="translate(-11.000000, 782.000000)">
<g id="Nx_Flat_White" transform="translate(487.000000, 502.000000)">
<polygon id="Path" fill="#FFFFFF" points="130.68 104.59 97.49 52.71 97.44 96.3 40.24 0 0 0 0 162.57 39.79 162.57 39.92 66.39 96.53 158.26"></polygon>
<polygon id="Path" fill="#FFFFFF" points="97.5 41.79 137.24 41.79 137.33 41.33 137.33 0 97.54 0 97.49 41.33"></polygon>
<path d="M198.66,86.86 C189.139872,86.6795216 180.538723,92.516445 177.19,101.43 C182.764789,93.0931021 193.379673,89.7432211 202.73,93.37 C207.05,95.13 212.73,97.97 217.23,96.45 C212.950306,90.4438814 206.034895,86.8725952 198.66,86.86 L198.66,86.86 Z" id="Path" fill="#96D8E9"></path>
<path d="M243.75,106.42 C243.75,101.55 241.1,100.42 235.6,98.42 C231.52,97 226.89,95.4 223.52,91 C222.86,90.13 222.25,89.15 221.6,88.11 C220.14382,85.4164099 218.169266,83.037429 215.79,81.11 C212.58,78.75 208.37,77.6 202.91,77.6 C191.954261,77.6076705 182.084192,84.2206169 177.91,94.35 C183.186964,87.0278244 191.956716,83.0605026 200.940147,83.9314609 C209.923578,84.8024193 217.767888,90.3805017 221.54,98.58 C223.424615,101.689762 227.141337,103.174819 230.65,102.22 C236.02,101.07 235.65,106.15 243.76,107.87 L243.75,106.42 Z" id="Path" fill="#48C4E5"></path>
<path d="M261.46,105.38 L261.46,105.27 C261.34,73.03 235.17,45.45 202.91,45.45 C183.207085,45.4363165 164.821777,55.3450614 154,71.81 L153.79,71.45 L137.23,45.45 L97.5,45.4499858 L135.25,104.57 L98.41,162.57 L137,162.57 L153.79,136.78 L170.88,162.57 L209.48,162.57 L174.48,107.49 C173.899005,106.416838 173.583536,105.220114 173.56,104 C173.557346,96.2203871 176.64661,88.7586448 182.147627,83.2576275 C187.648645,77.7566101 195.110387,74.6673462 202.89,74.67 C219.11,74.67 221.82,84.37 225.32,88.93 C232.23,97.93 246.03,93.99 246.03,105.73 L246.03,105.73 C246.071086,108.480945 247.576662,111.001004 249.979593,112.340896 C252.382524,113.680787 255.317747,113.636949 257.679593,112.225896 C260.041438,110.814842 261.471086,108.250945 261.43,105.5 L261.43,105.5 L261.43,105.38 L261.46,105.38 Z" id="Path" fill="#FFFFFF"></path>
<path d="M261.5,113.68 C261.892278,116.421801 261.504116,119.218653 260.38,121.75 C258.18,126.84 254.51,125.14 254.51,125.14 C254.51,125.14 251.35,123.6 253.27,120.65 C255.4,117.36 259.61,117.74 261.5,113.68 Z" id="Path" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,51 @@
import React from "react"
import {
BrowserRouter as Router,
Route,
Routes,
RouteProps,
} from "react-router-dom"
import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views"
import { DefaultLayout } from "./layouts"
interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component
from: string
}
const RouteWithHeader = ({ component: Component, ...rest }: Props) => {
return (
<Route
{...rest}
>
<DefaultLayout {...rest}>
<Component />
</DefaultLayout>
</Route>
)
}
export const DisplayRoutes = () => (
<Router>
<Routes>
<Route
path="/"
element={<DefaultLayout from="/">
<HomeView />
</DefaultLayout>} />
<Route path="/error"
element={<ErrorView />} />
<Route
path="/receipts"
element={<DefaultLayout from="/receipts">
<ReceiptsView />
</DefaultLayout>} />
<Route
path="/settings"
element={<DefaultLayout from="/settings">
<CaptureKeyView />
</DefaultLayout>} />
</Routes>
</Router>
)

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
className="material-icons"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
</svg>

After

Width:  |  Height:  |  Size: 347 B

@ -0,0 +1,6 @@
export type ReceiptStatus = "Verified" | "Queue"
export interface Receipt {
guid: string
status: ReceiptStatus
}

@ -0,0 +1 @@
export type ThemeType = "dark" | "light"

@ -0,0 +1,2 @@
export * from "./Receipt"
export * from "./ThemeType"

@ -0,0 +1 @@
export * from "./utilities"

@ -0,0 +1,34 @@
import { PluginClient } from "@remixproject/plugin"
import axios from 'axios'
type RemixClient = PluginClient
export const getEtherScanApi = (network: string) => {
return network === "main"
? `https://api.etherscan.io/api`
: `https://api-${network}.etherscan.io/api`
}
export const getNetworkName = async (client: RemixClient) => {
const network = await client.call("network", "detectNetwork")
if (!network) {
throw new Error("no known network to verify against")
}
const name = network.name!.toLowerCase()
// TODO : remove that when https://github.com/ethereum/remix-ide/issues/2017 is fixe
return name === "görli" ? "goerli" : name
}
export const getReceiptStatus = async (
receiptGuid: string,
apiKey: string,
etherscanApi: string
) => {
const params = `guid=${receiptGuid}&module=contract&action=checkverifystatus&apiKey=${apiKey}`
try {
const response = await axios.get(`${etherscanApi}?${params}`)
const { result } = response.data
return result
} catch (error) {
console.log("Error", error)
}
}

@ -0,0 +1,59 @@
import React from "react"
import { Formik, ErrorMessage, Field } from "formik"
import { useNavigate, useLocation } from "react-router-dom"
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
export const CaptureKeyView: React.FC = () => {
const location = useLocation()
const navigate = useNavigate()
return (
<AppContext.Consumer>
{({ apiKey, setAPIKey }) => (
<Formik
initialValues={{ apiKey }}
validate={(values) => {
const errors = {} as any
if (!values.apiKey) {
errors.apiKey = "Required"
}
return errors
}}
onSubmit={(values) => {
setAPIKey(values.apiKey)
navigate((location.state as any).from)
}}
>
{({ errors, touched, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="form-group" style={{ marginBottom: "0.5rem" }}>
<label htmlFor="apikey">Please Enter your API key</label>
<Field
className={
errors.apiKey && touched.apiKey
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="apiKey"
placeholder="Example: GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/>
<ErrorMessage
className="invalid-feedback"
name="apiKey"
component="div"
/>
</div>
<div>
<SubmitButton text="Save API key" />
</div>
</form>
)}
</Formik>
)}
</AppContext.Consumer>
)
}

@ -0,0 +1,31 @@
import React from "react"
export const ErrorView: React.FC = () => {
return (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<img
style={{ paddingBottom: "2em" }}
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<h5>Sorry, something unexpected happened. </h5>
<h5>
Please raise an issue:{" "}
<a
style={{ color: "red" }}
href="https://github.com/machinalabs/remix-etherscan/issues"
>
Here
</a>
</h5>
</div>
)
}

@ -0,0 +1,36 @@
import React from "react"
import { Navigate } from "react-router-dom"
import { AppContext } from "../AppContext"
import { Receipt } from "../types"
import { VerifyView } from "./VerifyView"
export const HomeView: React.FC = () => {
// const [hasError, setHasError] = useState(false)
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, setReceipts, receipts, contracts }) =>
!apiKey ? (
<Navigate
to={{
pathname: "/settings"
}}
/>
) : (
<VerifyView
contracts={contracts}
client={clientInstance}
apiKey={apiKey}
onVerifiedContract={(receipt: Receipt) => {
const newReceipts = [...receipts, receipt]
setReceipts(newReceipts)
}}
/>
)
}
</AppContext.Consumer>
)
}

@ -0,0 +1,135 @@
import React, { useState } from "react"
import { Formik, ErrorMessage, Field } from "formik"
import { getEtherScanApi, getNetworkName, getReceiptStatus } from "../utils"
import { Receipt } from "../types"
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
import { Navigate } from "react-router-dom"
interface FormValues {
receiptGuid: string
}
export const ReceiptsView: React.FC = () => {
const [results, setResults] = useState("")
const onGetReceiptStatus = async (
values: FormValues,
clientInstance: any,
apiKey: string
) => {
try {
const network = await getNetworkName(clientInstance)
if (network === "vm") {
setResults("Cannot verify in the selected network")
return
}
const etherscanApi = getEtherScanApi(network)
const result = await getReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
setResults(result)
} catch (error: any) {
setResults(error.message)
}
}
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, receipts }) =>
!apiKey ? (
<Navigate
to={{
pathname: "/settings"
}}
/>
) : (
<div>
<Formik
initialValues={{ receiptGuid: "" }}
validate={(values) => {
const errors = {} as any
if (!values.receiptGuid) {
errors.receiptGuid = "Required"
}
return errors
}}
onSubmit={(values) =>
onGetReceiptStatus(values, clientInstance, apiKey)
}
>
{({ errors, touched, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div
className="form-group"
style={{ marginBottom: "0.5rem" }}
>
<h6>Get your Receipt GUID status</h6>
<label htmlFor="receiptGuid">Receipt GUID</label>
<Field
className={
errors.receiptGuid && touched.receiptGuid
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="receiptGuid"
/>
<ErrorMessage
className="invalid-feedback"
name="receiptGuid"
component="div"
/>
</div>
<SubmitButton text="Check" />
</form>
)}
</Formik>
<div
style={{
marginTop: "2em",
fontSize: "0.8em",
textAlign: "center",
}}
dangerouslySetInnerHTML={{ __html: results }}
/>
<ReceiptsTable receipts={receipts} />
</div>
)
}
</AppContext.Consumer>
)
}
const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
return (
<div className="table-responsive" style={{ fontSize: "0.7em" }}>
<h6>Receipts</h6>
<table className="table table-sm">
<thead>
<tr>
<th scope="col">Guid</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
{receipts &&
receipts.length > 0 &&
receipts.map((item: Receipt, index) => {
return (
<tr key={item.guid}>
<td>{item.guid}</td>
<td>{item.status}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}

@ -0,0 +1,302 @@
import React, { useState } from "react"
import {
PluginClient,
} from "@remixproject/plugin"
import { Formik, ErrorMessage, Field } from "formik"
import { getNetworkName, getEtherScanApi, getReceiptStatus } from "../utils"
import { SubmitButton } from "../components"
import { Receipt } from "../types"
import { CompilationResult } from "@remixproject/plugin-api"
import axios from 'axios'
interface Props {
client: PluginClient
apiKey: string
onVerifiedContract: (receipt: Receipt) => void
contracts: string[]
}
interface FormValues {
contractName: string
contractArguments: string
contractAddress: string
}
export const getContractFileName = (
compilationResult: CompilationResult,
contractName: string
) => {
const compiledContracts = compilationResult.contracts
let fileName = ""
for (const file of Object.keys(compiledContracts)) {
for (const contract of Object.keys(compiledContracts[file])) {
if (contract === contractName) {
fileName = file
break
}
}
}
return fileName
}
export const getContractMetadata = (
compilationResult: CompilationResult,
contractName: string
) => {
const compiledContracts = compilationResult.contracts
let contractMetadata = ""
for (const file of Object.keys(compiledContracts)) {
for (const contract of Object.keys(compiledContracts[file])) {
if (contract === contractName) {
contractMetadata = compiledContracts[file][contract].metadata
if (contractMetadata) {
break
}
}
}
}
return contractMetadata
}
export const VerifyView: React.FC<Props> = ({
apiKey,
client,
contracts,
onVerifiedContract,
}) => {
const [results, setResults] = useState("")
const onVerifyContract = async (values: FormValues) => {
const compilationResult = (await client.call(
"solidity",
"getCompilationResult"
)) as any
if (!compilationResult) {
throw new Error("no compilation result available")
}
const contractArguments = values.contractArguments.replace("0x", "")
const verify = async (
apiKeyParam: string,
contractAddress: string,
contractArgumentsParam: string,
contractName: string,
compilationResultParam: any
) => {
const network = await getNetworkName(client)
if (network === "vm") {
return "Cannot verify in the selected network"
}
const etherscanApi = getEtherScanApi(network)
try {
const contractMetadata = getContractMetadata(
compilationResultParam.data,
contractName
)
if (!contractMetadata) {
return "Please recompile contract"
}
const contractMetadataParsed = JSON.parse(contractMetadata)
const fileName = getContractFileName(
compilationResultParam.data,
contractName
)
const jsonInput = {
language: 'Solidity',
sources: compilationResultParam.source.sources,
settings: {
optimizer: {
enabled: contractMetadataParsed.settings.optimizer.enabled,
runs: contractMetadataParsed.settings.optimizer.runs
}
}
}
const data: { [key: string]: string | any } = {
apikey: apiKeyParam, // A valid API-Key is required
module: "contract", // Do not change
action: "verifysourcecode", // Do not change
codeformat: "solidity-standard-json-input",
contractaddress: contractAddress, // Contract Address starts with 0x...
sourceCode: JSON.stringify(jsonInput),
contractname: fileName + ':' + contractName,
compilerversion: `v${contractMetadataParsed.compiler.version}`, // see http://etherscan.io/solcversions for list of support versions
constructorArguements: contractArgumentsParam, // if applicable
}
const body = new FormData()
Object.keys(data).forEach((key) => body.append(key, data[key]))
client.emit("statusChanged", {
key: "loading",
type: "info",
title: "Verifying ...",
})
const response = await axios.post(etherscanApi, body)
const { message, result, status } = await response.data
if (message === "OK" && status === "1") {
resetAfter10Seconds()
const receiptStatus = await getReceiptStatus(
result,
apiKey,
etherscanApi
)
onVerifiedContract({
guid: result,
status: receiptStatus,
})
return `Contract verified correctly <br> Receipt GUID ${result}`
}
if (message === "NOTOK") {
client.emit("statusChanged", {
key: "failed",
type: "error",
title: result,
})
resetAfter10Seconds()
}
return result
} catch (error) {
console.log("Error, something wrong happened", error)
setResults("Something wrong happened, try again")
}
}
const resetAfter10Seconds = () => {
setTimeout(() => {
client.emit("statusChanged", { key: "none" })
setResults("")
}, 10000)
}
const verificationResult = await verify(
apiKey,
values.contractAddress,
contractArguments,
values.contractName,
compilationResult
)
setResults(verificationResult)
}
return (
<div>
<Formik
initialValues={{
contractName: "",
contractArguments: "",
contractAddress: "",
}}
validate={(values) => {
const errors = {} as any
if (!values.contractName) {
errors.contractName = "Required"
}
if (!values.contractAddress) {
errors.contractAddress = "Required"
}
if (values.contractAddress.trim() === "") {
errors.contractAddress = "Please enter a valid contract address"
}
return errors
}}
onSubmit={(values) => onVerifyContract(values)}
>
{({ errors, touched, handleSubmit, isSubmitting }) => (
<form onSubmit={handleSubmit}>
<div className="form-group">
<h6>Verify your smart contracts</h6>
<label htmlFor="contractName">Contract</label>
<Field
as="select"
className={
errors.contractName && touched.contractName
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
name="contractName"
>
<option disabled={true} value="">
Select a contract
</option>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</Field>
<ErrorMessage
className="invalid-feedback"
name="contractName"
component="div"
/>
</div>
<div className="form-group">
<label htmlFor="contractArguments">Constructor Arguments</label>
<Field
className={
errors.contractArguments && touched.contractArguments
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="contractArguments"
placeholder="hex encoded"
/>
<ErrorMessage
className="invalid-feedback"
name="contractArguments"
component="div"
/>
</div>
<div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<Field
className={
errors.contractAddress && touched.contractAddress
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="contractAddress"
placeholder="i.e. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
<ErrorMessage
className="invalid-feedback"
name="contractAddress"
component="div"
/>
</div>
<SubmitButton text="Verify Contract" isSubmitting={isSubmitting} />
</form>
)}
</Formik>
<div
style={{ marginTop: "2em", fontSize: "0.8em", textAlign: "center" }}
dangerouslySetInnerHTML={{ __html: results }}
/>
{/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}>
<Link to="/receipts">View Receipts</Link>
</div> */}
</div>
)
}

@ -0,0 +1,4 @@
export { HomeView } from "./HomeView"
export { ErrorView } from "./ErrorView"
export { ReceiptsView } from "./ReceiptsView"
export { CaptureKeyView } from "./CaptureKeyView"

@ -0,0 +1,3 @@
export const environment = {
production: true
};

@ -0,0 +1,6 @@
// This file can be replaced during build by using the `fileReplacements` array.
// When building for production, this file is replaced with `environment.prod.ts`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Etherscan</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,7 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import App from './app/app';
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root'));

@ -0,0 +1,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -0,0 +1,20 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

@ -198,6 +198,9 @@
},
"vyper": {
"tags": []
},
"etherscan": {
"tags": []
}
},
"targetDependencies": {

@ -182,6 +182,7 @@
"file-path-filter": "^3.0.2",
"file-saver": "^2.0.5",
"form-data": "^4.0.0",
"formik": "^2.2.9",
"fs-extra": "^3.0.1",
"html-react-parser": "^1.3.0",
"http-server": "^0.11.1",
@ -201,6 +202,7 @@
"react-dom": "^17.0.2",
"react-draggable": "^4.4.4",
"react-json-view": "^1.21.3",
"react-router-dom": "^6.3.0",
"react-tabs": "^3.2.2",
"regenerator-runtime": "0.13.7",
"rss-parser": "^3.12.0",

@ -1566,7 +1566,88 @@
"linter": "eslint",
"config": "apps/vyper/.eslintrc",
"files": [
"apps/vyper/src/**/*.js", "apps/vyper/src/**/*.ts"
"apps/vyper/src/**/*.js",
"apps/vyper/src/**/*.ts"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"etherscan": {
"root": "apps/etherscan",
"sourceRoot": "apps/etherscan/src",
"projectType": "application",
"architect": {
"build": {
"builder": "@nrwl/web:build",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/apps/etherscan",
"index": "apps/etherscan/src/index.html",
"main": "apps/etherscan/src/main.tsx",
"polyfills": "apps/etherscan/src/polyfills.ts",
"tsConfig": "apps/etherscan/tsconfig.app.json",
"assets": [
"apps/etherscan/src/favicon.ico",
"apps/etherscan/src/assets"
],
"styles": [
"apps/etherscan/src/styles.css"
],
"scripts": [],
"webpackConfig": "@nrwl/react/plugins/webpack"
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "apps/etherscan/src/environments/environment.ts",
"with": "apps/etherscan/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
}
]
}
}
},
"serve": {
"builder": "@nrwl/web:dev-server",
"options": {
"buildTarget": "etherscan:build",
"port": 5003
},
"configurations": {
"production": {
"buildTarget": "etherscan:build:production",
"hmr": false
}
}
},
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"config": "apps/vyper/.eslintrc",
"files": [
"apps/vyper/src/**/*.js",
"apps/vyper/src/**/*.ts"
],
"exclude": [
"**/node_modules/**"

@ -1622,6 +1622,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.7.6":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@7.0.0-beta.53":
version "7.0.0-beta.53"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.53.tgz#3322290900d0b187b0a7174381e1f3bb71050d2e"
@ -9136,6 +9143,11 @@ deep-is@~0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
deepmerge@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
@ -11250,6 +11262,19 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formik@^2.2.9:
version "2.2.9"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0"
integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==
dependencies:
deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0"
lodash "^4.17.21"
lodash-es "^4.17.21"
react-fast-compare "^2.0.1"
tiny-warning "^1.0.2"
tslib "^1.10.0"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -12331,6 +12356,13 @@ header-case@^2.0.4:
capital-case "^1.0.4"
tslib "^2.0.3"
history@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b"
integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==
dependencies:
"@babel/runtime" "^7.7.6"
hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -15292,6 +15324,11 @@ lockfile@~1.0.3:
resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.3.tgz#2638fc39a0331e9cac1a04b71799931c9c50df79"
integrity sha1-Jjj8OaAzHpysGgS3F5mTHJxQ33k=
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash._arraycopy@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1"
@ -19584,6 +19621,11 @@ react-draggable@^4.4.4:
clsx "^1.1.1"
prop-types "^15.6.0"
react-fast-compare@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -19645,6 +19687,21 @@ react-refresh@^0.9.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf"
integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==
react-router-dom@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d"
integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==
dependencies:
history "^5.2.0"
react-router "6.3.0"
react-router@6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557"
integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==
dependencies:
history "^5.2.0"
react-tabs@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-3.2.2.tgz#07bdc3cdb17bdffedd02627f32a93cd4b3d6e4d0"
@ -22487,6 +22544,11 @@ tiny-invariant@^1.0.6:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tmp@0.0.33, tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"

Loading…
Cancel
Save