diff --git a/.gitignore b/.gitignore index 20a4770..3358ab1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* .log + +.env* \ No newline at end of file diff --git a/README.md b/README.md index 7a88033..c327009 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# cøsmwasm devtøøls +# Overview -a tool for interacting with cosmwasm contracts. online at https://cosmwasm.tools +originally: cøsmwasm devtøøls by aswever, but due to a stale repo, I've forked it and added some additionals tools. More to come! ## how to use diff --git a/craco.config.js b/craco.config.js index 75fff42..8d659eb 100644 --- a/craco.config.js +++ b/craco.config.js @@ -1,5 +1,8 @@ const { ProvidePlugin } = require("webpack"); module.exports = { + devServer: { + port: 3001 + }, webpack: { configure: (webpackConfig) => { return { diff --git a/dist/output.css b/dist/output.css new file mode 100644 index 0000000..bb4f1e9 --- /dev/null +++ b/dist/output.css @@ -0,0 +1,775 @@ +body, +pre { + font-family: "Courier New", Courier, monospace; +} + +.main { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.header, +.subhead { + font-family: "Courier New", Courier, monospace; + text-align: center; + font-weight: normal; + margin-bottom: 0; +} + +.subhead { + font-size: 0.9em; +} + +.subhead a { + color: slateblue; + text-decoration: none; +} + +sl-divider { + margin-bottom: 0; +} + +.sidebar { + font-family: Arial, Helvetica, sans-serif; + width: 20%; + height: 100%; + position: fixed; + top: 0; + left: 0; + overflow-x: hidden; + border-right: 1px solid #ccc; + z-index: 1; + display: flex; + flex-direction: column; +} + +.sidebar-main { + display: flex; + flex-direction: column; + overflow-y: auto; + overflow-x: hidden; + flex-grow: 1; +} + +.connection { + display: flex; + flex-direction: column; + align-items: center; +} + +.console { + width: 80%; + height: 100%; + position: fixed; + top: 0; + right: 0; + overflow-x: hidden; + z-index: 0; +} + +/* +! tailwindcss v3.0.19 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + /* 3 */ + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input:-ms-input-placeholder, textarea:-ms-input-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* +Ensure the default browser behavior of the `hidden` attribute. +*/ + +[hidden] { + display: none; +} + +*, ::before, ::after { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.fixed { + position: fixed; +} + +.relative { + position: relative; +} + +.m-auto { + margin: auto; +} + +.m-0 { + margin: 0px; +} + +.my-auto { + margin-top: auto; + margin-bottom: auto; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.mt-auto { + margin-top: auto; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.flex { + display: flex; +} + +.h-full { + height: 100%; +} + +.h-\[64px\] { + height: 64px; +} + +.h-\[42px\] { + height: 42px; +} + +.h-max { + height: -webkit-max-content; + height: max-content; +} + +.w-full { + width: 100%; +} + +.w-1\/2 { + width: 50%; +} + +.w-\[12px\] { + width: 12px; +} + +.w-\[2em\] { + width: 2em; +} + +.w-5\/6 { + width: 83.333333%; +} + +.w-2\/3 { + width: 66.666667%; +} + +.flex-none { + flex: none; +} + +.grow { + flex-grow: 1; +} + +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.gap-4 { + gap: 1rem; +} + +.gap-1 { + gap: 0.25rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.border { + border-width: 1px; +} + +.p-\[10px\] { + padding: 10px; +} + +.p-4 { + padding: 1rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-\[10px\] { + padding-top: 10px; + padding-bottom: 10px; +} + +.py-16 { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.pt-4 { + padding-top: 1rem; +} + +.pb-8 { + padding-bottom: 2rem; +} + +.pt-8 { + padding-top: 2rem; +} + +.pt-2 { + padding-top: 0.5rem; +} + +.pl-\[4px\] { + padding-left: 4px; +} + +.pl-\[5px\] { + padding-left: 5px; +} + +.pb-4 { + padding-bottom: 1rem; +} + +.pb-2 { + padding-bottom: 0.5rem; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.font-medium { + font-weight: 500; +} + +.font-bold { + font-weight: 700; +} + +.text-slate-500 { + --tw-text-opacity: 1; + color: rgb(100 116 139 / var(--tw-text-opacity)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.text-slate-800 { + --tw-text-opacity: 1; + color: rgb(30 41 59 / var(--tw-text-opacity)); +} + +.text-slate-400 { + --tw-text-opacity: 1; + color: rgb(148 163 184 / var(--tw-text-opacity)); +} + +.filter { + -webkit-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} \ No newline at end of file diff --git a/package.json b/package.json index a338a5f..8ff0686 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,14 @@ "version": "0.1.0", "private": true, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.27.1", - "@cosmjs/faucet-client": "^0.27.1", + "@cosmjs/amino": "^0.30.1", + "@cosmjs/cosmwasm-stargate": "^0.29.5", + "@cosmjs/encoding": "^0.30.1", + "@cosmjs/faucet-client": "^0.29.5", "@cosmjs/launchpad": "^0.27.1", - "@cosmjs/proto-signing": "^0.27.1", + "@cosmjs/proto-signing": "^0.30.1", + "@cosmjs/tendermint-rpc": "^0.29.5", + "@keplr-wallet/cosmos": "^0.11.56", "@reduxjs/toolkit": "^1.7.2", "@shoelace-style/shoelace": "^2.0.0-beta.64", "@testing-library/jest-dom": "^4.2.4", @@ -17,15 +21,22 @@ "@types/react": "^16.14.23", "@types/react-dom": "^16.9.14", "@types/react-redux": "^7.1.22", + "chain-registry": "^1.10.0", + "cosmjs-types": "^0.7.2", "crypto-browserify": "^3.12.0", "json-format-highlight": "^1.0.4", "path-browserify": "^1.0.1", + "primeicons": "^6.0.1", + "primereact": "^9.2.1", "react": "^17.0.2", + "react-copy-to-clipboard": "^5.1.0", "react-dom": "^17.0.2", "react-json-view": "^1.21.3", "react-redux": "^7.2.6", + "react-router-dom": "^6.10.0", "react-scripts": "5.0.0", "react-simple-code-editor": "^0.11.0", + "react-tooltip": "^5.11.1", "redux-persist": "^6.0.0", "stream-browserify": "^3.0.0", "typescript": "~4.1.5" @@ -34,7 +45,8 @@ "start": "GENERATE_SOURCEMAP=false craco start", "build": "craco build", "test": "craco test", - "eject": "craco eject" + "eject": "craco eject", + "tailwind": "npx tailwindcss -i ./src/App.css -o ./dist/output.css --watch" }, "eslintConfig": { "extends": "react-app" diff --git a/public/index.html b/public/index.html index 01d6b2e..ccee6c0 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,7 @@ + - cosmwasm devtools + Interchain Toolbox diff --git a/src/App.css b/src/App.css index fba18aa..5626faf 100644 --- a/src/App.css +++ b/src/App.css @@ -50,8 +50,9 @@ sl-divider { .sidebar-main { display: flex; flex-direction: column; - overflow: scroll; - flex-grow: 1; + overflow-y: auto; + overflow-x: hidden; + border-top: solid 1px #ccc; } .connection { @@ -69,3 +70,11 @@ sl-divider { overflow-x: hidden; z-index: 0; } + +.p-accordion-header-link { + height: 32px; +} + +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/src/App.test.tsx b/src/App.test.tsx deleted file mode 100644 index 659cc13..0000000 --- a/src/App.test.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { store } from './app/store'; -import App from './App'; - -test('renders learn react link', () => { - const { getByText } = render( - - - - ); - - expect(getByText(/learn/i)).toBeInTheDocument(); -}); diff --git a/src/App.tsx b/src/App.tsx index 1883d30..0351b74 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,45 +1,107 @@ -import React from "react"; import "./App.css"; import { AccountList } from "./features/accounts/AccountList"; import "@shoelace-style/shoelace/dist/themes/light.css"; import { setBasePath } from "@shoelace-style/shoelace/dist/utilities/base-path"; import { ContractList } from "./features/accounts/ContractList"; import { Console } from "./features/console/Console"; -import { SlDivider } from "@shoelace-style/shoelace/dist/react"; import { Connection } from "./features/connection/Connection"; import { Configuration } from "./features/connection/Configuration"; import { Messages } from "./features/messages/Messages"; import { ExecuteOptions } from "./features/console/ExecuteOptions"; import { Header } from "./components/Header"; import { Donate } from "./features/accounts/Donate"; +import { InstantiateOptions } from "./features/console/InstantiateOptions"; +import { Menu } from "primereact/menu"; +import { BechConverter } from "./features/tools/bechconverter"; +import { MsgSigner } from "./features/tools/msgsigner"; +import { Routes, Route, useNavigate, useLocation } from "react-router-dom"; setBasePath( - "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.64/dist/" + "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.64/dist/" ); function App() { - return ( -
- -
- -
- - - - -
- ); + ); } export default App; diff --git a/src/app/store.ts b/src/app/store.ts index 9c85870..e412543 100644 --- a/src/app/store.ts +++ b/src/app/store.ts @@ -6,10 +6,13 @@ import { } from "@reduxjs/toolkit"; import { persistStore, persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; +import appReducer from "../features/app/appSlice"; import accountsReducer from "../features/accounts/accountsSlice"; import connectionReducer from "../features/connection/connectionSlice"; import messagesReducer from "../features/messages/messagesSlice"; import consoleReducer from "../features/console/consoleSlice"; +import toolboxReducer from "../features/console/toolboxSlice"; +import signingReducer from "../features/tools/signingSlice"; const persistConfig = { key: "root", @@ -19,10 +22,13 @@ const persistConfig = { const reducer = persistReducer( persistConfig, combineReducers({ + appState: appReducer, accounts: accountsReducer, connection: connectionReducer, messages: messagesReducer, console: consoleReducer, + toolbox: toolboxReducer, + signing: signingReducer }) ); diff --git a/src/components/Header.module.css b/src/components/Header.module.css index 60484ae..50b1d7c 100644 --- a/src/components/Header.module.css +++ b/src/components/Header.module.css @@ -1,6 +1,14 @@ .header { } +.subtext { + font-family: "Courier New", Courier, monospace; + text-align: center; + font-weight: normal; + margin-bottom: 0; + font-size: 0.7em; +} + .name, .subhead { font-family: "Courier New", Courier, monospace; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index be56c1d..6cb97b4 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,36 +1,16 @@ -import { SlDivider } from "@shoelace-style/shoelace/dist/react"; -import React from "react"; import { useAppDispatch } from "../app/hooks"; -import { AccountList } from "../features/accounts/AccountList"; import { setDonationOpen } from "../features/accounts/accountsSlice"; -import { ContractList } from "../features/accounts/ContractList"; -import { Connection } from "../features/connection/Connection"; import styles from "./Header.module.css"; -const GITHUB_URL = "https://github.com/aswever/cosmwasm-devtools"; - export const Header = () => { - const dispatch = useAppDispatch(); - return ( -
-

cøsmwasm devtøøls

-
- - github - {" "} - |{" "} - -
-
- ); + const dispatch = useAppDispatch(); + return ( +
+ + Interchain Toolbox + +
+ ); }; diff --git a/src/features/accounts/AccountCard.tsx b/src/features/accounts/AccountCard.tsx index d4eda9a..ee86e4b 100644 --- a/src/features/accounts/AccountCard.tsx +++ b/src/features/accounts/AccountCard.tsx @@ -2,135 +2,162 @@ import { SlCard, SlIcon, SlTooltip } from "@shoelace-style/shoelace/dist/react"; import React, { FC, useCallback, useEffect, useState } from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { - Account, - AccountType, - balanceString, - checkBalance, - hitFaucet, - setSendCoinsOpen, + Account, + AccountType, + balanceString, + checkBalance, + hitFaucet, + setSendCoinsOpen, } from "./accountsSlice"; import styles from "./AccountCard.module.css"; interface AccountCardProps { - icon?: JSX.Element; - label: string; - account?: Account; - selected: boolean; - disabled?: boolean; - onClick: () => void; - onClickX: () => void; - onConfigChange?: () => void; + icon?: JSX.Element; + label: string; + account?: Account; + selected: boolean; + disabled?: boolean; + onClick: () => void; + onClickX: () => void; + onConfigChange?: () => void; } export const CHECK_BALANCE_INTERVAL = 30000; export const AccountCard: FC = ({ - icon, - label, - account, - selected, - disabled, - onClick, - onClickX, - onConfigChange, + icon, + label, + account, + selected, + disabled, + onClick, + onClickX, + onConfigChange, }) => { - const dispatch = useAppDispatch(); - const config = useAppSelector((state) => state.connection.config); - const balance = useAppSelector(balanceString(account?.address)); - const [copyTooltip, setCopyTooltip] = useState("Copy to clipboard"); + const dispatch = useAppDispatch(); + const config = useAppSelector((state) => state.connection.config); + const balance = useAppSelector(balanceString(account?.address)); + const [copyTooltip, setCopyTooltip] = useState("Copy to clipboard"); - const classes = [styles.accountCard]; - if (selected) { - classes.push(styles.selected); - } else if (disabled) { - classes.push(styles.disabled); - } + const classes = [styles.accountCard]; + if (selected) { + classes.push(styles.selected); + } else if (disabled) { + classes.push(styles.disabled); + } - useEffect(() => { - if (onConfigChange) onConfigChange(); - }, [config, onConfigChange]); + useEffect(() => { + if (onConfigChange) onConfigChange(); + }, [config, onConfigChange]); - useEffect(() => { - if (account?.address) { - dispatch(checkBalance(account.address)); - const interval = setInterval( - () => dispatch(checkBalance(account.address)), - CHECK_BALANCE_INTERVAL - ); - return () => clearInterval(interval); - } - }, [account?.address, dispatch, config]); + const [lastTimestamp, setLastTimestamp] = useState>( + new Map() + ); + + useEffect(() => { + if (account?.address && account?.address !== undefined) { + const lastTs = lastTimestamp.get(account?.address); + + if (lastTs !== undefined) { + if (Date.now() - lastTs <= 60000) { + return; + } + } + + updateTimestampMap(account.address, Date.now()); + dispatch(checkBalance(account.address)); + } + }, [account?.address]); + + const updateTimestampMap = (k: string, v: number) => { + setLastTimestamp(new Map(lastTimestamp.set(k, v))); + }; - const copyAddress = useCallback(() => { - if (account?.address) navigator.clipboard.writeText(account.address); - setCopyTooltip("Copied!"); - setTimeout(() => setCopyTooltip("Copy to clipboard"), 2000); - }, [account?.address]); + const copyAddress = useCallback(() => { + if (account?.address) navigator.clipboard.writeText(account.address); + setCopyTooltip("Copied!"); + setTimeout(() => setCopyTooltip("Copy to clipboard"), 2000); + }, [account?.address]); - const clickX = useCallback( - (e: React.MouseEvent) => { - e.preventDefault(); - onClickX(); - }, - [onClickX] - ); + const clickX = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + onClickX(); + }, + [onClickX] + ); - return ( - <> - -
-
- {icon} -
{label}
-
- {account && account.type !== AccountType.Contract && selected && ( - <> - {config["faucetEndpoint"] && ( - - dispatch(hitFaucet(account.address))} - /> - - )} - - dispatch(setSendCoinsOpen(true))} - /> - - - )} - {account && ( - + - - - )} -
- {account && selected && ( -
- -
copyAddress()}> - {account.address} -
-
-
{balance}
-
- )} -
- - ); +
+
+ {icon} +
{label}
+
+ {account && + account.type !== AccountType.Contract && + selected && ( + <> + {config["faucetEndpoint"] && ( + + + dispatch( + hitFaucet(account.address) + ) + } + /> + + )} + + + dispatch(setSendCoinsOpen(true)) + } + /> + + + )} + {account && ( + + + + )} +
+ {account && selected && ( +
+ +
copyAddress()} + > + {account.address} +
+
+
{balance}
+
+ )} + + + ); }; diff --git a/src/features/accounts/AccountList.module.css b/src/features/accounts/AccountList.module.css index 4c6c4e7..0baf1e6 100644 --- a/src/features/accounts/AccountList.module.css +++ b/src/features/accounts/AccountList.module.css @@ -1,5 +1,4 @@ .section { - margin: 1rem 0; display: flex; flex-direction: column; align-items: center; @@ -7,6 +6,4 @@ .header { font-weight: bold; - margin: 0.5rem; - margin-left: 1rem; } diff --git a/src/features/accounts/ContractList.module.css b/src/features/accounts/ContractList.module.css index 896d221..efd8d3e 100644 --- a/src/features/accounts/ContractList.module.css +++ b/src/features/accounts/ContractList.module.css @@ -1,5 +1,4 @@ .section { - margin: 1rem 0; display: flex; flex-direction: column; align-items: center; @@ -7,13 +6,10 @@ .header { font-weight: bold; - margin: 0.5rem; - margin-left: 1rem; } .address { width: 90%; - margin: 0.2rem 0.5rem; cursor: pointer; } diff --git a/src/features/accounts/ContractList.tsx b/src/features/accounts/ContractList.tsx index 1231653..0c11138 100644 --- a/src/features/accounts/ContractList.tsx +++ b/src/features/accounts/ContractList.tsx @@ -1,53 +1,86 @@ -import React, { FC, useCallback } from "react"; +import React, { FC, useCallback, useState } from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { - checkContract, - contractAccounts, - deleteAccount, - selectContract, + checkContract, + contractAccounts, + deleteAccount, + selectContract, } from "./accountsSlice"; import styles from "./ContractList.module.css"; import { AddContract } from "./AddContract"; import { AccountCard } from "./AccountCard"; import { Contract } from "../accounts/accountsSlice"; +import { setForceRefresh } from "../connection/connectionSlice"; interface ContractProps { - contract: Contract; + contract: Contract; } export const ContractDetails: FC = ({ contract }) => { - const dispatch = useAppDispatch(); - const selected = useAppSelector( - (state) => state.accounts.currentContract === contract.address - ); - - const check = useCallback( - () => dispatch(checkContract(contract)), - [dispatch, contract] - ); - - return ( - dispatch(selectContract(contract.address))} - onClickX={() => dispatch(deleteAccount(contract.address))} - onConfigChange={check} - /> - ); + const dispatch = useAppDispatch(); + const selected = useAppSelector( + (state) => state.accounts.currentContract === contract.address + ); + const forceRefresh = useAppSelector((state) => state.connection.forceRefresh); + + const [lastTimestamp, setLastTimestamp] = useState>( + new Map() + ); + + const [recheckCtr, setRecheckCtr] = useState(0); + + const check = useCallback(() => { + const updateTimestampMap = (k: string, v: number) => { + setLastTimestamp(new Map(lastTimestamp.set(k, v))); + }; + + if (forceRefresh) { + dispatch(setForceRefresh(false)) + setLastTimestamp(new Map()) + setRecheckCtr(recheckCtr + 1); + } + + const addr = contract.address; + if (addr !== "") { + const lastTs = lastTimestamp.get(addr); + + if (lastTs !== undefined) { + if (Date.now() - lastTs <= 60000) { + return; + } + } + + updateTimestampMap(addr, Date.now()); + dispatch(checkContract(contract)); + } + + setTimeout(() => { + setRecheckCtr(recheckCtr + 1); + }, 1000); + }, [contract, dispatch, lastTimestamp, recheckCtr, forceRefresh]); + + return ( + dispatch(selectContract(contract.address))} + onClickX={() => dispatch(deleteAccount(contract.address))} + onConfigChange={check} + /> + ); }; export const ContractList: FC = () => { - const contracts = useAppSelector(contractAccounts); - return ( -
-
Contracts
- {Object.values(contracts).map((contract) => ( - - ))} - -
- ); + const contracts = useAppSelector(contractAccounts); + return ( +
+
Contracts
+ {Object.values(contracts).map((contract) => ( + + ))} + +
+ ); }; diff --git a/src/features/accounts/accountsSlice.ts b/src/features/accounts/accountsSlice.ts index b87cfc9..f3d79e1 100644 --- a/src/features/accounts/accountsSlice.ts +++ b/src/features/accounts/accountsSlice.ts @@ -1,527 +1,594 @@ import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { coin, Coin, DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { + coin, + Coin, + coins, + DirectSecp256k1HdWallet, +} from "@cosmjs/proto-signing"; import { RootState } from "../../app/store"; import { Contract as CosmWasmContract } from "@cosmjs/cosmwasm-stargate"; import { fromMicroCoin, toMicroAmount } from "../../util/coins"; import { pushMessage } from "../messages/messagesSlice"; import { FaucetClient } from "@cosmjs/faucet-client"; import connectionManager from "../connection/connectionManager"; +import { setInstantiateOptions } from "../console/consoleSlice"; export enum AccountType { - Basic, - Keplr, - Contract, + Basic, + Keplr, + Contract, } export interface BaseAccount { - type: AccountType; - label: string; - address: string; - balance: Coin; + type: AccountType; + label?: string; + address: string; + balance: Coin; } export interface KeplrAccount extends BaseAccount { - type: AccountType.Keplr; + type: AccountType.Keplr; } export interface BasicAccount extends BaseAccount { - type: AccountType.Basic; - mnemonic: string; + type: AccountType.Basic; + mnemonic: string; } export interface Contract extends BaseAccount { - type: AccountType.Contract; - contract: CosmWasmContract; - exists: boolean; + type: AccountType.Contract; + contract?: CosmWasmContract; + exists: boolean; } export type Account = KeplrAccount | BasicAccount | Contract; export interface AccountsState { - accountList: { [key: string]: Account }; - keplrAccount?: Account; - currentAccount?: string; - currentContract?: string; - sendCoinsOpen: boolean; - donationOpen: boolean; - importContractOpen: boolean; + accountList: { [key: string]: Account }; + keplrAccount?: Account; + currentAccount?: string; + currentContract?: string; + sendCoinsOpen: boolean; + donationOpen: boolean; + importContractOpen: boolean; } const initialState: AccountsState = { - accountList: {}, - sendCoinsOpen: false, - donationOpen: false, - importContractOpen: false, + accountList: {}, + sendCoinsOpen: false, + donationOpen: false, + importContractOpen: false, }; export const importAccount = createAsyncThunk( - "accounts/import", - async ( - { - label, - mnemonic, - }: { - label: string; - mnemonic: string; - }, - { getState, dispatch } - ): Promise => { - const state = getState() as RootState; - const config = state.connection.config; - try { - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { - prefix: config["addressPrefix"], - }); - const [{ address }] = await wallet.getAccounts(); - - return { - label, - mnemonic, - address, - type: AccountType.Basic, - balance: coin(0, config["microDenom"]), - }; - } catch (e) { - dispatch( - pushMessage({ - status: "danger", - header: "Failed to add account", - message: e instanceof Error ? e.message : JSON.stringify(e), - }) - ); - - throw e; + "accounts/import", + async ( + { + label, + mnemonic, + }: { + label: string; + mnemonic: string; + }, + { getState, dispatch } + ): Promise => { + const state = getState() as RootState; + const config = state.connection.config; + try { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic( + mnemonic, + { + prefix: config["addressPrefix"], + } + ); + const [{ address }] = await wallet.getAccounts(); + + return { + label, + mnemonic, + address, + type: AccountType.Basic, + balance: coin(0, config["microDenom"]), + }; + } catch (e) { + dispatch( + pushMessage({ + status: "danger", + header: "Failed to add account", + message: e instanceof Error ? e.message : JSON.stringify(e), + }) + ); + + throw e; + } } - } ); export const checkContract = createAsyncThunk( - "accounts/checkContract", - async ( - account: Contract, - { getState } - ): Promise | null> => { - const state = getState() as RootState; - const config = state.connection.config; - const client = await connectionManager.getQueryClient(config); - try { - const contract = await client.getContract(account.address); - const { label } = contract; - - if (label !== account.label || !account.exists) { - return { - label, - exists: true, - }; - } - - return null; - } catch (e) { - return { - exists: false, - }; + "accounts/checkContract", + async ( + account: Contract, + { getState } + ): Promise | null> => { + const state = getState() as RootState; + const config = state.connection.config; + const conn = await connectionManager.getQueryClient(config); + try { + const contract = await conn?.client?.wasm.getContractInfo( + account.address + ); + const label = contract?.contractInfo?.label; + + if (label !== account.label || !account.exists) { + return { + label, + exists: true, + }; + } + + return null; + } catch (e) { + return { + exists: false, + }; + } } - } ); export const importContract = createAsyncThunk( - "accounts/importContract", - async (address: string, { getState, dispatch }): Promise => { - const state = getState() as RootState; - const config = state.connection.config; - const client = await connectionManager.getQueryClient(config); - try { - const contract = await client.getContract(address); - const { label } = contract; - - dispatch(setImportContractOpen(false)); - - return { - type: AccountType.Contract, - address, - label, - contract, - balance: coin(0, config["microDenom"]), - exists: true, - }; - } catch (e) { - dispatch( - pushMessage({ - status: "danger", - header: "Failed to add contract", - message: e instanceof Error ? e.message : JSON.stringify(e), - }) - ); - - throw e; + "accounts/importContract", + async (address: string, { getState, dispatch }): Promise => { + const state = getState() as RootState; + const config = state.connection.config; + const conn = await connectionManager.getQueryClient(config); + try { + const contract = await conn?.client?.wasm.getContractInfo(address); + const label = contract?.contractInfo?.label; + + dispatch(setImportContractOpen(false)); + + return { + type: AccountType.Contract, + address, + label, + contract: { + address: contract?.address ?? "", + codeId: Number(contract?.contractInfo?.codeId) ?? 0, + creator: contract?.contractInfo?.creator ?? "", + admin: contract?.contractInfo?.admin, + label: contract?.contractInfo?.label ?? "", + ibcPortId: contract?.contractInfo?.ibcPortId, + }, + balance: coin(0, config["microDenom"]), + exists: true, + }; + } catch (e) { + dispatch( + pushMessage({ + status: "danger", + header: "Failed to add contract", + message: e instanceof Error ? e.message : JSON.stringify(e), + }) + ); + + throw e; + } } - } ); export const uploadContract = createAsyncThunk( - "accounts/uploadContract", - async ( - { - address, - wasm, - label, - instantiateMsg, - }: { - address: string; - wasm: Uint8Array; - label: string; - instantiateMsg: Record; - }, - { getState, dispatch } - ): Promise => { - const state = getState() as RootState; - const config = state.connection.config; - const account = state.accounts.accountList[address]; - - const client = await connectionManager.getSigningClient(account, config); - - dispatch( - pushMessage({ - status: "neutral", - message: "Uploading contract...", - }) - ); - - try { - const { codeId } = await client.upload(address, wasm, "auto"); - - dispatch( - pushMessage({ - status: "success", - header: "Contract uploaded successfully", - message: `Stored at codeId ${codeId}`, - }) - ); - - dispatch(instantiateContract({ address, codeId, label, instantiateMsg })); - } catch (e) { - dispatch( - pushMessage({ - status: "danger", - header: "Failed to upload contract", - message: e instanceof Error ? e.message : JSON.stringify(e), - }) - ); - - throw e; + "accounts/uploadContract", + async ( + { + address, + wasm, + label, + instantiateMsg, + }: { + address: string; + wasm: Uint8Array; + label: string; + instantiateMsg?: Record; + }, + { getState, dispatch } + ): Promise => { + const state = getState() as RootState; + const config = state.connection.config; + const account = state.accounts.accountList[address]; + + const client = await connectionManager.getSigningClient( + account, + config + ); + + dispatch( + pushMessage({ + status: "neutral", + message: "Uploading contract...", + }) + ); + + try { + const { codeId } = await client.upload(address, wasm, "auto"); + + dispatch( + pushMessage({ + status: "success", + header: "Contract uploaded successfully", + message: `Stored at codeId ${codeId}`, + }) + ); + + dispatch( + instantiateContract({ address, codeId, label, instantiateMsg }) + ); + } catch (e) { + dispatch( + pushMessage({ + status: "danger", + header: "Failed to upload contract", + message: e instanceof Error ? e.message : JSON.stringify(e), + }) + ); + + throw e; + } } - } ); -export const instantiateContract = createAsyncThunk( - "accounts/instantiateContract", - async ( - { - address, - codeId, - label, - instantiateMsg, - }: { - address: string; - codeId: number; - label: string; - instantiateMsg: Record; - }, - { getState, dispatch } - ): Promise => { - const state = getState() as RootState; - const config = state.connection.config; - const account = state.accounts.accountList[address]; - const client = await connectionManager.getSigningClient(account, config); - dispatch( - pushMessage({ - status: "neutral", - message: "Instantiating contract...", - }) - ); - try { - const { contractAddress } = await client.instantiate( - address, - codeId, - instantiateMsg, - label, - "auto" - ); - - dispatch( - pushMessage({ - status: "success", - header: "Contract instantiated successfully", - message: `Contract address: ${contractAddress}`, - }) - ); - - dispatch(importContract(contractAddress)); - } catch (e) { - dispatch( - pushMessage({ - status: "danger", - header: "Failed to instantiate contract", - message: e instanceof Error ? e.message : JSON.stringify(e), - }) - ); - - throw e; +interface IInstantiateOptions { + memo?: string; + funds?: Coin[]; + admin?: string; +} + +export const instantiateContract = createAsyncThunk( + "accounts/instantiateContract", + async ( + { + address, + codeId, + label, + instantiateMsg, + memo, + funds, + admin, + }: { + address: string; + codeId: number; + label: string; + instantiateMsg?: Record; + memo?: string; + funds?: string; + admin?: string; + }, + { getState, dispatch } + ): Promise => { + const state = getState() as RootState; + const config = state.connection.config; + const account = state.accounts.accountList[address]; + const client = await connectionManager.getSigningClient( + account, + config + ); + dispatch( + pushMessage({ + status: "neutral", + message: "Instantiating contract...", + }) + ); + + try { + instantiateMsg = JSON.parse(state.console.input); + // memo? funds? admin? + let options: IInstantiateOptions | undefined = undefined; + + if (memo || funds || admin) { + options = {} as IInstantiateOptions; + options.memo = memo; + options.funds = funds + ? coins( + toMicroAmount(funds, config["coinDecimals"]), + config["microDenom"] + ) + : undefined; + options.admin = admin; + } + + const { contractAddress } = await client.instantiate( + address, + codeId, + instantiateMsg, + label, + "auto", + options + ); + + dispatch( + pushMessage({ + status: "success", + header: "Contract instantiated successfully", + message: `Contract address: ${contractAddress}`, + }) + ); + + dispatch(setInstantiateOptions(undefined)); + + dispatch(importContract(contractAddress)); + } catch (e) { + dispatch( + pushMessage({ + status: "danger", + header: "Failed to instantiate contract", + message: e instanceof Error ? e.message : JSON.stringify(e), + }) + ); + + throw e; + } } - } ); export const checkBalance = createAsyncThunk( - "accounts/checkBalance", - async (address: string, { getState }): Promise => { - const state = getState() as RootState; - const config = state.connection.config; - const denom: string = config["microDenom"]; - const client = await connectionManager.getQueryClient(config); - return client.getBalance(address, denom); - } + "accounts/checkBalance", + async (address: string, { getState }): Promise => { + const state = getState() as RootState; + const config = state.connection.config; + const denom: string = config["microDenom"]; + const conn = await connectionManager.getQueryClient(config); + const balance = await conn?.bankQueryService?.Balance({ + address, + denom, + }); + const coin: Coin = { + amount: balance?.balance?.amount ?? "0", + denom: balance?.balance?.denom ?? "", + }; + return coin; + } ); export const hitFaucet = createAsyncThunk( - "accounts/hitFaucet", - async (address: string, { getState, dispatch }): Promise => { - const state = getState() as RootState; - const config = state.connection.config; - const faucet = new FaucetClient(config["faucetEndpoint"]); - dispatch( - pushMessage({ - status: "neutral", - message: "Requesting faucet funds...", - }) - ); - try { - await faucet.credit(address, config["microDenom"]); - dispatch( - pushMessage({ - status: "success", - message: "Successfully requested funds from faucet", - }) - ); - dispatch(checkBalance(address)); - } catch (e) { - console.error(e); - dispatch( - pushMessage({ - status: "danger", - header: "Error requesting funds from faucet", - message: e instanceof Error ? e.message : JSON.stringify(e), - }) - ); + "accounts/hitFaucet", + async (address: string, { getState, dispatch }): Promise => { + const state = getState() as RootState; + const config = state.connection.config; + const faucet = new FaucetClient(config["faucetEndpoint"]); + dispatch( + pushMessage({ + status: "neutral", + message: "Requesting faucet funds...", + }) + ); + try { + await faucet.credit(address, config["microDenom"]); + dispatch( + pushMessage({ + status: "success", + message: "Successfully requested funds from faucet", + }) + ); + dispatch(checkBalance(address)); + } catch (e) { + console.error(e); + dispatch( + pushMessage({ + status: "danger", + header: "Error requesting funds from faucet", + message: e instanceof Error ? e.message : JSON.stringify(e), + }) + ); + } } - } ); export const sendCoins = createAsyncThunk( - "accounts/sendCoins", - async ( - { - sender, - recipient, - amount, - memo, - customConfig, - }: { - sender: string; - recipient: string; - amount: string; - memo?: string; - customConfig?: { [key: string]: string }; - }, - { getState, dispatch } - ): Promise => { - try { - const state = getState() as RootState; - const config = customConfig ?? state.connection.config; - const senderAccount = state.accounts.accountList[sender]; - - if (!sender) { - throw new Error("No account selected"); - } - - const client = await connectionManager.getSigningClient( - senderAccount, - config - ); - - const coinsAmount = [ + "accounts/sendCoins", + async ( { - amount: toMicroAmount(amount, config["coinDecimals"]), - denom: config["microDenom"], + sender, + recipient, + amount, + memo, + customConfig, + }: { + sender: string; + recipient: string; + amount: string; + memo?: string; + customConfig?: { [key: string]: string }; }, - ]; - - dispatch( - pushMessage({ - status: "neutral", - message: "Sending coins...", - }) - ); - - const { code } = await client.sendTokens( - sender, - recipient, - coinsAmount, - "auto", - memo - ); - - if (code !== 0) throw new Error("Transaction failed"); - - dispatch( - pushMessage({ - status: "success", - message: "Coins sent successfully", - }) - ); - } catch (e) { - console.error(e); - dispatch( - pushMessage({ - status: "danger", - header: "Error sending coins", - message: e instanceof Error ? e.message : JSON.stringify(e), - }) - ); + { getState, dispatch } + ): Promise => { + try { + const state = getState() as RootState; + const config = customConfig ?? state.connection.config; + const senderAccount = state.accounts.accountList[sender]; + + if (!sender) { + throw new Error("No account selected"); + } + + const client = await connectionManager.getSigningClient( + senderAccount, + config + ); + + const coinsAmount = [ + { + amount: toMicroAmount(amount, config["coinDecimals"]), + denom: config["microDenom"], + }, + ]; + + dispatch( + pushMessage({ + status: "neutral", + message: "Sending coins...", + }) + ); + + const { code } = await client.sendTokens( + sender, + recipient, + coinsAmount, + "auto", + memo + ); + + if (code !== 0) throw new Error("Transaction failed"); + + dispatch( + pushMessage({ + status: "success", + message: "Coins sent successfully", + }) + ); + } catch (e) { + console.error(e); + dispatch( + pushMessage({ + status: "danger", + header: "Error sending coins", + message: e instanceof Error ? e.message : JSON.stringify(e), + }) + ); + } } - } ); export const accountsSlice = createSlice({ - name: "accounts", - initialState, - reducers: { - selectAccount: (state, action: PayloadAction) => { - state.currentAccount = action.payload; - }, - selectContract: (state, action: PayloadAction) => { - state.currentContract = action.payload; - }, - deleteAccount: (state, action: PayloadAction) => { - delete state.accountList[action.payload]; - }, - setKeplrAccount: ( - state, - action: PayloadAction - ) => { - const account = action.payload; - - if (account) { - state.accountList[account.address] = account; - state.currentAccount = account.address; - } else { - if ( - state.currentAccount && - state.accountList[state.currentAccount]?.type === AccountType.Keplr - ) { - state.currentAccount = undefined; - } - delete state.accountList[state.keplrAccount!.address]; - } - - state.keplrAccount = account; - }, - setAccountBalance: ( - state, - action: PayloadAction<{ address: string; balance: Coin }> - ) => { - const { address, balance } = action.payload; - const account = state.accountList[address]; - if (account) { - account.balance = balance; - } - }, - setSendCoinsOpen: (state, action: PayloadAction) => { - state.sendCoinsOpen = action.payload; - }, - setDonationOpen: (state, action: PayloadAction) => { - state.donationOpen = action.payload; + name: "accounts", + initialState, + reducers: { + selectAccount: (state, action: PayloadAction) => { + state.currentAccount = action.payload; + }, + selectContract: (state, action: PayloadAction) => { + state.currentContract = action.payload; + }, + deleteAccount: (state, action: PayloadAction) => { + delete state.accountList[action.payload]; + }, + setKeplrAccount: ( + state, + action: PayloadAction + ) => { + const account = action.payload; + + if (account) { + state.accountList[account.address] = account; + state.currentAccount = account.address; + } else { + if ( + state.currentAccount && + state.accountList[state.currentAccount]?.type === + AccountType.Keplr + ) { + state.currentAccount = undefined; + } + delete state.accountList[state.keplrAccount!.address]; + } + + state.keplrAccount = account; + }, + setAccountBalance: ( + state, + action: PayloadAction<{ address: string; balance: Coin }> + ) => { + const { address, balance } = action.payload; + const account = state.accountList[address]; + if (account) { + account.balance = balance; + } + }, + setSendCoinsOpen: (state, action: PayloadAction) => { + state.sendCoinsOpen = action.payload; + }, + setDonationOpen: (state, action: PayloadAction) => { + state.donationOpen = action.payload; + }, + setImportContractOpen: (state, action: PayloadAction) => { + state.importContractOpen = action.payload; + }, }, - setImportContractOpen: (state, action: PayloadAction) => { - state.importContractOpen = action.payload; + extraReducers: (builder) => { + builder + .addCase(importAccount.fulfilled, (state, action) => { + const account = action.payload; + state.accountList[account.address] = account; + state.currentAccount = account.address; + }) + .addCase(checkBalance.fulfilled, (state, action) => { + const address = action.meta.arg; + const balance = action.payload; + const account = state.accountList[address]; + if (account) { + account.balance = balance; + } + }) + .addCase(importContract.fulfilled, (state, action) => { + const account = action.payload; + state.accountList[account.address] = account; + state.currentContract = account.address; + }) + .addCase(checkContract.fulfilled, (state, action) => { + const newAccountInfo = action.payload; + const account = action.meta.arg; + if (newAccountInfo && state.accountList[account.address]) { + state.accountList[account.address] = { + ...account, + ...newAccountInfo, + }; + if ( + !newAccountInfo.exists && + state.currentContract === account.address + ) { + state.currentContract = undefined; + } + } + }); }, - }, - extraReducers: (builder) => { - builder - .addCase(importAccount.fulfilled, (state, action) => { - const account = action.payload; - state.accountList[account.address] = account; - state.currentAccount = account.address; - }) - .addCase(checkBalance.fulfilled, (state, action) => { - const address = action.meta.arg; - const balance = action.payload; - const account = state.accountList[address]; - if (account) { - account.balance = balance; - } - }) - .addCase(importContract.fulfilled, (state, action) => { - const account = action.payload; - state.accountList[account.address] = account; - state.currentContract = account.address; - }) - .addCase(checkContract.fulfilled, (state, action) => { - const newAccountInfo = action.payload; - const account = action.meta.arg; - if (newAccountInfo && state.accountList[account.address]) { - state.accountList[account.address] = { - ...account, - ...newAccountInfo, - }; - if ( - !newAccountInfo.exists && - state.currentContract === account.address - ) { - state.currentContract = undefined; - } - } - }); - }, }); export const { - selectAccount, - selectContract, - deleteAccount, - setKeplrAccount, - setAccountBalance, - setSendCoinsOpen, - setDonationOpen, - setImportContractOpen, + selectAccount, + selectContract, + deleteAccount, + setKeplrAccount, + setAccountBalance, + setSendCoinsOpen, + setDonationOpen, + setImportContractOpen, } = accountsSlice.actions; export const selectedAccount = (state: RootState) => - state.accounts.currentAccount !== undefined - ? state.accounts.accountList[state.accounts.currentAccount] - : undefined; + state.accounts.currentAccount !== undefined + ? state.accounts.accountList[state.accounts.currentAccount] + : undefined; export const balanceString = (address?: string) => (state: RootState) => { - const config = state.connection.config; - if (!address) return `0${config["microDenom"]}`; + const config = state.connection.config; + if (!address) return `0${config["microDenom"]}`; - const account = state.accounts.accountList[address]; - const balance = fromMicroCoin(account.balance, config["coinDecimals"]); - return `${balance.amount}${balance.denom}`; + const account = state.accounts.accountList[address]; + const balance = fromMicroCoin(account.balance, config["coinDecimals"]); + return `${balance.amount}${balance.denom}`; }; export const selectedContract = (state: RootState) => - state.accounts.currentContract !== undefined - ? state.accounts.accountList[state.accounts.currentContract] - : undefined; + state.accounts.currentContract !== undefined + ? state.accounts.accountList[state.accounts.currentContract] + : undefined; export const basicAccounts = (state: RootState) => - Object.values(state.accounts.accountList).filter( - (account) => account.type === AccountType.Basic - ); + Object.values(state.accounts.accountList).filter( + (account) => account.type === AccountType.Basic + ); export const contractAccounts = (state: RootState) => - Object.values(state.accounts.accountList).filter( - (account) => account.type === AccountType.Contract - ) as Contract[]; + Object.values(state.accounts.accountList).filter( + (account) => account.type === AccountType.Contract + ) as Contract[]; export default accountsSlice.reducer; diff --git a/src/features/app/appSlice.ts b/src/features/app/appSlice.ts new file mode 100644 index 0000000..32553eb --- /dev/null +++ b/src/features/app/appSlice.ts @@ -0,0 +1,29 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export enum AppLocations { + Home = "", + BechConverter = "bechConverter", + MsgSignVerify = "msgSignVerify" +} + +export interface AppState { + appLocation: AppLocations +} + +const initialState: AppState = { + appLocation: AppLocations.Home, +}; + +export const appSlice = createSlice({ + name: "appState", + initialState, + reducers: { + changeAppLocation: (state, action: PayloadAction) => { + state.appLocation = action.payload; + }, + }, +}); + +export const { changeAppLocation } = appSlice.actions; + +export default appSlice.reducer; diff --git a/src/features/connection/Configuration.module.css b/src/features/connection/Configuration.module.css index 6914f11..19b5e74 100644 --- a/src/features/connection/Configuration.module.css +++ b/src/features/connection/Configuration.module.css @@ -32,7 +32,6 @@ .form sl-input, .form sl-button { - margin: 0.5em 0; align-self: center; width: 45%; } diff --git a/src/features/connection/Configuration.tsx b/src/features/connection/Configuration.tsx index cd92cbb..98e435c 100644 --- a/src/features/connection/Configuration.tsx +++ b/src/features/connection/Configuration.tsx @@ -7,7 +7,7 @@ import { } from "@shoelace-style/shoelace/dist/react"; import type SlInputElement from "@shoelace-style/shoelace/dist/components/input/input"; import type SlSelectElement from "@shoelace-style/shoelace/dist/components/select/select"; -import React, { FC, useEffect, useState } from "react"; +import { FC, useEffect, useState } from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import styles from "./Configuration.module.css"; import { diff --git a/src/features/connection/Connection.tsx b/src/features/connection/Connection.tsx index 9649888..c7d0374 100644 --- a/src/features/connection/Connection.tsx +++ b/src/features/connection/Connection.tsx @@ -1,43 +1,45 @@ import { SlCard, SlIcon } from "@shoelace-style/shoelace/dist/react"; -import React, { FC, useEffect } from "react"; +import { FC, useEffect } from "react"; import styles from "./Connection.module.css"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { - checkConnection, - ConnectionStatus, - setConfigModalOpen, + checkConnection, + ConnectionStatus, + setConfigModalOpen, } from "./connectionSlice"; export const Connection: FC = () => { - const dispatch = useAppDispatch(); - const config = useAppSelector((state) => state.connection.config); - const connection = useAppSelector((state) => state.connection.status); - const chainName: string = config["chainName"]; + const dispatch = useAppDispatch(); + const config = useAppSelector((state) => state.connection.config); + const connection = useAppSelector((state) => state.connection.status); + const chainName: string = config["chainName"]; - useEffect(() => { - dispatch(checkConnection()); - }, [dispatch]); + useEffect(() => { + dispatch(checkConnection()); + }, [dispatch]); - const classes = [styles.connection, styles[`state-${connection}`]].join(" "); + const classes = [styles.connection, styles[`state-${connection}`]].join( + " " + ); - return ( - - -
{chainName}
- dispatch(setConfigModalOpen(true))} - /> -
- ); + return ( + + +
{chainName}
+ dispatch(setConfigModalOpen(true))} + /> +
+ ); }; diff --git a/src/features/connection/connectionManager.ts b/src/features/connection/connectionManager.ts index f5f8fb7..7505f4b 100644 --- a/src/features/connection/connectionManager.ts +++ b/src/features/connection/connectionManager.ts @@ -1,88 +1,120 @@ import { - CosmWasmClient, - SigningCosmWasmClient, + SigningCosmWasmClient, + setupWasmExtension, + WasmExtension, } from "@cosmjs/cosmwasm-stargate"; import { DirectSecp256k1HdWallet, OfflineSigner } from "@cosmjs/proto-signing"; -import { GasPrice } from "@cosmjs/stargate"; +import { + GasPrice, + QueryClient, + createProtobufRpcClient, +} from "@cosmjs/stargate"; +import { Tendermint34Client, HttpBatchClient } from "@cosmjs/tendermint-rpc"; import { Account, AccountType } from "../accounts/accountsSlice"; import { getKeplr } from "../accounts/useKeplr"; +import { QueryClientImpl as BankQueryClientImpl } from "cosmjs-types/cosmos/bank/v1beta1/query"; +import { QueryClientImpl as AccountQueryClientImpl } from "cosmjs-types/cosmos/auth/v1beta1/query"; + interface ClientConnection { - client: CosmWasmClient; - rpcEndpoint: string; + client?: QueryClient & WasmExtension; + bankQueryService?: BankQueryClientImpl; + accountQueryService?: AccountQueryClientImpl; + rpcEndpoint: string; } interface SigningClientConnection extends ClientConnection { - client: SigningCosmWasmClient; - address: string; + signingClient: SigningCosmWasmClient; + address: string; } class ConnectionManager { - private queryingClientConnection: ClientConnection | undefined; - private signingClientConnections: { [key: string]: SigningClientConnection } = - {}; + private queryingClientConnection: ClientConnection | undefined; + private signingClientConnections: { + [key: string]: SigningClientConnection; + } = {}; + + getQueryClient = async ( + config: { + [key: string]: string; + }, + forceRefresh = false + ): Promise => { + const rpcEndpoint: string = config["rpcEndpoint"]; + if ( + this.queryingClientConnection === undefined || + this.queryingClientConnection.rpcEndpoint !== rpcEndpoint || + forceRefresh + ) { + const httpClient = new HttpBatchClient(rpcEndpoint); + + const client = await Tendermint34Client.create(httpClient).then( + (res) => { + const queryClient = QueryClient.withExtensions( + res, + setupWasmExtension + ); + + return queryClient; + } + ); + + const rpcClient = createProtobufRpcClient(client); - getQueryClient = async ( - config: { - [key: string]: string; - }, - forceRefresh = false - ): Promise => { - const rpcEndpoint: string = config["rpcEndpoint"]; - if ( - this.queryingClientConnection === undefined || - this.queryingClientConnection.rpcEndpoint !== rpcEndpoint || - forceRefresh - ) { - this.queryingClientConnection = { - client: await CosmWasmClient.connect(rpcEndpoint), - rpcEndpoint, - }; - } + this.queryingClientConnection = { + client, + rpcEndpoint, + bankQueryService: new BankQueryClientImpl(rpcClient), + accountQueryService: new AccountQueryClientImpl(rpcClient), + }; + } - return this.queryingClientConnection.client; - }; + return this.queryingClientConnection; + }; - getSigningClient = async ( - account: Account, - config: { [key: string]: string } - ): Promise => { - const rpcEndpoint: string = config["rpcEndpoint"]; - const { address } = account; - if ( - this.signingClientConnections[address] === undefined || - this.signingClientConnections[address].rpcEndpoint !== rpcEndpoint - ) { - let signer: OfflineSigner; - if (account.type === AccountType.Basic) { - const prefix: string = config["addressPrefix"]; - signer = await DirectSecp256k1HdWallet.fromMnemonic(account.mnemonic, { - prefix, - }); - } else if (account.type === AccountType.Keplr) { - const keplr = await getKeplr(); - const chainId: string = config["chainId"]; - await keplr.enable(chainId); - signer = keplr.getOfflineSigner(chainId); - } else { - throw new Error("Invalid account type"); - } - this.signingClientConnections[address] = { - client: await SigningCosmWasmClient.connectWithSigner( - rpcEndpoint, - signer, - { - gasPrice: GasPrice.fromString( - `${config["gasPrice"]}${config["microDenom"]}` - ), - } - ), - address, - rpcEndpoint, - }; - } - return this.signingClientConnections[address].client; - }; + getSigningClient = async ( + account: Account, + config: { [key: string]: string } + ): Promise => { + const rpcEndpoint: string = config["rpcEndpoint"]; + const { address } = account; + if ( + this.signingClientConnections[address] === undefined || + this.signingClientConnections[address].rpcEndpoint !== rpcEndpoint + ) { + let signer: OfflineSigner; + if (account.type === AccountType.Basic) { + const prefix: string = config["addressPrefix"]; + signer = await DirectSecp256k1HdWallet.fromMnemonic( + account.mnemonic, + { + prefix, + } + ); + } else if (account.type === AccountType.Keplr) { + const keplr = await getKeplr(); + const chainId: string = config["chainId"]; + await keplr.enable(chainId); + signer = keplr.getOfflineSigner(chainId); + } else { + throw new Error("Invalid account type"); + } + this.signingClientConnections[address] = { + signingClient: await SigningCosmWasmClient.connectWithSigner( + rpcEndpoint, + signer, + { + gasPrice: GasPrice.fromString( + `${config["gasPrice"]}${config["microDenom"]}` + ), + } + ), + address, + rpcEndpoint, + }; + } + return this.signingClientConnections[address].signingClient; + }; } export default new ConnectionManager(); diff --git a/src/features/connection/connectionSlice.ts b/src/features/connection/connectionSlice.ts index 255bb47..01882b2 100644 --- a/src/features/connection/connectionSlice.ts +++ b/src/features/connection/connectionSlice.ts @@ -5,90 +5,102 @@ import connectionManager from "./connectionManager"; import presets from "./presets.json"; export enum ConnectionStatus { - Connecting = "connecting", - Connected = "connected", - Error = "error", + Connecting = "connecting", + Connected = "connected", + Error = "error", } export interface ConnectionState { - config: { [key: string]: string }; - modalOpen: boolean; - status: ConnectionStatus; + config: { [key: string]: string }; + modalOpen: boolean; + status: ConnectionStatus; + forceRefresh: boolean; } const initialState: ConnectionState = { - config: presets["juno-uni"], - modalOpen: false, - status: ConnectionStatus.Connecting, + config: presets["juno-uni"], + modalOpen: false, + status: ConnectionStatus.Connecting, + forceRefresh: false, }; export const checkConnection = createAsyncThunk< - void, - | { - testing: boolean; - } - | undefined + void, + | { + testing: boolean; + } + | undefined >( - "connection/checkConnection", - async ( - { testing } = { testing: false }, - { dispatch, getState } - ): Promise => { - const state = getState() as RootState; - const config = state.connection.config; + "connection/checkConnection", + async ( + { testing } = { testing: false }, + { dispatch, getState } + ): Promise => { + const state = getState() as RootState; + const config = state.connection.config; - dispatch(setConnectionStatus(ConnectionStatus.Connecting)); - try { - await connectionManager.getQueryClient(config, testing); - dispatch(setConnectionStatus(ConnectionStatus.Connected)); - if (testing) - dispatch( - pushMessage({ - status: "success", - message: `Successfully connected to ${config["chainName"]}`, - }) - ); - } catch (e) { - console.error(e); - dispatch(setConnectionStatus(ConnectionStatus.Error)); - if (testing) - dispatch( - pushMessage({ status: "danger", message: "Connection failed" }) - ); + dispatch(setConnectionStatus(ConnectionStatus.Connecting)); + try { + await connectionManager.getQueryClient(config, testing); + dispatch(setConnectionStatus(ConnectionStatus.Connected)); + if (testing) dispatch(setForceRefresh(true)); + dispatch( + pushMessage({ + status: "success", + message: `Successfully connected to ${config["chainName"]}`, + }) + ); + } catch (e) { + console.error(e); + dispatch(setConnectionStatus(ConnectionStatus.Error)); + if (testing) + dispatch( + pushMessage({ + status: "danger", + message: "Connection failed", + }) + ); + } } - } ); export const connectionSlice = createSlice({ - name: "connection", - initialState, - reducers: { - setConfigModalOpen: (state, action: PayloadAction) => { - state.modalOpen = action.payload; - }, - setConnectionConfigItem: ( - state, - action: PayloadAction<{ key: string; value: string }> - ) => { - state.config[action.payload.key] = action.payload.value; - }, - setConnectionConfig: ( - state, - action: PayloadAction<{ [key: string]: string }> - ) => { - state.config = action.payload; - }, - setConnectionStatus: (state, action: PayloadAction) => { - state.status = action.payload; + name: "connection", + initialState, + reducers: { + setForceRefresh: (state, action: PayloadAction) => { + state.forceRefresh = action.payload; + }, + setConfigModalOpen: (state, action: PayloadAction) => { + state.modalOpen = action.payload; + }, + setConnectionConfigItem: ( + state, + action: PayloadAction<{ key: string; value: string }> + ) => { + state.config[action.payload.key] = action.payload.value; + }, + setConnectionConfig: ( + state, + action: PayloadAction<{ [key: string]: string }> + ) => { + state.config = action.payload; + }, + setConnectionStatus: ( + state, + action: PayloadAction + ) => { + state.status = action.payload; + }, }, - }, }); export const { - setConfigModalOpen, - setConnectionConfigItem, - setConnectionConfig, - setConnectionStatus, + setConfigModalOpen, + setConnectionConfigItem, + setConnectionConfig, + setConnectionStatus, + setForceRefresh, } = connectionSlice.actions; export default connectionSlice.reducer; diff --git a/src/features/connection/presets.json b/src/features/connection/presets.json index 284c891..2d41b83 100644 --- a/src/features/connection/presets.json +++ b/src/features/connection/presets.json @@ -11,10 +11,10 @@ "gasPrice": "0.025" }, "juno-uni": { - "chainName": "Juno Testnet - Uni", - "chainId": "uni-3", - "rpcEndpoint": "https://rpc.uni.juno.deuslabs.fi", - "restEndpoint": "https://lcd.uni.juno.deuslabs.fi", + "chainName": "Juno XTREME", + "chainId": "uni-6", + "rpcEndpoint": "https://juno-testnet-rpc.polkachu.com/", + "restEndpoint": "https://juno-testnet-api.polkachu.com/", "addressPrefix": "juno", "microDenom": "ujunox", "coinDecimals": "6", diff --git a/src/features/console/Console.tsx b/src/features/console/Console.tsx index f60f478..10829fd 100644 --- a/src/features/console/Console.tsx +++ b/src/features/console/Console.tsx @@ -1,5 +1,5 @@ import { SlSplitPanel } from "@shoelace-style/shoelace/dist/react"; -import React, { FC } from "react"; +import { FC } from "react"; import styles from "./Console.module.css"; import { Output } from "./Output"; import { Input } from "./Input"; diff --git a/src/features/console/ExecuteOptions.module.css b/src/features/console/ExecuteOptions.module.css index e14a003..6d87681 100644 --- a/src/features/console/ExecuteOptions.module.css +++ b/src/features/console/ExecuteOptions.module.css @@ -6,9 +6,6 @@ display: flex; flex-direction: column; justify-content: space-between; - height: 10rem; - margin-top: -1rem; - margin-bottom: 0.5rem; } .buttons { @@ -21,3 +18,12 @@ .buttons sl-switch { flex-grow: 1; } + +.required-input { + outline: 1px solid slategray; +} + +.error { + outline: 1px solid red !important; + background: rgb(255,240,240); +} \ No newline at end of file diff --git a/src/features/console/ExecuteOptions.tsx b/src/features/console/ExecuteOptions.tsx index fd24042..cf084bb 100644 --- a/src/features/console/ExecuteOptions.tsx +++ b/src/features/console/ExecuteOptions.tsx @@ -1,73 +1,82 @@ import { - SlButton, - SlDialog, - SlInput, - SlSwitch, + SlButton, + SlDialog, + SlInput, + SlSwitch, } from "@shoelace-style/shoelace/dist/react"; import type SlInputElement from "@shoelace-style/shoelace/dist/components/input/input"; import type SlSwitchElement from "@shoelace-style/shoelace/dist/components/switch/switch"; -import React, { FC, useState } from "react"; +import { FC, useState } from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import styles from "./ExecuteOptions.module.css"; import { execute, setExecuteOptions, setOptionsOpen } from "./consoleSlice"; import { fromMicroDenom } from "../../util/coins"; export const ExecuteOptions: FC = () => { - const dispatch = useAppDispatch(); - const open = useAppSelector((state) => state.console.optionsOpen); - const config = useAppSelector((state) => state.connection.config); - const [funds, setFunds] = useState(""); - const [memo, setMemo] = useState(""); - const [saveDefaults, setSaveDefaults] = useState(false); + const dispatch = useAppDispatch(); + const open = useAppSelector((state) => state.console.optionsOpen); + const config = useAppSelector((state) => state.connection.config); + const [funds, setFunds] = useState(""); + const [memo, setMemo] = useState(""); + const [saveDefaults, setSaveDefaults] = useState(false); - function saveAndExecute() { - dispatch(setOptionsOpen(false)); - if (!saveDefaults) { - setFunds(""); - setMemo(""); - } else { - dispatch(setExecuteOptions({ funds, memo })); + function saveAndExecute() { + dispatch(setOptionsOpen(false)); + if (!saveDefaults) { + setFunds(""); + setMemo(""); + } else { + dispatch(setExecuteOptions({ funds, memo })); + } + dispatch(execute({ funds, memo })); } - dispatch(execute({ funds, memo })); - } - return ( - dispatch(setOptionsOpen(false))} - className={styles.dialog} - > -
- - setFunds((e.target as SlInputElement).value.trim()) - } + return ( + dispatch(setOptionsOpen(false))} + className={styles.dialog} > -
{fromMicroDenom(config["microDenom"])}
-
- setMemo((e.target as SlInputElement).value.trim())} - /> -
- - setSaveDefaults((e.target as SlSwitchElement).checked) - } - > - Save as default - - saveAndExecute()}> - Execute - -
-
-
- ); +
+ + setFunds((e.target as SlInputElement).value.trim()) + } + > +
+ {fromMicroDenom(config["microDenom"])} +
+
+ + setMemo((e.target as SlInputElement).value.trim()) + } + /> +
+ + setSaveDefaults( + (e.target as SlSwitchElement).checked + ) + } + > + Save as default + + saveAndExecute()} + > + Execute + +
+
+ + ); }; diff --git a/src/features/console/Input.module.css b/src/features/console/Input.module.css index d42592e..97c9df2 100644 --- a/src/features/console/Input.module.css +++ b/src/features/console/Input.module.css @@ -4,7 +4,7 @@ .editor { width: 100%; - height: 90%; + height: 100%; background-color: #f5f5f5; } diff --git a/src/features/console/Input.tsx b/src/features/console/Input.tsx index d6f9786..65e07c3 100644 --- a/src/features/console/Input.tsx +++ b/src/features/console/Input.tsx @@ -1,70 +1,447 @@ import { - SlButton, - SlButtonGroup, - SlDropdown, - SlIcon, - SlMenu, - SlMenuItem, + SlButton, + SlButtonGroup, + SlDropdown, + SlMenu, + SlMenuItem, } from "@shoelace-style/shoelace/dist/react"; -import React, { FC, useEffect, useState } from "react"; +import { FC, useEffect, useLayoutEffect, useRef, useState } from "react"; import Editor from "react-simple-code-editor"; import formatHighlight from "json-format-highlight"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import styles from "./Input.module.css"; import { - execute, - prettifyInput, - query, - highlightColors, - setOptionsOpen, - sign, + execute, + prettifyInput, + query, + highlightColors, + setOptionsOpen, + sign, + setInstantiateOpen, } from "./consoleSlice"; + +import { + buildExecuteToolbox, + buildQueryToolbox, + resetState, + toolboxExecute, + toolboxQuery, +} from "./toolboxSlice"; + import { AppThunk } from "../../app/store"; +import { Accordion, AccordionTab } from "primereact/accordion"; +import { Dropdown } from "primereact/dropdown"; +import { Tooltip } from "primereact/tooltip"; + +interface IDropdownOption { + name: string; + code: string; +} + export const Input: FC = () => { - const dispatch = useAppDispatch(); - const input = useAppSelector((state) => state.console.input); - const [message, setMessage] = useState(input); - - useEffect(() => { - setMessage(input); - }, [input]); - - function run(action: AppThunk) { - dispatch(prettifyInput(message)); - dispatch(action); - } - - return ( -
- formatHighlight(code, highlightColors)} - placeholder="Enter JSON message" - value={message} - padding={10} - onValueChange={(code) => setMessage(code)} - /> -
- - dispatch(prettifyInput(message))}> - Format - - run(query())}>Query - run(execute())}>Execute - - - - run(sign())}> - Sign - - dispatch(setOptionsOpen(true))}> - Execute with... - - - - -
-
- ); + const dispatch = useAppDispatch(); + const input = useAppSelector((state) => state.console.input); + const [message, setMessage] = useState(input); + + const currentAccount = useAppSelector( + (state) => state.accounts.currentAccount + ); + + const [lastContractSelected, setLastContractSelected] = useState< + string | undefined + >(undefined); + + const [lineNumbers, setLineNumbers] = useState([]); + + // used when a user switches contracts. will requery the selected contract + const selected = useAppSelector((state) => state.accounts.currentContract); + const stateReset = useAppSelector((state) => state.toolbox.stateReset); + const forceRefresh = useAppSelector( + (state) => state.connection.forceRefresh + ); + + // query processing handlers + // refs so we dont get in a rerender loop + const queryReady = useRef(false); + const toolboxQueryExecutedRef = useRef(false); + const toolboxQueryBuiltRef = useRef(false); + + const toolboxQueryProcessing = useAppSelector( + (state) => state.toolbox.toolboxQueryProcessing + ); + const toolboxQueryReady = useAppSelector( + (state) => state.toolbox.toolboxQueryReady + ); + const toolboxQueryOptions = useAppSelector( + (state) => state.toolbox.toolboxQueryOptions + ); + + // execute processing handlers + // refs so we dont get in a rerender loop + const executeReady = useRef(false); + const toolboxExecuteExecutedRef = useRef(false); + const toolboxExecuteBuiltRef = useRef(false); + + const toolboxExecuteProcessing = useAppSelector( + (state) => state.toolbox.toolboxExecuteProcessing + ); + const toolboxExecuteReady = useAppSelector( + (state) => state.toolbox.toolboxExecuteReady + ); + const toolboxExecuteOptions = useAppSelector( + (state) => state.toolbox.toolboxExecuteOptions + ); + + // final "cards" that are built + const [queryCards, setQueryCards] = useState([]); + const [executeCards, setExecuteCards] = useState([]); + + useEffect(() => { + setMessage(input); + }, [input]); + + function run(action: AppThunk) { + dispatch(prettifyInput(message)); + dispatch(action); + } + + // resets toolbox states upon loading the page + useEffect(() => { + dispatch(resetState()); + toolboxQueryExecutedRef.current = false; + toolboxQueryBuiltRef.current = false; + queryReady.current = true; + + toolboxExecuteExecutedRef.current = false; + toolboxExecuteBuiltRef.current = false; + executeReady.current = true; + }, []); + + // when a contract is switched, we'll reset state + useEffect(() => { + if (selected !== lastContractSelected) { + queryReady.current = false; + toolboxQueryExecutedRef.current = false; + toolboxQueryBuiltRef.current = false; + + executeReady.current = false; + toolboxExecuteExecutedRef.current = false; + toolboxExecuteBuiltRef.current = false; + + setLastContractSelected(selected); + dispatch(resetState()); + rebuildToolbox(); + } + }, [dispatch, lastContractSelected, selected]); + + // if stateReset is detected, we'll rebuild the toolbox + useEffect(() => { + if (stateReset) { + dispatch(resetState()); + rebuildToolbox(); + } + }, [stateReset]); + + useEffect(() => { + if (forceRefresh) { + dispatch(resetState()); + setQueryCards([]); + setExecuteCards([]); + if (selected) { + console.log("forceRefresh"); + rebuildToolbox(); + } + } + }, [forceRefresh, selected]); + + const rebuildToolbox = () => { + setQueryCards([]); + setExecuteCards([]); + queryReady.current = true; + dispatch(buildQueryToolbox()); + + executeReady.current = true; + dispatch(buildExecuteToolbox()); + }; + + // this hook will iterate through the available options and then populate parameters + useEffect(() => { + if ( + queryReady.current && + toolboxQueryProcessing && + toolboxQueryOptions && + Object.keys(toolboxQueryOptions).length > 0 && + !toolboxQueryExecutedRef.current + ) { + toolboxQueryExecutedRef.current = true; + + for (let i = 0; i < Object.keys(toolboxQueryOptions).length; i++) { + let isLastOperation = + i === Object.keys(toolboxQueryOptions).length - 1; + + let obj = Object.keys(toolboxQueryOptions)[i]; + dispatch(toolboxQuery(obj, isLastOperation)); + + if (isLastOperation) { + toolboxQueryBuiltRef.current = true; + } + } + } + }, [toolboxQueryProcessing, toolboxQueryOptions, dispatch]); + + // this hook builds the dropdown options + useEffect(() => { + if ( + toolboxQueryReady && + toolboxQueryOptions && + Object.keys(toolboxQueryOptions).length > 0 && + toolboxQueryExecutedRef.current && + toolboxQueryBuiltRef.current + ) { + let qCards: IDropdownOption[] = []; + + let keys = Object.keys(toolboxQueryOptions).sort(); + + for (const key of keys) { + qCards.push({ name: key, code: key } as IDropdownOption); + } + + setQueryCards(qCards); + } + }, [toolboxQueryReady, toolboxQueryOptions]); + + // this hook will iterate through the available options and then populate parameters + useEffect(() => { + if ( + executeReady.current && + toolboxExecuteProcessing && + toolboxExecuteOptions && + Object.keys(toolboxExecuteOptions).length > 0 && + !toolboxExecuteExecutedRef.current + ) { + toolboxExecuteExecutedRef.current = true; + + for ( + let i = 0; + i < Object.keys(toolboxExecuteOptions).length; + i++ + ) { + let isLastOperation = + i === Object.keys(toolboxExecuteOptions).length - 1; + + let obj = Object.keys(toolboxExecuteOptions)[i]; + dispatch(toolboxExecute(obj, isLastOperation)); + + if (isLastOperation) { + toolboxExecuteBuiltRef.current = true; + } + } + } + }, [toolboxExecuteProcessing, toolboxExecuteOptions, dispatch]); + + // this hook builds the dropdown options + useEffect(() => { + if ( + toolboxExecuteReady && + toolboxExecuteOptions && + Object.keys(toolboxExecuteOptions).length > 0 && + toolboxExecuteExecutedRef.current && + toolboxExecuteBuiltRef.current + ) { + let eCards: IDropdownOption[] = []; + + let keys = Object.keys(toolboxExecuteOptions).sort(); + + for (const key of keys) { + eCards.push({ name: key, code: key } as IDropdownOption); + } + + setExecuteCards(eCards); + } + }, [toolboxExecuteReady, toolboxExecuteOptions]); + + // if a user selects an option, we'll populate the input + const setSelectedCard = (selectType: string, opt: IDropdownOption) => { + let options: + | { + [key: string]: { + [key: string]: {}; + }; + } + | undefined = undefined; + + if (selectType === "q") { + options = toolboxQueryOptions[opt.name]; + } else if (selectType === "e") { + options = toolboxExecuteOptions[opt.name]; + } + + if (options !== undefined) { + let newInput = `{"${opt.name}": ${JSON.stringify(options)}}`; + dispatch(prettifyInput(newInput)); + try { + // just in case this fails, just let it error into void + editorValueChange( + JSON.stringify(JSON.parse(newInput), null, 2) + ); + } catch (e) {} + } + }; + + const editorValueChange = (code: string) => { + setMessage(code); + dispatch(prettifyInput(code)); + + let i = 1; + let count = code.split("\n").length + 1; + + let _lineNumbers: JSX.Element[] = []; + + while (i < count) { + _lineNumbers.push( +
+ + {i} + +
+ ); + i++; + } + + setLineNumbers(_lineNumbers); + }; + + useLayoutEffect(() => { + editorValueChange(message); + }, []); + + return ( +
+
+
+ Contract Toolbox + + +

+ The "toolbox" does a few queries against the selected contract to build the toolbox. + The helpers also add in optional parameters without types, so the bulk of the work will + be up to your understanding of the contract you're trying to interact with +

+
+
+
+
+ setSelectedCard("q", e.value)} + options={queryCards} + optionLabel="name" + placeholder="Query Helper" + className="w-full" + scrollHeight={"600px"} + /> + setSelectedCard("e", e.value)} + options={executeCards} + optionLabel="name" + placeholder="Execute Helper" + className="w-full" + scrollHeight={"600px"} + /> +
+
+
+
+ {lineNumbers} +
+ formatHighlight(code, highlightColors)} + placeholder="Enter JSON message" + value={message} + padding={10} + onValueChange={(code) => editorValueChange(code)} + //onValueChange={(code) => setMessage(code)} + /> +
+
+ + + + + dispatch(prettifyInput(message))} + > + Format + + run(query())} + > + Query + + run(execute())} + disabled={currentAccount === undefined} + > + Execute + + + + + { + if (currentAccount !== undefined) { + dispatch(setInstantiateOpen(true)); + } + }} + disabled={currentAccount === undefined} + > + Instantiate + + { + if (currentAccount !== undefined) { + run(sign()); + } + }} + disabled={currentAccount === undefined} + > + Sign + + { + if (currentAccount !== undefined) { + dispatch(setOptionsOpen(true)); + } + }} + disabled={currentAccount === undefined} + > + Execute with... + + + + +
+
+ ); }; diff --git a/src/features/console/InstantiateOptions.tsx b/src/features/console/InstantiateOptions.tsx new file mode 100644 index 0000000..9ebe5e5 --- /dev/null +++ b/src/features/console/InstantiateOptions.tsx @@ -0,0 +1,153 @@ +import { + SlButton, + SlDialog, + SlInput, +} from "@shoelace-style/shoelace/dist/react"; +import type SlInputElement from "@shoelace-style/shoelace/dist/components/input/input"; +import { FC, useEffect, useState } from "react"; +import { useAppDispatch, useAppSelector } from "../../app/hooks"; +import styles from "./ExecuteOptions.module.css"; +import { setInstantiateOpen, setInstantiateOptions } from "./consoleSlice"; +import { fromMicroDenom } from "../../util/coins"; +import { + instantiateContract, + selectedAccount, +} from "../accounts/accountsSlice"; +import { useSelector } from "react-redux"; + +export const InstantiateOptions: FC = () => { + const dispatch = useAppDispatch(); + const open = useAppSelector((state) => state.console.instantiateOpen); + const config = useAppSelector((state) => state.connection.config); + const [funds, setFunds] = useState(""); + const [memo, setMemo] = useState(""); + const [label, setLabel] = useState(""); + const [codeId, setCodeId] = useState(""); + const [admin, setAdmin] = useState(""); + const [errors, setErrors] = useState<{ [key: string]: string }>({}); + + const account = useSelector(selectedAccount); + + useEffect(() => { + if (!open) { + clearForm(); + } + }, [open]); + + const clearForm = () => { + setFunds(""); + setMemo(""); + setLabel(""); + setCodeId(""); + setAdmin(""); + setErrors({}); + }; + + const validateField = (val: string | undefined) => { + if (val && val.length > 0) { + return val; + } + return undefined; + }; + + function saveAndExecute() { + setErrors({}) + if (!account) throw new Error("No account selected"); + + let hasErrors = false; + + if (!codeId || codeId.length === 0) { + setErrors((prev) => ({ ...prev, codeId: styles.error })); + hasErrors = true; + } + + if (!label || label.length === 0) { + setErrors((prev) => ({ ...prev, label: styles.error })); + hasErrors = true; + } + + if (hasErrors) { + throw new Error("Required fields are missing"); + } + + dispatch(setInstantiateOptions({ funds, memo, label, codeId, admin })); + + dispatch( + instantiateContract({ + address: account.address, + codeId: Number(codeId), + label, + memo: validateField(memo), + admin: validateField(admin), + funds: validateField(funds), + }) + ); + } + + return ( + dispatch(setInstantiateOpen(false))} + onSlAfterHide={() => dispatch(setInstantiateOpen(false))} + className={styles.dialog} + > +
+ + setCodeId((e.target as SlInputElement).value.trim()) + } + /> + + setLabel((e.target as SlInputElement).value.trim()) + } + /> + + setAdmin((e.target as SlInputElement).value.trim()) + } + /> + + setFunds((e.target as SlInputElement).value.trim()) + } + > +
+ {fromMicroDenom(config["microDenom"])} +
+
+ + setMemo((e.target as SlInputElement).value.trim()) + } + /> +
+ clearForm()}> + Clear + + saveAndExecute()} + className="w-2/3" + > + Instantiate + +
+
+
+ ); +}; diff --git a/src/features/console/Output.tsx b/src/features/console/Output.tsx index fc87a4e..766b183 100644 --- a/src/features/console/Output.tsx +++ b/src/features/console/Output.tsx @@ -1,31 +1,41 @@ -import React, { FC, useMemo } from "react"; +import { FC, useMemo } from "react"; import { useAppSelector } from "../../app/hooks"; import styles from "./Output.module.css"; import ReactJson from "react-json-view"; export const Output: FC = () => { - const output = useAppSelector((state) => state.console.output); - const outputObject = useMemo(() => { - try { - return JSON.parse(output); - } catch (_) { - return; - } - }, [output]); - const error = useAppSelector((state) => state.console.error); + const output = useAppSelector((state) => state.console.output); + const outputObject = useMemo(() => { + try { + return JSON.parse(output); + } catch (_) { + return; + } + }, [output]); - const classes = [styles.output]; - if (error) { - classes.push(styles.error); - } + const errorOutput = useAppSelector((state) => state.console.errorOutput); - return ( -
- {outputObject ? ( - - ) : ( -
-      )}
-    
- ); + return ( +
+ {errorOutput && ( +
+
JSON Format issues:
+
+                
+ )} +
+ {outputObject ? ( + + ) : ( +
+                )}
+            
+
+ ); }; diff --git a/src/features/console/consoleSlice.ts b/src/features/console/consoleSlice.ts index bc8df68..cd043a5 100644 --- a/src/features/console/consoleSlice.ts +++ b/src/features/console/consoleSlice.ts @@ -1,210 +1,268 @@ import { coins } from "@cosmjs/proto-signing"; -import { serializeSignDoc, Secp256k1HdWallet } from '@cosmjs/launchpad'; +import { serializeSignDoc, Secp256k1HdWallet } from "@cosmjs/launchpad"; import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; import { AppThunk, RootState } from "../../app/store"; import { toMicroAmount } from "../../util/coins"; -import { selectedAccount, selectedContract, AccountType } from "../accounts/accountsSlice"; +import { + selectedAccount, + selectedContract, + AccountType, +} from "../accounts/accountsSlice"; import connectionManager from "../connection/connectionManager"; import { StdSignature } from "@cosmjs/amino"; import { getKeplr } from "../accounts/useKeplr"; import { makeADR36AminoSignDoc } from "../../util/adr36"; export const highlightColors = { - keyColor: "black", - numberColor: "blue", - stringColor: "#0B7500", - trueColor: "#00cc00", - falseColor: "#ff8080", - nullColor: "cornflowerblue", + keyColor: "black", + numberColor: "blue", + stringColor: "#0B7500", + trueColor: "#00cc00", + falseColor: "#ff8080", + nullColor: "cornflowerblue", }; export interface ExecuteOptions { - funds: string; - memo: string; + funds: string; + memo: string; +} + +export interface InstantiateOptions { + codeId: string; + label: string; + funds?: string; + memo?: string; + admin?: string; } export interface ConsoleState { - input: string; - output: string; - error: boolean; - optionsOpen: boolean; - executeOptions?: ExecuteOptions; + input: string; + output: string; + errorOutput: string; + error: boolean; + optionsOpen: boolean; + instantiateOpen: boolean; + executeOptions?: ExecuteOptions; + instantiateOptions?: InstantiateOptions; } const initialState: ConsoleState = { - input: "", - output: "Response will appear here", - error: false, - optionsOpen: false, + input: "", + output: "Response will appear here", + errorOutput: "", + error: false, + optionsOpen: false, + instantiateOpen: false, }; -class ConsoleError extends Error { } +class ConsoleError extends Error {} export const sign = (): AppThunk => (dispatch, getState) => { - dispatch( - run(async (msgObj) => { - const account = selectedAccount(getState()); - if (!account) throw new Error("No account selected"); - - const signDoc = makeADR36AminoSignDoc( - account.address, - JSON.stringify(msgObj), - ); - - let stdSig: StdSignature; - - if (account.type === AccountType.Keplr) { - const keplr = await getKeplr(); - const chainId = getState().connection.config["chainId"]; - - stdSig = await keplr.signArbitrary(chainId, account.address, JSON.stringify(msgObj)); - } else if (account.type === AccountType.Basic) { - const wallet = await Secp256k1HdWallet.fromMnemonic(account.mnemonic, { - prefix: getState().connection.config["addressPrefix"], - }); - - stdSig = (await wallet.signAmino(account.address, signDoc)).signature; - } else { - throw new Error("Invalid account type"); - } - - const document = Buffer.from(serializeSignDoc(signDoc)).toString("base64"); - const { signature, pub_key: { value: pubkey } } = stdSig; - return { document, signature, pubkey }; - })); -} + dispatch( + run(async (msgObj) => { + const account = selectedAccount(getState()); + if (!account) throw new Error("No account selected"); + + const signDoc = makeADR36AminoSignDoc( + account.address, + JSON.stringify(msgObj) + ); + + let stdSig: StdSignature; + + if (account.type === AccountType.Keplr) { + const keplr = await getKeplr(); + const chainId = getState().connection.config["chainId"]; + + stdSig = await keplr.signArbitrary( + chainId, + account.address, + JSON.stringify(msgObj) + ); + } else if (account.type === AccountType.Basic) { + const wallet = await Secp256k1HdWallet.fromMnemonic( + account.mnemonic, + { + prefix: getState().connection.config["addressPrefix"], + } + ); + + stdSig = (await wallet.signAmino(account.address, signDoc)) + .signature; + } else { + throw new Error("Invalid account type"); + } + + const document = Buffer.from(serializeSignDoc(signDoc)).toString( + "base64" + ); + const { + signature, + pub_key: { value: pubkey }, + } = stdSig; + return { document, signature, pubkey }; + }) + ); +}; const run = createAsyncThunk( - "console/run", - async ( - command: (msgObj: any) => Promise, - { getState } - ): Promise => { - let result = ""; - try { - const msgObj = JSON.parse((getState() as RootState).console.input); - const resObj = await command(msgObj); - result = JSON.stringify(resObj, null, 2); - } catch (e) { - if (e instanceof SyntaxError) { - throw new ConsoleError(`Invalid JSON: ${e.message}`); - } else if (e instanceof Error) { - throw new ConsoleError(`Error: ${e.message}`); - } else { - throw new ConsoleError(`Unknown error: ${e}`); - } - } + "console/run", + async ( + command: (msgObj: any) => Promise, + { getState } + ): Promise => { + let result = ""; + try { + const msgObj = JSON.parse((getState() as RootState).console.input); + const resObj = await command(msgObj); + result = JSON.stringify(resObj, null, 2); + } catch (e) { + if (e instanceof SyntaxError) { + throw new ConsoleError(`Invalid JSON: ${e.message}`); + } else if (e instanceof Error) { + throw new ConsoleError(`Error: ${e.message}`); + } else { + throw new ConsoleError(`Unknown error: ${e}`); + } + } - return result; - } + return result; + } ); export const query = (): AppThunk => (dispatch, getState) => { - dispatch( - run(async (queryObj) => { - const contract = selectedContract(getState()); - if (!contract) throw new Error("No contract selected"); - const client = await connectionManager.getQueryClient( - getState().connection.config - ); - return client.queryContractSmart(contract.address, queryObj); - }) - ); + dispatch( + run(async (queryObj) => { + const contract = selectedContract(getState()); + if (!contract) throw new Error("No contract selected"); + const conn = await connectionManager.getQueryClient( + getState().connection.config + ); + return conn?.client?.wasm.queryContractSmart( + contract.address, + queryObj + ); + }) + ); }; export const execute = - ({ - memo, - funds, - }: { - memo?: string; - funds?: string; - } = {}): AppThunk => + ({ + memo, + funds, + }: { + memo?: string; + funds?: string; + } = {}): AppThunk => (dispatch, getState) => { - dispatch( - run(async (executeObj) => { - const config = getState().connection.config; - const contract = selectedContract(getState()); - const account = selectedAccount(getState()); - if (!contract) throw new Error("No contract selected"); - if (!account) throw new Error("No account selected"); - - const client = await connectionManager.getSigningClient( - account, - config - ); - - const executeOptions = getState().console.executeOptions; - const executeMemo = memo ?? executeOptions?.memo; - const executeFunds = funds ?? executeOptions?.funds; - - return client.execute( - account.address, - contract.address, - executeObj, - "auto", - executeMemo, - executeFunds - ? coins( - toMicroAmount(executeFunds, config["coinDecimals"]), - config["microDenom"] - ) - : undefined - ); - }) - ); + dispatch( + run(async (executeObj) => { + const config = getState().connection.config; + const contract = selectedContract(getState()); + const account = selectedAccount(getState()); + if (!contract) throw new Error("No contract selected"); + if (!account) throw new Error("No account selected"); + + const client = await connectionManager.getSigningClient( + account, + config + ); + + const executeOptions = getState().console.executeOptions; + const executeMemo = memo ?? executeOptions?.memo; + const executeFunds = funds ?? executeOptions?.funds; + + return client.execute( + account.address, + contract.address, + executeObj, + "auto", + executeMemo, + executeFunds + ? coins( + toMicroAmount( + executeFunds, + config["coinDecimals"] + ), + config["microDenom"] + ) + : undefined + ); + }) + ); }; export const consoleSlice = createSlice({ - name: "console", - initialState, - reducers: { - setInput: (state, action: PayloadAction) => { - state.input = action.payload; - }, - setOutput: (state, action: PayloadAction) => { - state.output = action.payload; - }, - setExecuteOptions: (state, action: PayloadAction) => { - state.executeOptions = action.payload; - }, - prettifyInput: (state, action: PayloadAction) => { - try { - state.input = JSON.stringify(JSON.parse(action.payload), null, 2); - } catch (e) { - if (e instanceof SyntaxError) { - state.output = `Invalid JSON: ${e.message}`; - } else if (e instanceof Error) { - state.output = `Error: ${e.message}`; - } - } + name: "console", + initialState, + reducers: { + setInput: (state, action: PayloadAction) => { + state.input = action.payload; + }, + setOutput: (state, action: PayloadAction) => { + state.output = action.payload; + }, + setErrorOutput: (state, action: PayloadAction) => { + state.errorOutput = action.payload; + }, + setExecuteOptions: (state, action: PayloadAction) => { + state.executeOptions = action.payload; + }, + setInstantiateOptions: ( + state, + action: PayloadAction + ) => { + state.instantiateOptions = action.payload; + }, + prettifyInput: (state, action: PayloadAction) => { + state.errorOutput = ""; + + try { + state.input = JSON.stringify( + JSON.parse(action.payload), + null, + 2 + ); + } catch (e) { + if (e instanceof SyntaxError) { + state.errorOutput = `Invalid JSON: ${e.message}`; + } else if (e instanceof Error) { + state.errorOutput = `Error: ${e.message}`; + } + } + }, + setOptionsOpen: (state, action: PayloadAction) => { + state.optionsOpen = action.payload; + }, + setInstantiateOpen: (state, action: PayloadAction) => { + state.instantiateOpen = action.payload; + }, }, - setOptionsOpen: (state, action: PayloadAction) => { - state.optionsOpen = action.payload; + extraReducers: (builder) => { + builder + .addCase(run.pending, (state) => { + state.output = "Loading..."; + }) + .addCase(run.fulfilled, (state, action) => { + state.output = action.payload; + state.error = false; + }) + .addCase(run.rejected, (state, action) => { + state.output = action.error.message ?? "Error"; + state.error = true; + }); }, - }, - extraReducers: (builder) => { - builder - .addCase(run.pending, (state) => { - state.output = "Loading..."; - }) - .addCase(run.fulfilled, (state, action) => { - state.output = action.payload; - state.error = false; - }) - .addCase(run.rejected, (state, action) => { - state.output = action.error.message ?? "Error"; - state.error = true; - }); - }, }); export const { - setInput, - setOutput, - prettifyInput, - setOptionsOpen, - setExecuteOptions, + setInput, + setOutput, + setErrorOutput, + prettifyInput, + setOptionsOpen, + setInstantiateOpen, + setExecuteOptions, + setInstantiateOptions, } = consoleSlice.actions; export default consoleSlice.reducer; diff --git a/src/features/console/toolboxSlice.ts b/src/features/console/toolboxSlice.ts new file mode 100644 index 0000000..e144255 --- /dev/null +++ b/src/features/console/toolboxSlice.ts @@ -0,0 +1,503 @@ +import { coins } from "@cosmjs/proto-signing"; +import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { AppThunk, RootState } from "../../app/store"; +import { toMicroAmount } from "../../util/coins"; +import { selectedAccount, selectedContract } from "../accounts/accountsSlice"; +import connectionManager from "../connection/connectionManager"; + +export const highlightColors = { + keyColor: "black", + numberColor: "blue", + stringColor: "#0B7500", + trueColor: "#00cc00", + falseColor: "#ff8080", + nullColor: "cornflowerblue", +}; + +export interface ExecuteOptions { + funds: string; + memo: string; +} + +export interface ToolboxState { + input: string; + output: string; + error: boolean; + optionsOpen: boolean; + executeOptions?: ExecuteOptions; + toolboxQueryOptions: { + [key: string]: { + [key: string]: {}; + }; + }; + toolboxQueryProcessing: boolean; + toolboxQueryReady: boolean; + toolboxExecuteOptions: { + [key: string]: { + [key: string]: {}; + }; + }; + toolboxExecuteProcessing: boolean; + toolboxExecuteReady: boolean; + stateReset: boolean; +} + +const QUERY_MASTER_KEY: string = "D7DFB68ED7C1BA3C1BA5184251C52"; +const EXECUTE_MASTER_KEY: string = "88B3A2ABFB8DFFB589B39C1FD4BF5"; + +const initialState: ToolboxState = { + input: "", + output: "Response will appear here", + error: false, + optionsOpen: false, + toolboxQueryOptions: {}, + toolboxQueryProcessing: false, + toolboxQueryReady: false, + toolboxExecuteOptions: {}, + toolboxExecuteProcessing: false, + toolboxExecuteReady: false, + stateReset: false, +}; + +class ToolboxError extends Error {} + +const run = createAsyncThunk( + "toolbox/run", + async ( + command: (msgObj: any) => Promise, + { getState } + ): Promise => { + let result = ""; + try { + let msgObj = {}; + if ((getState() as RootState)?.toolbox?.input.length > 0) { + msgObj = JSON.parse((getState() as RootState).toolbox.input); + } + const resObj = await command(msgObj); + result = JSON.stringify(resObj, null, 2); + } catch (e) { + if (e instanceof SyntaxError) { + throw new ToolboxError(`Invalid JSON: ${e.message}`); + } else if (e instanceof Error) { + throw new ToolboxError(`Error: ${e.message}`); + } else { + throw new ToolboxError(`Unknown error: ${e}`); + } + } + + return result; + } +); + +export const buildQueryToolbox = (): AppThunk => (dispatch, getState) => { + dispatch( + run(async () => { + dispatch(setToolboxQueryProcessing(false)); + dispatch(setToolboxQueryReady(false)); + + let queryObj: { [key: string]: {} } = {}; + queryObj[QUERY_MASTER_KEY] = {}; + const contract = selectedContract(getState()); + if (!contract) throw new Error("No contract selected"); + const conn = await connectionManager.getQueryClient( + getState().connection.config + ); + return conn?.client?.wasm.queryContractSmart( + contract.address, + queryObj + ); + }) + ); +}; + +interface IRunCmd { + cmd: string; + isLastCmd: boolean; +} + +const runQuery = createAsyncThunk( + "toolbox/runQuery", + async (command: (msgObj: any) => Promise, { getState, dispatch }) => { + try { + let msgObj = {}; + + const resObj = await command(msgObj); + let iRunCmd = resObj() as IRunCmd; + + let _state = getState() as RootState; + + let queryObj: { + [key: string]: { + [key: string]: {}; + }; + } = {}; + + queryObj[iRunCmd.cmd] = {}; + queryObj[iRunCmd.cmd][QUERY_MASTER_KEY] = {}; + + const contract = selectedContract(_state); + + if (!contract) throw new Error("No contract selected"); + + const conn = await connectionManager.getQueryClient( + _state.connection.config + ); + + const func = new Promise((resolve, reject) => { + resolve( + conn?.client?.wasm.queryContractSmart( + contract.address, + queryObj + ) + ); + }); + + await func + .then((res) => { + return res; + }) + .catch((e2) => { + if ("message" in e2) { + let regExp = /`(.*?)`/g; + let match = [...e2.message.matchAll(regExp)]; + + let toolboxOptions: IToolboxOptions = { + key: iRunCmd.cmd, + options: {}, + }; + + if (match && match.length > 0) { + match.forEach((cmd) => { + if ( + cmd && + cmd.length > 0 && + cmd[1] && + cmd[1] !== QUERY_MASTER_KEY + ) { + toolboxOptions.options[cmd[1]] = ""; + } + }); + } + + dispatch(setToolboxQueryOptions(toolboxOptions)); + + if (iRunCmd.isLastCmd) { + dispatch(setToolboxQueryReady(true)); + } + } + }); + } catch (e) {} + } +); + +export const toolboxQuery = + (cmd: string, isLastCmd: boolean): AppThunk => + (dispatch, _) => { + dispatch( + runQuery(async () => { + dispatch(setToolboxQueryReady(false)); + + return () => { + return { cmd, isLastCmd }; + }; + }) + ); + }; + +export const buildExecuteToolbox = + ({ + memo, + funds, + }: { + memo?: string; + funds?: string; + } = {}): AppThunk => + (dispatch, getState) => { + dispatch( + run(async () => { + dispatch(setToolboxExecuteProcessing(false)); + dispatch(setToolboxExecuteReady(false)); + let executeObj: { [key: string]: {} } = {}; + executeObj[EXECUTE_MASTER_KEY] = {}; + + const config = getState().connection.config; + const contract = selectedContract(getState()); + const account = selectedAccount(getState()); + if (!contract) throw new Error("No contract selected"); + if (!account) throw new Error("No account selected"); + + const client = await connectionManager.getSigningClient( + account, + config + ); + + const executeOptions = getState().toolbox.executeOptions; + const executeMemo = memo ?? executeOptions?.memo; + const executeFunds = funds ?? executeOptions?.funds; + + return client.execute( + account.address, + contract.address, + executeObj, + "auto", + executeMemo, + executeFunds + ? coins( + toMicroAmount( + executeFunds, + config["coinDecimals"] + ), + config["microDenom"] + ) + : undefined + ); + }) + ); + }; + +const runExecute = createAsyncThunk( + "toolbox/runExecute", + async (command: (msgObj: any) => Promise, { getState, dispatch }) => { + try { + let msgObj = {}; + + const resObj = await command(msgObj); + let iRunCmd = resObj() as IRunCmd; + + let _state = getState() as RootState; + + let executeObj: { + [key: string]: { + [key: string]: {}; + }; + } = {}; + + executeObj[iRunCmd.cmd] = {}; + executeObj[iRunCmd.cmd][EXECUTE_MASTER_KEY] = {}; + + const config = _state.connection.config; + const contract = selectedContract(_state); + const account = selectedAccount(_state); + if (!contract) throw new Error("No contract selected"); + if (!account) throw new Error("No account selected"); + + const client = await connectionManager.getSigningClient( + account, + config + ); + + const func = new Promise((resolve, reject) => { + resolve( + client.execute( + account.address, + contract.address, + executeObj, + "auto", + undefined, + undefined + ) + ); + }); + + await func + .then((res) => { + return res; + }) + .catch((e2) => { + if ("message" in e2) { + let regExp = /`(.*?)`/g; + let match = [...e2.message.matchAll(regExp)]; + + let toolboxOptions: IToolboxOptions = { + key: iRunCmd.cmd, + options: {}, + }; + + if (match && match.length > 0) { + match.forEach((cmd) => { + if ( + cmd && + cmd.length > 0 && + cmd[1] && + cmd[1] !== EXECUTE_MASTER_KEY + ) { + toolboxOptions.options[cmd[1]] = ""; + } + }); + } + + dispatch(setToolboxExecuteOptions(toolboxOptions)); + + if (iRunCmd.isLastCmd) { + dispatch(setToolboxExecuteReady(true)); + } + } + }); + } catch (e) {} + } +); + +export const toolboxExecute = + (cmd: string, isLastCmd: boolean): AppThunk => + (dispatch, _) => { + dispatch( + runExecute(async () => { + dispatch(setToolboxExecuteReady(false)); + + return () => { + return { cmd, isLastCmd }; + }; + }) + ); + }; + +interface IToolboxOptions { + key: string; + options: { + [key: string]: {}; + }; +} + +export const toolboxSlice = createSlice({ + name: "toolbox", + initialState, + reducers: { + setInput: (state, action: PayloadAction) => { + state.input = action.payload; + }, + setOutput: (state, action: PayloadAction) => { + state.output = action.payload; + }, + setExecuteOptions: (state, action: PayloadAction) => { + state.executeOptions = action.payload; + }, + setToolboxQueryProcessing: (state, action: PayloadAction) => { + state.toolboxQueryProcessing = action.payload; + }, + setToolboxQueryReady: (state, action: PayloadAction) => { + state.toolboxQueryReady = action.payload; + }, + setToolboxQueryOptions: ( + state, + action: PayloadAction + ) => { + if (action.payload) { + let newObj = { ...state.toolboxQueryOptions }; + let newSubObj = Object.assign( + {}, + state.toolboxQueryOptions[action.payload.key] ?? {}, + action.payload.options ?? {} + ); + + newObj[action.payload.key] = newSubObj; + + state.toolboxQueryOptions = newObj; + } + }, + setToolboxExecuteProcessing: ( + state, + action: PayloadAction + ) => { + state.toolboxExecuteProcessing = action.payload; + }, + setToolboxExecuteReady: (state, action: PayloadAction) => { + state.toolboxExecuteReady = action.payload; + }, + setToolboxExecuteOptions: ( + state, + action: PayloadAction + ) => { + if (action.payload) { + let newObj = { ...state.toolboxExecuteOptions }; + let newSubObj = Object.assign( + {}, + state.toolboxExecuteOptions[action.payload.key] ?? {}, + action.payload.options ?? {} + ); + + newObj[action.payload.key] = newSubObj; + + state.toolboxExecuteOptions = newObj; + } + }, + resetState: (state) => { + state.toolboxQueryOptions = {}; + state.toolboxQueryProcessing = false; + state.toolboxQueryReady = false; + state.toolboxExecuteOptions = {}; + state.toolboxExecuteProcessing = false; + state.toolboxExecuteReady = false; + state.stateReset = true; + }, + }, + extraReducers: (builder) => { + builder + .addCase(run.pending, (state) => { + state.output = "Loading..."; + }) + .addCase(run.fulfilled, (state, action) => { + state.output = action.payload; + state.error = false; + }) + .addCase(run.rejected, (state, action) => { + if (action.error.message?.includes(QUERY_MASTER_KEY)) { + state.stateReset = false; + state.toolboxQueryOptions = {}; + + let regExp = /`(.*?)`/g; + let match = [...action.error.message.matchAll(regExp)]; + + if (match && match.length > 0) { + match.forEach((cmd) => { + if ( + cmd && + cmd.length > 0 && + cmd[1] && + cmd[1] !== QUERY_MASTER_KEY + ) { + state.toolboxQueryOptions[cmd[1]] = {}; + } + }); + } + + state.toolboxQueryProcessing = true; + } else if (action.error.message?.includes(EXECUTE_MASTER_KEY)) { + state.stateReset = false; + state.toolboxExecuteOptions = {}; + + let regExp = /`(.*?)`/g; + let match = [...action.error.message.matchAll(regExp)]; + + if (match && match.length > 0) { + match.forEach((cmd) => { + if ( + cmd && + cmd.length > 0 && + cmd[1] && + cmd[1] !== EXECUTE_MASTER_KEY + ) { + state.toolboxExecuteOptions[cmd[1]] = {}; + } + }); + } + + state.toolboxExecuteProcessing = true; + } + }); + }, +}); + +export const { + setInput, + setOutput, + setExecuteOptions, + setToolboxQueryProcessing, + setToolboxQueryReady, + setToolboxQueryOptions, + setToolboxExecuteProcessing, + setToolboxExecuteReady, + setToolboxExecuteOptions, + resetState, +} = toolboxSlice.actions; + +export default toolboxSlice.reducer; diff --git a/src/features/tools/address_component.tsx b/src/features/tools/address_component.tsx new file mode 100644 index 0000000..5a9f0e2 --- /dev/null +++ b/src/features/tools/address_component.tsx @@ -0,0 +1,121 @@ +import { useState } from "react"; +import { CopyToClipboard } from "react-copy-to-clipboard"; +import { Chain } from "@chain-registry/types"; +import { Tooltip } from "primereact/tooltip"; + +export const AddressComponent = (props: { + address: string; + customHeader?: string; + showFullAddress?: boolean; + chain?: Chain; + bech32_prefix?: string; + chain_name?: string; +}) => { + const [copied, setCopied] = useState(false); + + const updateCopied = () => { + setCopied(true); + + setTimeout(() => { + setCopied(false); + }, 1500); + }; + + return ( +
+ +
+
+ + updateCopied()} + > + + + +
+
+
+ + {props.customHeader ?? "Chain"}: + + + {props.chain_name ?? props.chain?.chain_name} + +
+
+ {props.showFullAddress ? ( + <> + + {props.bech32_prefix ?? + props.chain?.bech32_prefix} + + + {props.address.replace( + props.bech32_prefix ?? + props.chain?.bech32_prefix ?? + "", + "" + )} + + + ) : ( +
+ + {props.bech32_prefix ?? + props.chain?.bech32_prefix} + + + {props.address + .replace( + props.bech32_prefix ?? + props.chain?.bech32_prefix ?? + "", + "" + ) + .substring(0, 7)} + ... + {props.address + .replace( + props.bech32_prefix ?? + props.chain?.bech32_prefix ?? + "", + "" + ) + .substring( + props.address.replace( + props.bech32_prefix ?? + props.chain + ?.bech32_prefix ?? + "", + "" + ).length - 6, + props.address.replace( + props.bech32_prefix ?? + props.chain + ?.bech32_prefix ?? + "", + "" + ).length + )} + +
+ )} +
+
+
+
+ ); +}; diff --git a/src/features/tools/bechconverter.tsx b/src/features/tools/bechconverter.tsx new file mode 100644 index 0000000..1d2d5ba --- /dev/null +++ b/src/features/tools/bechconverter.tsx @@ -0,0 +1,161 @@ +import { Divider } from "primereact/divider"; +import { InputText } from "primereact/inputtext"; + +import { chains } from "chain-registry"; +import { useEffect, useState } from "react"; + +import { fromBech32, toBech32 } from "@cosmjs/encoding"; +import { AddressComponent } from "./address_component"; +import { Chain } from "@chain-registry/types"; + +export const BechConverter = () => { + const [fromAddress, setFromAddress] = useState(""); + const [addresses, setAddresses] = useState([]); + const [targetChainPrefix, setTargetChainPrefix] = useState(""); + const [targetChainResult, setTargetChainResult] = useState< + JSX.Element | undefined + >(undefined); + + useEffect(() => { + if (fromAddress) { + let newAddresses: JSX.Element[] = []; + try { + let decoded = fromBech32(fromAddress, Infinity); + + let chainAddresses: Map = new Map(); + + chains.forEach((chain) => { + if ( + chain && + chain.bech32_prefix && + chain.bech32_prefix.length > 0 + ) { + let newChainAddr = toBech32( + chain.bech32_prefix, + decoded.data, + Infinity + ); + + if (!chainAddresses.has(newChainAddr)) { + chainAddresses.set(newChainAddr, chain); + } + } + }); + + if ([...chainAddresses.keys()].length > 0) { + let validAddresses = [...chainAddresses.keys()].sort(); + + newAddresses = validAddresses.map((key) => { + let chain = chainAddresses.get(key)!; + + return ( + + ); + }); + } + + setAddresses(newAddresses); + + if (targetChainPrefix && targetChainPrefix.length > 0) { + let newChainAddr = toBech32( + targetChainPrefix, + decoded.data, + Infinity + ); + + setTargetChainResult( + + ); + } else { + setTargetChainResult(undefined); + } + } catch (e) {} + } + }, [fromAddress, targetChainPrefix]); + + return ( +
+

Bech Converter

+ +
+
+ +
+ + + + + setFromAddress( + (e.target as HTMLInputElement).value + ) + } + /> +
+
+
+ +
+ + + + + setTargetChainPrefix( + (e.target as HTMLInputElement).value + ) + } + /> +
+
+
+ + {targetChainResult && ( +
+
+ Target Prefix +
+ {targetChainResult} + +
+ )} + {addresses && addresses.length > 0 ? ( + <> +
+ All chains in registry +
+
{addresses}
+ + ) : ( + "Enter a valid address" + )} +
+ ); +}; diff --git a/src/features/tools/msgsigner.tsx b/src/features/tools/msgsigner.tsx new file mode 100644 index 0000000..40915b8 --- /dev/null +++ b/src/features/tools/msgsigner.tsx @@ -0,0 +1,235 @@ +import { SlButton, SlButtonGroup } from "@shoelace-style/shoelace/dist/react"; + +import { Divider } from "primereact/divider"; +import { InputText } from "primereact/inputtext"; +import { InputTextarea } from "primereact/inputtextarea"; +//import { Tooltip } from "primereact/tooltip"; +import { Tooltip } from 'react-tooltip' +import { useEffect, useLayoutEffect, useRef, useState } from "react"; +import styles from "../console/Input.module.css"; +import { useAppDispatch, useAppSelector } from "../../app/hooks"; +import { AppThunk } from "../../app/store"; +import { resetState, sign, verify } from "./signingSlice"; +import { useSearchParams } from "react-router-dom"; +import { buildMsgSignerLink } from "../../util/common"; +import { CopyToClipboard } from "react-copy-to-clipboard"; + +export const MsgSigner = () => { + const dispatch = useAppDispatch(); + const [message, setMessage] = useState(""); + const [address, setAddress] = useState(""); + const [signature, setSignature] = useState(""); + const [msgVerified, setMsgVerified] = useState(undefined); + const signedMsg = useAppSelector((state) => state.signing.signedMsg); + const output = useAppSelector((state) => state.signing.output); + let [searchParams, _] = useSearchParams(); + + const [ttIsOpen, setTtIsOpen] = useState(false); + + const currentAccount = useAppSelector( + (state) => state.accounts.currentAccount + ); + + function run(action: AppThunk) { + dispatch(action); + } + + useLayoutEffect(() => { + dispatch(resetState()); + }, []); + + useEffect(() => { + if (signedMsg && signedMsg.address && signedMsg.signature) { + setAddress(signedMsg.address); + setSignature(signedMsg.signature); + + if (signedMsg.msg && message && signedMsg.msg !== message) { + setMessage(signedMsg.msg); + } + } + + setMsgVerified(signedMsg?.verified); + }, [signedMsg]); + + const runSignMsg = () => { + run(sign(message)); + }; + + const runVerifyMsg = () => { + run(verify(message, address, signature)); + }; + + useEffect(() => { + if ([...searchParams.keys()].length > 0) { + setAddress( + decodeURIComponent(searchParams.get("address") ?? address) + ); + setMessage( + decodeURIComponent(searchParams.get("message") ?? message) + ); + setSignature( + decodeURIComponent(searchParams.get("signature") ?? signature) + ); + } + }, [searchParams]); + + const [copied, setCopied] = useState(false); + + const updateCopied = (e: any) => { + setCopied(true); + setTtIsOpen(true); + + setTimeout(() => { + setCopied(false); + setTtIsOpen(false); + }, 1500); + }; + + return ( +
+
+

