diff --git a/.env b/.env index 0157db51e..8322cdda4 100644 --- a/.env +++ b/.env @@ -1 +1,3 @@ SASS_PATH=node_modules:src/styles +INLINE_RUNTIME_CHUNK=false +REACT_APP_SANDBOX=true diff --git a/package.json b/package.json index 880ad0854..8293be222 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "start": "react-scripts start", "build-scripts": "webpack --config scripts/webpack.config.js", - "build": "cross-env REACT_APP_SANDBOX=true INLINE_RUNTIME_CHUNK=false react-app-rewired build && npm run build-scripts", + "build": "cross-env react-app-rewired build && npm run build-scripts", "test": "react-scripts test", "prepare": "husky install", "pre-commit": "lint-staged", diff --git a/public/manifest.json b/public/manifest.json index ae2de8ba6..cd5677399 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Terra Station Wallet", - "version": "2.0.0", + "version": "2.1.0", "background": { "scripts": ["background.js"], "persistent": true diff --git a/src/data/settings/Theme.ts b/src/data/settings/Theme.ts index 91d458e60..78116b62f 100644 --- a/src/data/settings/Theme.ts +++ b/src/data/settings/Theme.ts @@ -30,6 +30,11 @@ export const useThemeFavicon = () => { return favicon } +export const useThemeFront = () => { + const { front } = useTheme() + return front +} + export const useThemeAnimation = () => { const { animation } = useTheme() return animation diff --git a/src/extension/App.tsx b/src/extension/App.tsx index 1940ed9ba..6cab8cde4 100644 --- a/src/extension/App.tsx +++ b/src/extension/App.tsx @@ -8,6 +8,8 @@ import LatestTx from "app/sections/LatestTx" import NetworkName from "app/sections/NetworkName" import SendTx from "txs/send/SendTx" import SwapTx from "txs/swap/SwapTx" +import SignMultisigTxPage from "pages/multisig/SignMultisigTxPage" +import PostMultisigTxPage from "pages/multisig/PostMultisigTxPage" import { storeNetwork, storeWalletAddress } from "./storage" import RequestContainer from "./RequestContainer" import ManageNetworks from "./networks/ManageNetworks" @@ -33,9 +35,17 @@ const App = () => { const routes = useRoutes([ { path: "/networks", element: }, { path: "/network/new", element: }, + + /* auth */ { path: "/auth/*", element: }, + + /* default txs */ { path: "/send", element: }, { path: "/swap", element: }, + { path: "/multisig/sign", element: }, + { path: "/multisig/post", element: }, + + /* 404 */ { path: "*", element: }, ]) diff --git a/src/extension/RequestContainer.tsx b/src/extension/RequestContainer.tsx index 89c28ff6e..e9c72c983 100644 --- a/src/extension/RequestContainer.tsx +++ b/src/extension/RequestContainer.tsx @@ -22,6 +22,7 @@ interface RequestContext { response: TxResponse, password?: string ) => void + multisigTx: (request: PrimitiveDefaultRequest) => void } } @@ -117,9 +118,26 @@ const RequestContainer: FC = ({ children }) => { }) } + /* multisig */ + const handleMultisigTx = (request: PrimitiveDefaultRequest) => { + // Delete request + extension.storage?.local.get(["post"], (storage: ExtensionStorage) => { + const list = storage.post || [] + const next = list.filter( + ({ id, origin }) => !(id === request.id && origin === request.origin) + ) + + extension.storage?.local.set({ post: next }, () => setTx(undefined)) + }) + } + /* context */ const requests = { connect, tx } - const actions = { connect: handleConnect, tx: handleTx } + const actions = { + connect: handleConnect, + tx: handleTx, + multisigTx: handleMultisigTx, + } return ( {children} diff --git a/src/extension/auth/Auth.tsx b/src/extension/auth/Auth.tsx index 226d1a57f..475962047 100644 --- a/src/extension/auth/Auth.tsx +++ b/src/extension/auth/Auth.tsx @@ -7,6 +7,7 @@ import AccessWithLedgerPage from "./AccessWithLedgerPage" import NewWalletPage from "./NewWalletPage" import RecoverWalletPage from "./RecoverWalletPage" import ImportWalletPage from "./ImportWalletPage" +import NewMultisigWalletPage from "./NewMultisigWalletPage" /* manage */ import ExportWalletPage from "./ExportWalletPage" @@ -24,6 +25,7 @@ const Auth = () => { } /> } /> } /> + } /> {/* manage */} } /> diff --git a/src/extension/auth/NewMultisigWalletPage.tsx b/src/extension/auth/NewMultisigWalletPage.tsx new file mode 100644 index 000000000..438c73780 --- /dev/null +++ b/src/extension/auth/NewMultisigWalletPage.tsx @@ -0,0 +1,15 @@ +import { useTranslation } from "react-i18next" +import NewMultisigWalletForm from "auth/modules/create/NewMultisigWalletForm" +import ExtensionPage from "../components/ExtensionPage" + +const NewMultisigWalletPage = () => { + const { t } = useTranslation() + + return ( + + + + ) +} + +export default NewMultisigWalletPage diff --git a/src/extension/auth/SwitchWallet.tsx b/src/extension/auth/SwitchWallet.tsx index 6aaccdd16..a81be8f40 100644 --- a/src/extension/auth/SwitchWallet.tsx +++ b/src/extension/auth/SwitchWallet.tsx @@ -1,4 +1,6 @@ -import { useAuth } from "auth" +import { isWallet, useAuth } from "auth" +import { Flex } from "components/layout" +import MultisigBadge from "auth/components/MultisigBadge" import { clearStoredPassword } from "../storage" import ExtensionList from "../components/ExtensionList" @@ -7,13 +9,23 @@ const SwitchWallet = () => { const list = wallets .filter(({ name }) => name !== connectedWallet?.name) - .map(({ name, address }) => { + .map((wallet) => { const select = () => { connect(name) clearStoredPassword() } - return { children: name, description: address, onClick: select } + const { name, address } = wallet + return { + children: ( + + {isWallet.multisig(wallet) && } + {name} + + ), + description: address, + onClick: select, + } }) return diff --git a/src/extension/components/ExtensionList.module.scss b/src/extension/components/ExtensionList.module.scss index 287f91a17..a55103b20 100644 --- a/src/extension/components/ExtensionList.module.scss +++ b/src/extension/components/ExtensionList.module.scss @@ -11,7 +11,7 @@ @include flex(space-between); color: var(--text); - padding: 20px; + padding: 16px 20px; text-align: left; transition: background var(--transition); width: 100%; diff --git a/src/extension/components/ExtensionList.tsx b/src/extension/components/ExtensionList.tsx index e93d047ac..10ca79de3 100644 --- a/src/extension/components/ExtensionList.tsx +++ b/src/extension/components/ExtensionList.tsx @@ -5,7 +5,7 @@ import { Flex, Grid } from "components/layout" import styles from "./ExtensionList.module.scss" interface DefaultItemProps { - children: string + children: ReactNode description?: string icon?: ReactNode active?: boolean @@ -22,7 +22,10 @@ interface ButtonItem extends DefaultItemProps { type Item = LinkItem | ButtonItem const ExtensionList = ({ list }: { list: Item[] }) => { - const renderItem = ({ children, description, icon, ...item }: Item) => { + const renderItem = ( + { children, description, icon, ...item }: Item, + index: number + ) => { const props = { className: styles.item, children: ( @@ -32,14 +35,14 @@ const ExtensionList = ({ list }: { list: Item[] }) => {

{children}

-

{description}

+ {description &&

{description}

}
), - key: children, + key: index, } return "to" in item ? ( diff --git a/src/extension/components/WalletCard.tsx b/src/extension/components/WalletCard.tsx index b125ae9f4..7548efad5 100644 --- a/src/extension/components/WalletCard.tsx +++ b/src/extension/components/WalletCard.tsx @@ -1,6 +1,7 @@ import { ReactNode } from "react" import { Flex, Grid } from "components/layout" -import { useAuth } from "auth" +import { isWallet, useAuth } from "auth" +import MultisigBadge from "auth/components/MultisigBadge" import Copy from "./Copy" import styles from "./WalletCard.module.scss" import WalletQR from "./WalletQR" @@ -14,12 +15,18 @@ const WalletCard = ({ extra }: Props) => { if (!wallet) return null const { address } = wallet - const name = "name" in wallet ? wallet.name : undefined + const name = isWallet.local(wallet) ? wallet.name : undefined return (
- {name &&

{name}

} + {name && ( + + {isWallet.multisig(wallet) && } +

{name}

+
+ )} + {address} diff --git a/src/extension/modules/ConfirmTx.tsx b/src/extension/modules/ConfirmTx.tsx index 27ba5d9b1..fc6d4ee84 100644 --- a/src/extension/modules/ConfirmTx.tsx +++ b/src/extension/modules/ConfirmTx.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" +import { useNavigate } from "react-router-dom" import { useForm } from "react-hook-form" import { getErrorMessage } from "utils/error" import { useThemeAnimation } from "data/settings/Theme" @@ -8,9 +9,10 @@ import { Flex, FlexColumn, Grid } from "components/layout" import { Form, FormError, FormItem, FormWarning } from "components/form" import { Input, Checkbox } from "components/form" import Overlay from "app/components/Overlay" -import { useAuth } from "auth" +import useToPostMultisigTx from "pages/multisig/utils/useToPostMultisigTx" +import { isWallet, useAuth } from "auth" import { PasswordError } from "auth/scripts/keystore" -import { getStoredPassword } from "../storage" +import { getOpenURL, getStoredPassword } from "../storage" import { getIsDangerousTx, SignBytesRequest, TxRequest } from "../utils" import { useRequest } from "../RequestContainer" import ExtensionPage from "../components/ExtensionPage" @@ -26,7 +28,7 @@ const ConfirmTx = (props: TxRequest | SignBytesRequest) => { const animation = useThemeAnimation() const { wallet, ...auth } = useAuth() const { actions } = useRequest() - const passwordRequired = wallet && "name" in wallet + const passwordRequired = isWallet.single(wallet) /* form */ const form = useForm({ @@ -58,6 +60,8 @@ const ConfirmTx = (props: TxRequest | SignBytesRequest) => { ? t("Enter password") : "" + const navigate = useNavigate() + const toPostMultisigTx = useToPostMultisigTx() const submit = async ({ password }: Values) => { setSubmitting(true) @@ -66,9 +70,19 @@ const ConfirmTx = (props: TxRequest | SignBytesRequest) => { try { if (disabled) throw new Error(disabled) - const result = await auth[requestType](tx, password) - const response = { result, success: true } - actions.tx(requestType, props, response, nextPassword) + + if (isWallet.multisig(wallet)) { + const unsignedTx = await auth.create(props.tx) + const { pathname, search } = toPostMultisigTx(unsignedTx) + const openURL = getOpenURL([pathname, search].join("?")) + actions.multisigTx(props) + if (openURL) openURL() + else navigate({ pathname, search }) + } else { + const result = await auth[requestType](tx, password) + const response = { result, success: true } + actions.tx(requestType, props, response, nextPassword) + } } catch (error) { if (error instanceof PasswordError) { setIncorrect(error.message) @@ -118,8 +132,7 @@ const ConfirmTx = (props: TxRequest | SignBytesRequest) => { const error = props.requestType === "signBytes" && - wallet && - "ledger" in wallet && + isWallet.ledger(wallet) && t("Arbitrary data cannot be signed by Ledger") const SIZE = { width: 100, height: 100 } @@ -127,7 +140,7 @@ const ConfirmTx = (props: TxRequest | SignBytesRequest) => { {t("Submitting...")} - {wallet && "ledger" in wallet &&

{t("Confirm in ledger")}

} + {isWallet.ledger(wallet) &&

{t("Confirm in ledger")}

}
) : ( diff --git a/src/extension/modules/Front.tsx b/src/extension/modules/Front.tsx index 62acb0f4b..7468efaec 100644 --- a/src/extension/modules/Front.tsx +++ b/src/extension/modules/Front.tsx @@ -14,7 +14,7 @@ const Front = () => { const { requests } = useRequest() const { connect, tx } = requests - if (!wallet) + if (!wallet) { return ( @@ -23,6 +23,7 @@ const Front = () => { ) + } if (connect) return if (tx) return diff --git a/src/extension/modules/Welcome.module.scss b/src/extension/modules/Welcome.module.scss index e0571a876..3b5f5c2b4 100644 --- a/src/extension/modules/Welcome.module.scss +++ b/src/extension/modules/Welcome.module.scss @@ -1,6 +1,5 @@ .component { - padding-top: 32px; - padding-bottom: 8px; + padding-top: 8px; } .content { diff --git a/src/extension/modules/Welcome.tsx b/src/extension/modules/Welcome.tsx index d74e028a8..fa2370315 100644 --- a/src/extension/modules/Welcome.tsx +++ b/src/extension/modules/Welcome.tsx @@ -1,15 +1,15 @@ import { useTranslation } from "react-i18next" -import { useThemeFavicon } from "data/settings/Theme" +import { useThemeFront } from "data/settings/Theme" import { FlexColumn } from "components/layout" import styles from "./Welcome.module.scss" const Welcome = () => { const { t } = useTranslation() - const favicon = useThemeFavicon() + const front = useThemeFront() return ( - - Terra Station + + Terra Station

{t("Connect to Terra blockchain")}

) diff --git a/src/styles/themes/Blossom/Front.png b/src/styles/themes/Blossom/Front.png new file mode 100644 index 000000000..fc4a8fb53 Binary files /dev/null and b/src/styles/themes/Blossom/Front.png differ diff --git a/src/styles/themes/Dark/Front.png b/src/styles/themes/Dark/Front.png new file mode 100644 index 000000000..7cda29122 Binary files /dev/null and b/src/styles/themes/Dark/Front.png differ diff --git a/src/styles/themes/Light/Front.png b/src/styles/themes/Light/Front.png new file mode 100644 index 000000000..c150625e5 Binary files /dev/null and b/src/styles/themes/Light/Front.png differ diff --git a/src/styles/themes/Madness/Front.png b/src/styles/themes/Madness/Front.png new file mode 100644 index 000000000..585fa3eef Binary files /dev/null and b/src/styles/themes/Madness/Front.png differ diff --git a/src/styles/themes/Moon/Front.png b/src/styles/themes/Moon/Front.png new file mode 100644 index 000000000..9a1f528e4 Binary files /dev/null and b/src/styles/themes/Moon/Front.png differ diff --git a/src/styles/themes/Whale/Front.png b/src/styles/themes/Whale/Front.png new file mode 100644 index 000000000..7cb64b194 Binary files /dev/null and b/src/styles/themes/Whale/Front.png differ diff --git a/src/styles/themes/themes.tsx b/src/styles/themes/themes.tsx index 9026439f8..365abc6dc 100644 --- a/src/styles/themes/themes.tsx +++ b/src/styles/themes/themes.tsx @@ -17,6 +17,14 @@ import FaviconMoon from "./Moon/favicon.svg" import FaviconWhale from "./Whale/favicon.svg" import FaviconMadness from "./Madness/favicon.svg" +/* favicon */ +import FrontLight from "./Light/Front.png" +import FrontDark from "./Dark/Front.png" +import FrontBlossom from "./Blossom/Front.png" +import FrontMoon from "./Moon/Front.png" +import FrontWhale from "./Whale/Front.png" +import FrontMadness from "./Madness/Front.png" + /* preview */ import { ReactComponent as PreviewLight } from "./Light/preview.svg" import { ReactComponent as PreviewDark } from "./Dark/preview.svg" @@ -30,6 +38,7 @@ export interface Theme { unlock: Amount animation: string favicon: string + front: string preview: ReactNode } @@ -39,6 +48,7 @@ export const themes: Theme[] = [ unlock: toAmount("0"), animation: AnimationLight, favicon: FaviconLight, + front: FrontLight, preview: , }, { @@ -46,6 +56,7 @@ export const themes: Theme[] = [ unlock: toAmount("0"), animation: AnimationDark, favicon: FaviconDark, + front: FrontDark, preview: , }, { @@ -53,6 +64,7 @@ export const themes: Theme[] = [ unlock: toAmount("1"), animation: AnimationBlossom, favicon: FaviconBlossom, + front: FrontBlossom, preview: , }, { @@ -60,6 +72,7 @@ export const themes: Theme[] = [ unlock: toAmount("10"), animation: AnimationMoon, favicon: FaviconMoon, + front: FrontMoon, preview: , }, { @@ -67,6 +80,7 @@ export const themes: Theme[] = [ unlock: toAmount("100"), animation: AnimationWhale, favicon: FaviconWhale, + front: FrontWhale, preview: , }, { @@ -74,6 +88,7 @@ export const themes: Theme[] = [ unlock: toAmount("1000"), animation: AnimationMadness, favicon: FaviconMadness, + front: FrontMadness, preview: , }, ]