Message Sign & Verify

+ +
+ +
+ + setMessage((e.target as HTMLInputElement).value) + } + /> +
+
+
+
+ + + setAddress((e.target as HTMLInputElement).value) + } + /> +
+
+
+
+ + + setSignature((e.target as HTMLInputElement).value) + } + /> +
+
+
+ + + + { + if (currentAccount !== undefined) { + runSignMsg(); + } + }} + variant="neutral" + outline + disabled={currentAccount === undefined} + > + Sign + + { + runVerifyMsg(); + }} + variant="neutral" + outline + > + Verify + + +
+ +
+
+
Share me!
+ + +
+
+
+ +
+
+ + {buildMsgSignerLink( + message, + address, + signature + )} + +
+
+
+
+
+
+

{output}

+
+
+ ); +}; diff --git a/src/features/tools/signingSlice.ts b/src/features/tools/signingSlice.ts new file mode 100644 index 0000000..925996b --- /dev/null +++ b/src/features/tools/signingSlice.ts @@ -0,0 +1,200 @@ +import { BaseAccount } from "cosmjs-types/cosmos/auth/v1beta1/auth"; +import { Secp256k1HdWallet } from "@cosmjs/launchpad"; +import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import { AppThunk } from "../../app/store"; +import { selectedAccount, AccountType } from "../accounts/accountsSlice"; +import { StdSignature } from "@cosmjs/amino"; +import { getKeplr } from "../accounts/useKeplr"; +import { makeADR36AminoSignDoc } from "../../util/adr36"; +import { verifyADR36Amino } from "@keplr-wallet/cosmos"; +import connectionManager from "../connection/connectionManager"; +import { fromBase64 } from "@cosmjs/encoding"; + +class ConsoleError extends Error {} + +export interface SigningState { + signedMsg: SignedMsg; + output: string; +} + +export interface SignedMsg { + verified?: boolean; + address?: string; + signature?: string; + msg?: string; +} + +const initialState: SigningState = { + signedMsg: {}, + output: "", +}; + +export const sign = + (msg: string): AppThunk => + (dispatch, getState) => { + dispatch( + run(async () => { + const account = selectedAccount(getState()); + if (!account) throw new Error("No account selected"); + + const signDoc = makeADR36AminoSignDoc(account.address, msg); + + let stdSig: StdSignature; + + if (account.type === AccountType.Keplr) { + const keplr = await getKeplr(); + const chainId = getState().connection.config["chainId"]; + + stdSig = await keplr.signArbitrary( + chainId, + account.address, + msg + ); + } else if (account.type === AccountType.Basic) { + const wallet = await Secp256k1HdWallet.fromMnemonic( + account.mnemonic, + { + prefix: getState().connection.config[ + "addressPrefix" + ], + } + ); + + stdSig = (await wallet.signAmino(account.address, signDoc)) + .signature; + } else { + throw new Error("Invalid account type"); + } + + const { signature } = stdSig; + return { + address: account.address, + msg, + signature, + } as SignedMsg; + }) + ); + }; + +// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 +// https://github.com/cosmos/cosmos-sdk/blob/main/types/bech32/bech32.go +// https://github.com/cosmos/cosmjs/issues/1053 +// (prefix)(optional secondary prefix)(1)(6 alphanumeric) +// separator is "1" +// data is 6 chars alpha numeric excluding 1, b, i, o +// ^(.*?)(pub|valoper|valoperpub|valcons|valconspub)?(1)([02-9ac-hj-np-z]{6,})$ +// need to extract and store secondary prefix + +export const verify = + (msg: string, address: string, signature: string): AppThunk => + (dispatch, getState) => { + dispatch( + run(async () => { + const conn = await connectionManager.getQueryClient( + getState().connection.config + ); + let account = await conn?.accountQueryService?.Account({ + address, + }); + + if (account && account?.account?.value) { + let baseAccount = BaseAccount.decode( + account?.account?.value + ); + + if (baseAccount.pubKey) { + try { + // baseAccount.pubKey.value is in "/cosmos.crypto.secp256k1.PubKey" format, to get to + // "tendermint/PubKeySecp256k1" format we just have to trim the first 2 bytes off + // the first 2 bytes repesent 0xEB which indicates the secp256k1 curve and the 2nd byte + // is the key type which could be either 0x02 or 0x03 + const tmPubkeyBase = + baseAccount.pubKey.value.slice(2); + const data = verifyADR36Amino( + getState().connection.config["addressPrefix"], + address, + msg, + tmPubkeyBase, + fromBase64(signature) + ); + + return { + verified: data, + }; + } catch (e) { + throw e; + } + } + } + + return { + address: "", + msg, + signature, + } as SignedMsg; + }) + ); + }; + +const run = createAsyncThunk( + "signing/run", + async (command: () => Promise): Promise => { + let result: SignedMsg = {}; + try { + result = await command(); + } catch (e) { + if (e instanceof SyntaxError) { + throw new ConsoleError(`Invalid JSON: ${e.message}`); + } else if (e instanceof Error) { + throw new ConsoleError(`Error: ${e.message}`); + } else { + throw new ConsoleError(`Unknown error: ${e}`); + } + } + + return result; + } +); + +export const signingSlice = createSlice({ + name: "signing", + initialState, + reducers: { + setOutput: (state, action: PayloadAction) => { + state.output = action.payload; + }, + resetState: (state) => { + state.output = "Awaiting orders" + state.signedMsg = initialState.signedMsg; + } + }, + extraReducers: (builder) => { + builder + .addCase(run.pending, (state) => { + state.signedMsg = {}; + state.output = "Processing..."; + }) + .addCase(run.fulfilled, (state, action) => { + if (action.payload.verified === undefined) { + state.output = "Message signed!"; + state.signedMsg = action.payload; + } else { + let outputMsg = + "The message does not match the signature for this address."; + + if (action.payload.verified === true) { + outputMsg = "The message and signature match!"; + } + state.output = outputMsg; + state.signedMsg = action.payload; + } + }) + .addCase(run.rejected, (state, action) => { + state.output = action.error.message ?? "Error"; + }); + }, +}); + +export const { resetState, setOutput } = signingSlice.actions; + +export default signingSlice.reducer; diff --git a/src/index.css b/src/index.css index ec2585e..3e3b6a1 100644 --- a/src/index.css +++ b/src/index.css @@ -10,4 +10,4 @@ body { code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; -} +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 09d8717..93779e5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,21 +4,24 @@ import "./index.css"; import App from "./App"; import { persistor, store } from "./app/store"; import { Provider } from "react-redux"; -import * as serviceWorker from "./serviceWorker"; import { PersistGate } from "redux-persist/es/integration/react"; +import { BrowserRouter } from "react-router-dom"; + +import "primereact/resources/themes/lara-light-indigo/theme.css"; //theme +import "primereact/resources/primereact.min.css"; //core css +import "primeicons/primeicons.css"; //icons +import 'react-tooltip/dist/react-tooltip.css' + ReactDOM.render( - - - - - - - , - document.getElementById("root") + + + + + + + + + , + document.getElementById("root") ); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index a391971..25f1680 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1,2 +1,3 @@ /// declare module "json-format-highlight"; +declare module "react-copy-to-clipboard"; \ No newline at end of file diff --git a/src/serviceWorker.ts b/src/serviceWorker.ts deleted file mode 100644 index b72854a..0000000 --- a/src/serviceWorker.ts +++ /dev/null @@ -1,146 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA - -const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); - -type Config = { - onSuccess?: (registration: ServiceWorkerRegistration) => void; - onUpdate?: (registration: ServiceWorkerRegistration) => void; -}; - -export function register(config?: Config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } - - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); - }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } -} - -function registerValidSW(swUrl: string, config?: Config) { - navigator.serviceWorker - .register(swUrl) - .then((registration) => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch((error) => { - console.error('Error during service worker registration:', error); - }); -} - -function checkValidServiceWorker(swUrl: string, config?: Config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, - }) - .then((response) => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then((registration) => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); -} - -export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready - .then((registration) => { - registration.unregister(); - }) - .catch((error) => { - console.error(error.message); - }); - } -} diff --git a/src/setupTests.ts b/src/setupTests.ts deleted file mode 100644 index 74b1a27..0000000 --- a/src/setupTests.ts +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom/extend-expect'; diff --git a/src/util/common.ts b/src/util/common.ts new file mode 100644 index 0000000..1b30448 --- /dev/null +++ b/src/util/common.ts @@ -0,0 +1,8 @@ +export function buildMsgSignerLink( + message: string, + address: string, + signature: string +): string { + const url = process.env.REACT_APP_PUBLIC_URL; + return encodeURI(`${url}/msgsigner?message=${message}&address=${address}&signature=${signature}`); +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..7310ed1 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + content: ["./src/**/*.{html,js,jsx,ts,tsx}"], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/tsconfig.json b/tsconfig.json index f4261ee..7a0557c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,8 @@ "jsx": "react-jsx", "downlevelIteration": true }, + "paths": { + "cosmjs-types/*": ["node_modules/cosmjs-types/*"] + }, "include": ["src", "*.d.ts"] } diff --git a/yarn.lock b/yarn.lock index 3baea06..9724082 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1031,6 +1031,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.19.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -1069,15 +1076,20 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@confio/ics23@^0.6.3": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@confio/ics23/-/ics23-0.6.5.tgz#9c21a61089d4c3c2429875a69d6d9cd8c87512aa" - integrity sha512-1GdPMsaP/l8JSF4P4HWFLBhdcxHcJT8lS0nknBYNSZ1XrJOsJKUy6EkOwd9Pa1qJkXzY2gyNv7MdHR+AIwSTAg== +"@chain-registry/types@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@chain-registry/types/-/types-0.14.0.tgz#43ea04992adabdee2a0f03f8a519b01722ab354b" + integrity sha512-TlIqc3CijT734no7RiYBfUvCG2fory0blwrBcK4XTYOCi2vANsxfDdiPLFQcaSETYDd14DdjhrdXwMocEeOnLQ== + dependencies: + "@babel/runtime" "^7.19.4" + +"@confio/ics23@^0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@confio/ics23/-/ics23-0.6.8.tgz#2a6b4f1f2b7b20a35d9a0745bb5a446e72930b3d" + integrity sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w== dependencies: - js-sha512 "^0.8.0" + "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" - ripemd160 "^2.0.2" - sha.js "^2.4.11" "@cosmjs/amino@0.27.1": version "0.27.1" @@ -1089,23 +1101,42 @@ "@cosmjs/math" "0.27.1" "@cosmjs/utils" "0.27.1" -"@cosmjs/cosmwasm-stargate@^0.27.1": - version "0.27.1" - resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.27.1.tgz#7c90517f07ea652d06e8c8d0f6f14b10f3a50698" - integrity sha512-miEAYH4k0YPHRGmp5NTN93lrMg2opxZjr2d4fpRD8H3VVngP4+uUmiI2aUZpHTejlPjqrSTGQnPyycRVMHEFsw== - dependencies: - "@cosmjs/amino" "0.27.1" - "@cosmjs/crypto" "0.27.1" - "@cosmjs/encoding" "0.27.1" - "@cosmjs/math" "0.27.1" - "@cosmjs/proto-signing" "0.27.1" - "@cosmjs/stargate" "0.27.1" - "@cosmjs/tendermint-rpc" "0.27.1" - "@cosmjs/utils" "0.27.1" - cosmjs-types "^0.4.0" +"@cosmjs/amino@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.5.tgz#053b4739a90b15b9e2b781ccd484faf64bd49aec" + integrity sha512-Qo8jpC0BiziTSUqpkNatBcwtKNhCovUnFul9SlT/74JUCdLYaeG5hxr3q1cssQt++l4LvlcpF+OUXL48XjNjLw== + dependencies: + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + +"@cosmjs/amino@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.30.1.tgz#7c18c14627361ba6c88e3495700ceea1f76baace" + integrity sha512-yNHnzmvAlkETDYIpeCTdVqgvrdt1qgkOXwuRVi8s27UKI5hfqyE9fJ/fuunXE6ZZPnKkjIecDznmuUOMrMvw4w== + dependencies: + "@cosmjs/crypto" "^0.30.1" + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + +"@cosmjs/cosmwasm-stargate@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.5.tgz#3f257da682658833e0f4eb9e8ff758e4d927663a" + integrity sha512-TNdSvm2tEE3XMCuxHxquzls56t40hC8qnLeYJWHsY2ECZmRK3KrnpRReEr7N7bLtODToK7X/riYrV0JaYxjrYA== + dependencies: + "@cosmjs/amino" "^0.29.5" + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/proto-signing" "^0.29.5" + "@cosmjs/stargate" "^0.29.5" + "@cosmjs/tendermint-rpc" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + cosmjs-types "^0.5.2" long "^4.0.0" pako "^2.0.2" - protobufjs "~6.10.2" "@cosmjs/crypto@0.27.1": version "0.27.1" @@ -1141,6 +1172,32 @@ sha.js "^2.4.11" unorm "^1.5.0" +"@cosmjs/crypto@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.5.tgz#ab99fc382b93d8a8db075780cf07487a0f9519fd" + integrity sha512-2bKkaLGictaNL0UipQCL6C1afaisv6k8Wr/GCLx9FqiyFkh9ZgRHDyetD64ZsjnWV/N/D44s/esI+k6oPREaiQ== + dependencies: + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + "@noble/hashes" "^1" + bn.js "^5.2.0" + elliptic "^6.5.4" + libsodium-wrappers "^0.7.6" + +"@cosmjs/crypto@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.30.1.tgz#21e94d5ca8f8ded16eee1389d2639cb5c43c3eb5" + integrity sha512-rAljUlake3MSXs9xAm87mu34GfBLN0h/1uPPV6jEwClWjNkAMotzjC0ab9MARy5FFAvYHL3lWb57bhkbt2GtzQ== + dependencies: + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + "@noble/hashes" "^1" + bn.js "^5.2.0" + elliptic "^6.5.4" + libsodium-wrappers "^0.7.6" + "@cosmjs/encoding@0.27.1": version "0.27.1" resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.27.1.tgz#3cd5bc0af743485eb2578cdb08cfa84c86d610e1" @@ -1168,19 +1225,37 @@ bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/faucet-client@^0.27.1": - version "0.27.1" - resolved "https://registry.yarnpkg.com/@cosmjs/faucet-client/-/faucet-client-0.27.1.tgz#f23904c99235effc3f28582a30e4f30fc2315d27" - integrity sha512-w2x0KI4e4ijYMl2O5qNTEVMv2V4Bc9HuiVWMdh8aJmLJgBFkfixwvVbq3Y6Oss1B92QmmH8cAnWQ2sXcq7Jivw== +"@cosmjs/encoding@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.5.tgz#009a4b1c596cdfd326f30ccfa79f5e56daa264f2" + integrity sha512-G4rGl/Jg4dMCw5u6PEZHZcoHnUBlukZODHbm/wcL4Uu91fkn5jVo5cXXZcvs4VCkArVGrEj/52eUgTZCmOBGWQ== + dependencies: + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" + +"@cosmjs/encoding@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.30.1.tgz#b5c4e0ef7ceb1f2753688eb96400ed70f35c6058" + integrity sha512-rXmrTbgqwihORwJ3xYhIgQFfMSrwLu1s43RIK9I8EBudPx3KmnmyAKzMOVsRDo9edLFNuZ9GIvysUCwQfq3WlQ== + dependencies: + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" + +"@cosmjs/faucet-client@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/faucet-client/-/faucet-client-0.29.5.tgz#696412a747888690c6d925a2f1610e8338deb882" + integrity sha512-9H/bp+oHYixVUFMf4+jdvuvYGoJN2R990LyXgi+qsxT3fRO6Z/8pnzkUW7dYXelqLVXAm6JSsj7UZ6yyk4p7GA== dependencies: axios "^0.21.2" -"@cosmjs/json-rpc@0.27.1": - version "0.27.1" - resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.27.1.tgz#ce0a6157f57a76e964587ceb9027884bc4ffe701" - integrity sha512-AKvsllGr6oN5kiroatIeIIxRdCFetLd8LCWV04RRNkoJ2OefDNb46VlWEQ+gI3ay5GgfVjB9qAcfvbJyrcEv+A== +"@cosmjs/json-rpc@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.5.tgz#5e483a9bd98a6270f935adf0dfd8a1e7eb777fe4" + integrity sha512-C78+X06l+r9xwdM1yFWIpGl03LhB9NdM1xvZpQHwgCOl0Ir/WV8pw48y3Ez2awAoUBRfTeejPe4KvrE6NoIi/w== dependencies: - "@cosmjs/stream" "0.27.1" + "@cosmjs/stream" "^0.29.5" xstream "^11.14.0" "@cosmjs/launchpad@^0.24.0-alpha.25", "@cosmjs/launchpad@^0.24.1": @@ -1229,17 +1304,19 @@ dependencies: bn.js "^4.11.8" -"@cosmjs/proto-signing@0.27.1", "@cosmjs/proto-signing@^0.27.1": - version "0.27.1" - resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.27.1.tgz#1f8f662550aab012d957d02f43c77d914c2ae0db" - integrity sha512-t7/VvQivMdM1KgKWai/9ZCEcGFXJtr9Xo0hGcPLTn9wGkh9tmOsUXINYVMsf5D/jWIm1MDPbGCYfdb9V1Od4hw== +"@cosmjs/math@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.5.tgz#722c96e080d6c2b62215ce9f4c70da7625b241b6" + integrity sha512-2GjKcv+A9f86MAWYLUkjhw1/WpRl2R1BTb3m9qPG7lzMA7ioYff9jY5SPCfafKdxM4TIQGxXQlYGewQL16O68Q== dependencies: - "@cosmjs/amino" "0.27.1" - "@cosmjs/crypto" "0.27.1" - "@cosmjs/math" "0.27.1" - cosmjs-types "^0.4.0" - long "^4.0.0" - protobufjs "~6.10.2" + bn.js "^5.2.0" + +"@cosmjs/math@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.30.1.tgz#8b816ef4de5d3afa66cb9fdfb5df2357a7845b8a" + integrity sha512-yaoeI23pin9ZiPHIisa6qqLngfnBR/25tSaWpkTm8Cy10MX70UF5oN4+/t1heLaM6SSmRrhk3psRkV4+7mH51Q== + dependencies: + bn.js "^5.2.0" "@cosmjs/proto-signing@^0.24.0-alpha.25": version "0.24.1" @@ -1250,52 +1327,79 @@ long "^4.0.0" protobufjs "~6.10.2" -"@cosmjs/socket@0.27.1": - version "0.27.1" - resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.27.1.tgz#c7a3eceb15efb9874a048c3238d1f0b185185742" - integrity sha512-bKCRsaSXh/TA7efxVCogzS2K3cgC40Ge2itFYmTfgpOE+++52FchCblVCsCYwMNDLS497RP4P0GbeC1VEBToMA== +"@cosmjs/proto-signing@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.5.tgz#af3b62a46c2c2f1d2327d678b13b7262db1fe87c" + integrity sha512-QRrS7CiKaoETdgIqvi/7JC2qCwCR7lnWaUsTzh/XfRy3McLkEd+cXbKAW3cygykv7IN0VAEIhZd2lyIfT8KwNA== + dependencies: + "@cosmjs/amino" "^0.29.5" + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + cosmjs-types "^0.5.2" + long "^4.0.0" + +"@cosmjs/proto-signing@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.30.1.tgz#f0dda372488df9cd2677150b89b3e9c72b3cb713" + integrity sha512-tXh8pPYXV4aiJVhTKHGyeZekjj+K9s2KKojMB93Gcob2DxUjfKapFYBMJSgfKPuWUPEmyr8Q9km2hplI38ILgQ== + dependencies: + "@cosmjs/amino" "^0.30.1" + "@cosmjs/crypto" "^0.30.1" + "@cosmjs/encoding" "^0.30.1" + "@cosmjs/math" "^0.30.1" + "@cosmjs/utils" "^0.30.1" + cosmjs-types "^0.7.1" + long "^4.0.0" + +"@cosmjs/socket@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.5.tgz#a48df6b4c45dc6a6ef8e47232725dd4aa556ac2d" + integrity sha512-5VYDupIWbIXq3ftPV1LkS5Ya/T7Ol/AzWVhNxZ79hPe/mBfv1bGau/LqIYOm2zxGlgm9hBHOTmWGqNYDwr9LNQ== dependencies: - "@cosmjs/stream" "0.27.1" + "@cosmjs/stream" "^0.29.5" isomorphic-ws "^4.0.1" ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@0.27.1": - version "0.27.1" - resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.27.1.tgz#0abc1f91e5cc421940c920f16a22c6c93cc774d5" - integrity sha512-7hAIyNd6NbhQA51w9mPVyMYw515Hpj0o7SXMaqbc7nxs3hkJNMONQ9RakyMm0U/WeCd6ObcSaPEcdkqbfkc+mg== - dependencies: - "@confio/ics23" "^0.6.3" - "@cosmjs/amino" "0.27.1" - "@cosmjs/encoding" "0.27.1" - "@cosmjs/math" "0.27.1" - "@cosmjs/proto-signing" "0.27.1" - "@cosmjs/stream" "0.27.1" - "@cosmjs/tendermint-rpc" "0.27.1" - "@cosmjs/utils" "0.27.1" - cosmjs-types "^0.4.0" +"@cosmjs/stargate@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.5.tgz#d597af1c85a3c2af7b5bdbec34d5d40692cc09e4" + integrity sha512-hjEv8UUlJruLrYGJcUZXM/CziaINOKwfVm2BoSdUnNTMxGvY/jC1ABHKeZUYt9oXHxEJ1n9+pDqzbKc8pT0nBw== + dependencies: + "@confio/ics23" "^0.6.8" + "@cosmjs/amino" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/proto-signing" "^0.29.5" + "@cosmjs/stream" "^0.29.5" + "@cosmjs/tendermint-rpc" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + cosmjs-types "^0.5.2" long "^4.0.0" - protobufjs "~6.10.2" + protobufjs "~6.11.3" xstream "^11.14.0" -"@cosmjs/stream@0.27.1": - version "0.27.1" - resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.27.1.tgz#02f40856c0840e34ef11054da9e84e8196d37593" - integrity sha512-cEyEAVfXEyuUpKYBeEJrOj8Dp/c+M6a0oGJHxvDdhP5gSsaeCPgQXrh7qZFBiUfu3Brmqd+e/bKZm+068l9bBw== +"@cosmjs/stream@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.5.tgz#350981cac496d04939b92ee793b9b19f44bc1d4e" + integrity sha512-TToTDWyH1p05GBtF0Y8jFw2C+4783ueDCmDyxOMM6EU82IqpmIbfwcdMOCAm0JhnyMh+ocdebbFvnX/sGKzRAA== dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@0.27.1": - version "0.27.1" - resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.27.1.tgz#66f4a04d1b9ac5849ea2981c2e67bc229996a85a" - integrity sha512-eN1NyBYIiFutDNleEaTfvIJ3S3KA1gP45UHaLhSETm8KyiaUqg/b0Mj6sp7J3h4BhgwLq2zn/TDtIn0k5luedg== - dependencies: - "@cosmjs/crypto" "0.27.1" - "@cosmjs/encoding" "0.27.1" - "@cosmjs/json-rpc" "0.27.1" - "@cosmjs/math" "0.27.1" - "@cosmjs/socket" "0.27.1" - "@cosmjs/stream" "0.27.1" +"@cosmjs/tendermint-rpc@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.5.tgz#f205c10464212bdf843f91bb2e4a093b618cb5c2" + integrity sha512-ar80twieuAxsy0x2za/aO3kBr2DFPAXDmk2ikDbmkda+qqfXgl35l9CVAAjKRqd9d+cRvbQyb5M4wy6XQpEV6w== + dependencies: + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/json-rpc" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/socket" "^0.29.5" + "@cosmjs/stream" "^0.29.5" + "@cosmjs/utils" "^0.29.5" axios "^0.21.2" readonly-date "^1.0.0" xstream "^11.14.0" @@ -1315,6 +1419,16 @@ resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.24.1.tgz#0adfefe63b7f17222bc2bc12f71296f35e7ad378" integrity sha512-VA3WFx1lMFb7esp9BqHWkDgMvHoA3D9w+uDRvWhVRpUpDc7RYHxMbWExASjz+gNblTCg556WJGzF64tXnf9tdQ== +"@cosmjs/utils@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.5.tgz#3fed1b3528ae8c5f1eb5d29b68755bebfd3294ee" + integrity sha512-m7h+RXDUxOzEOGt4P+3OVPX7PuakZT3GBmaM/Y2u+abN3xZkziykD/NvedYFvvCCdQo714XcGl33bwifS9FZPQ== + +"@cosmjs/utils@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.30.1.tgz#6d92582341be3c2ec8d82090253cfa4b7f959edb" + integrity sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g== + "@craco/craco@^5.5.0": version "5.9.0" resolved "https://registry.yarnpkg.com/@craco/craco/-/craco-5.9.0.tgz#dcd34330b558596a4841374743071b7fa041dce9" @@ -1372,6 +1486,66 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@ethersproject/address@^5.6.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/keccak256@^5.5.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@floating-ui/core@^1.2.6": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.6.tgz#d21ace437cc919cdd8f1640302fa8851e65e75c0" + integrity sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg== + +"@floating-ui/dom@^1.0.0": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.6.tgz#bcf0c7bada97c20d9d1255b889f35bac838c63fe" + integrity sha512-02vxFDuvuVPs22iJICacezYJyf7zwwOCWkPNkWNBr1U0Qt1cKFYzWvxts0AmqcOQGwt/3KJWcWIgtbUU38keyw== + dependencies: + "@floating-ui/core" "^1.2.6" + "@humanwhocodes/config-array@^0.9.2": version "0.9.3" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.3.tgz#f2564c744b387775b436418491f15fce6601f63e" @@ -1650,6 +1824,62 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@keplr-wallet/common@0.11.56": + version "0.11.56" + resolved "https://registry.yarnpkg.com/@keplr-wallet/common/-/common-0.11.56.tgz#391d32984755a8085a48915ab13da41069a126d8" + integrity sha512-vs9QU2D4kryqPSPq3bejZnSBmA0x7FjfFVshf6wfgcfZg2E3rKqOkT+6bxkTE5jz1aYcieD7am/0SuE93IwoSg== + dependencies: + "@keplr-wallet/crypto" "0.11.56" + buffer "^6.0.3" + delay "^4.4.0" + +"@keplr-wallet/cosmos@^0.11.56": + version "0.11.56" + resolved "https://registry.yarnpkg.com/@keplr-wallet/cosmos/-/cosmos-0.11.56.tgz#c169e1743450dc9fe608306e80bb547175803a56" + integrity sha512-wMQ52aYLj811pg3rphcpYcjZjPFWWE0U1DY5WeEsHhCwPxEsdn03eucPapRU8M6SsuqmmOhLuDKxomHqS3Gjkw== + dependencies: + "@ethersproject/address" "^5.6.0" + "@keplr-wallet/common" "0.11.56" + "@keplr-wallet/crypto" "0.11.56" + "@keplr-wallet/proto-types" "0.11.56" + "@keplr-wallet/types" "0.11.56" + "@keplr-wallet/unit" "0.11.56" + axios "^0.27.2" + bech32 "^1.1.4" + buffer "^6.0.3" + long "^4.0.0" + protobufjs "^6.11.2" + +"@keplr-wallet/crypto@0.11.56": + version "0.11.56" + resolved "https://registry.yarnpkg.com/@keplr-wallet/crypto/-/crypto-0.11.56.tgz#0aafc7ac70af3d589b51b9915a1da4c95f077df5" + integrity sha512-ITP0S4B9k/GcjjZk5X6gOShqwQ3+jkgwWZD7E0HBUjl5zo9M83HbYuxwbxt4nnd4l3KLV8lUtsuggKwexrpECA== + dependencies: + "@ethersproject/keccak256" "^5.5.0" + bip32 "^2.0.6" + bip39 "^3.0.3" + bs58check "^2.1.2" + buffer "^6.0.3" + crypto-js "^4.0.0" + elliptic "^6.5.3" + sha.js "^2.4.11" + +"@keplr-wallet/proto-types@0.11.56": + version "0.11.56" + resolved "https://registry.yarnpkg.com/@keplr-wallet/proto-types/-/proto-types-0.11.56.tgz#2c03a19459c5be6353797be511effb745e8fca9e" + integrity sha512-op4uBWjL+TYdshD5IonY6joXJ4BxNKiSHVLpqHg58EOabroiPeg/U0y3ANR6h4cjFoZCbTGZVWV/nP84oW+8Sg== + dependencies: + long "^4.0.0" + protobufjs "^6.11.2" + +"@keplr-wallet/types@0.11.56": + version "0.11.56" + resolved "https://registry.yarnpkg.com/@keplr-wallet/types/-/types-0.11.56.tgz#a67d792c6241c85f72396aab3112acb659fffe31" + integrity sha512-NPqzuRLKQRXgCkA5pLi566wW54a/i1mqC23Dx9YQkBbY1EJW4rJlKzGXNnTqSwbJ4Gr1JDr6+rtkcyzoM5s2Uw== + dependencies: + axios "^0.27.2" + long "^4.0.0" + "@keplr-wallet/types@^0.9.12": version "0.9.12" resolved "https://registry.yarnpkg.com/@keplr-wallet/types/-/types-0.9.12.tgz#5771913343030a491c551a5a2eebb9cb0ad480f7" @@ -1661,6 +1891,15 @@ long "^4.0.0" secretjs "^0.17.0" +"@keplr-wallet/unit@0.11.56": + version "0.11.56" + resolved "https://registry.yarnpkg.com/@keplr-wallet/unit/-/unit-0.11.56.tgz#46ae269ada551da9dc3a4f3ee698b4e7f4311ddc" + integrity sha512-eOK5UfhmTtRvdx6+umqnEROVubzfnfJg85NL00HQFrmxuhBbmqcddzPyXDCdffmCMWdv/bpocd+YfIYTuaY4EA== + dependencies: + "@keplr-wallet/types" "0.11.56" + big-integer "^1.6.48" + utility-types "^3.10.0" + "@lit-labs/react@^1.0.1": version "1.0.2" resolved "https://registry.yarnpkg.com/@lit-labs/react/-/react-1.0.2.tgz#a9059c39d40a1598deb17fc57dd9ed7f924165fe" @@ -1671,6 +1910,11 @@ resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.2.2.tgz#bfa5539dfe4776899c3e3ba11e0e10224aeb9ff7" integrity sha512-HkhTTO2rT8jlf4izz7ME/+YUjqz+ZHgmnOKorA+7tkDmQDg6QzDpWSFz//1YyiL193W4bc7rlQCiYyFiZa9pkQ== +"@noble/hashes@^1", "@noble/hashes@^1.0.0", "@noble/hashes@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" + integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1775,6 +2019,11 @@ redux-thunk "^2.4.1" reselect "^4.1.5" +"@remix-run/router@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.5.0.tgz#57618e57942a5f0131374a9fdb0167e25a117fdc" + integrity sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg== + "@rollup/plugin-babel@^5.2.0": version "5.3.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879" @@ -2250,6 +2499,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.16.tgz#e3733f46797b9df9e853ca9f719c8a6f7b84cd26" integrity sha512-ydLaGVfQOQ6hI1xK2A5nVh8bl0OGoIfYMxPWHqqYe9bTkWCfqiVvZoh2I/QF2sNSkZzZyROBoTefIEI+PB6iIA== +"@types/node@10.12.18": + version "10.12.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" + integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== + "@types/node@11.11.6": version "11.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" @@ -2319,6 +2573,13 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-transition-group@^4.4.1": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" + integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== + dependencies: + "@types/react" "*" + "@types/react@*": version "17.0.39" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce" @@ -3232,6 +3493,14 @@ axios@^0.21.1, axios@^0.21.2, axios@^0.21.4: dependencies: follow-redirects "^1.14.0" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -3385,6 +3654,13 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + base16@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" @@ -3428,6 +3704,11 @@ bfj@^7.0.2: hoopy "^0.1.4" tryer "^1.0.1" +big-integer@^1.6.48: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -3443,13 +3724,26 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bindings@^1.5.0: +bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" +bip32@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-2.0.6.tgz#6a81d9f98c4cd57d05150c60d8f9e75121635134" + integrity sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA== + dependencies: + "@types/node" "10.12.18" + bs58check "^2.1.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + tiny-secp256k1 "^1.1.3" + typeforce "^1.11.5" + wif "^2.0.6" + bip39@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" @@ -3460,6 +3754,13 @@ bip39@^3.0.2: pbkdf2 "^3.0.9" randombytes "^2.0.1" +bip39@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" + integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== + dependencies: + "@noble/hashes" "^1.2.0" + bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -3475,6 +3776,11 @@ bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + body-parser@1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" @@ -3621,6 +3927,22 @@ browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4 node-releases "^2.0.1" picocolors "^1.0.0" +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -3643,7 +3965,7 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= -buffer@6.0.3: +buffer@6.0.3, buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -3780,6 +4102,14 @@ case-sensitive-paths-webpack-plugin@^2.4.0: resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== +chain-registry@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/chain-registry/-/chain-registry-1.10.0.tgz#b0afb3d0919fee40bafccea465ac19117ad9ed7c" + integrity sha512-JfdJufFq2F5tRpw1PMO32pv199xnZvwoSLKpW/wlbVOdpyLka7s4GwxgqYK+CPOdfCj5v6GiaGfWTq7slUemXg== + dependencies: + "@babel/runtime" "^7.19.4" + "@chain-registry/types" "^0.14.0" + chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3902,6 +4232,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@^2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + clean-css@^5.2.2: version "5.2.4" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4" @@ -4143,6 +4478,13 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy-to-clipboard@^3.3.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" + integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== + dependencies: + toggle-selection "^1.0.6" + core-js-compat@^3.20.2, core-js-compat@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.0.tgz#bcc86aa5a589cee358e7a7fa0a4979d5a76c3885" @@ -4188,10 +4530,18 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cosmjs-types@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.4.1.tgz#3b2a53ba60d33159dd075596ce8267cfa7027063" - integrity sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog== +cosmjs-types@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.5.2.tgz#2d42b354946f330dfb5c90a87fdc2a36f97b965d" + integrity sha512-zxCtIJj8v3Di7s39uN4LNcN3HIE1z0B9Z0SPE8ZNQR0oSzsuSe1ACgxoFkvhkS7WBasCAFcglS11G2hyfd5tPg== + dependencies: + long "^4.0.0" + protobufjs "~6.11.2" + +cosmjs-types@^0.7.1, cosmjs-types@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.2.tgz#a757371abd340949c5bd5d49c6f8379ae1ffd7e2" + integrity sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA== dependencies: long "^4.0.0" protobufjs "~6.11.2" @@ -4276,6 +4626,11 @@ crypto-browserify@^3.11.0, crypto-browserify@^3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -4630,6 +4985,11 @@ del@^6.0.0: rimraf "^3.0.2" slash "^3.0.0" +delay@^4.4.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/delay/-/delay-4.4.1.tgz#6e02d02946a1b6ab98b39262ced965acba2ac4d1" + integrity sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -4767,6 +5127,14 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -4880,7 +5248,7 @@ electron-to-chromium@^1.4.17: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.66.tgz#d7453d363dcd7b06ed1757adcde34d724e27b367" integrity sha512-f1RXFMsvwufWLwYUxTiP7HmjprKXrqEWHiQkjAYa9DJeVIlZk5v8gBGcaV+FhtXLly6C1OTVzQY+2UQrACiLlg== -elliptic@^6.4.0, elliptic@^6.5.3: +elliptic@^6.4.0, elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -5668,6 +6036,11 @@ follow-redirects@^1.10.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== +follow-redirects@^1.14.9: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -5701,6 +6074,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -7204,16 +7586,11 @@ js-encoding-utils@0.5.6: resolved "https://registry.yarnpkg.com/js-encoding-utils/-/js-encoding-utils-0.5.6.tgz#517351d8f4a85b2ad121183d41df8319981bee03" integrity sha512-qnAGsUIWrmzh5n+3AXqbxX1KsB9hkQmJZf3aA9DLAS7GpL/NEHCBreFFbW+imramoU+Q0TDyvkwhRbBRH1TVkg== -js-sha3@^0.8.0: +js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== -js-sha512@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha512/-/js-sha512-0.8.0.tgz#dd22db8d02756faccf19f218e3ed61ec8249f7d4" - integrity sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -7898,6 +8275,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +nan@^2.13.2: + version "2.17.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== + nanoid@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" @@ -9070,6 +9452,19 @@ pretty-format@^27.0.2, pretty-format@^27.5.0: ansi-styles "^5.0.0" react-is "^17.0.1" +primeicons@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/primeicons/-/primeicons-6.0.1.tgz#431fa7c79825934eefd62087d8e1faa6a9e376ad" + integrity sha512-KDeO94CbWI4pKsPnYpA1FPjo79EsY9I+M8ywoPBSf9XMXoe/0crjbUK7jcQEDHuc0ZMRIZsxH3TYLv4TUtHmAA== + +primereact@^9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/primereact/-/primereact-9.2.1.tgz#5dcf18d42c5bb4356bc509d51520fce04fdec383" + integrity sha512-7jQQgftWuRAseYtrGwtakqM4GEBDLWeTSzwrFrRKWCdG3uGE4JdVNwFW5QzlOQlZJKcASXlsNx/YBW07McuvRQ== + dependencies: + "@types/react-transition-group" "^4.4.1" + react-transition-group "^4.4.1" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -9107,7 +9502,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2: +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -9154,6 +9549,25 @@ protobufjs@~6.10.2: "@types/node" "^13.7.0" long "^4.0.0" +protobufjs@~6.11.3: + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -9323,6 +9737,14 @@ react-base16-styling@^0.6.0: lodash.flow "^3.3.0" pure-color "^1.2.0" +react-copy-to-clipboard@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz#09aae5ec4c62750ccb2e6421a58725eabc41255c" + integrity sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A== + dependencies: + copy-to-clipboard "^3.3.1" + prop-types "^15.8.1" + react-dev-utils@^12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" @@ -9409,6 +9831,21 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-router-dom@^6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.10.0.tgz#090ddc5c84dc41b583ce08468c4007c84245f61f" + integrity sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg== + dependencies: + "@remix-run/router" "1.5.0" + react-router "6.10.0" + +react-router@6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.10.0.tgz#230f824fde9dd0270781b5cb497912de32c0a971" + integrity sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ== + dependencies: + "@remix-run/router" "1.5.0" + react-scripts@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.0.tgz#6547a6d7f8b64364ef95273767466cc577cb4b60" @@ -9478,6 +9915,24 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.3.0" use-latest "^1.2.1" +react-tooltip@^5.11.1: + version "5.11.1" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.11.1.tgz#75857ffaed1d13e3e35189934e8a373751fc78c0" + integrity sha512-+2a/DvmOlPQd5e1f3s32E/+vv4Tv4UmPxZfCyeVr4BeY3SRCEKGHiE36jH+UtqxSuRP9TKviwmhow4gNDRXCMQ== + dependencies: + "@floating-ui/dom" "^1.0.0" + classnames "^2.3.0" + +react-transition-group@^4.4.1: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -9573,6 +10028,11 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -10706,6 +11166,17 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-secp256k1@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c" + integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA== + dependencies: + bindings "^1.3.0" + bn.js "^4.11.8" + create-hmac "^1.1.7" + elliptic "^6.4.0" + nan "^2.13.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -10753,6 +11224,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== + toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -10882,6 +11358,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typeforce@^1.11.5: + version "1.18.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" + integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== + typescript@~4.1.5: version "4.1.6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.6.tgz#1becd85d77567c3c741172339e93ce2e69932138" @@ -11070,6 +11551,11 @@ utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -11407,6 +11893,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wif@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" + integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== + dependencies: + bs58check "<3.0.0" + word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"