Skip to content

Commit

Permalink
feature: Multisig wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
sim committed Jan 13, 2022
1 parent a9861a8 commit 7d110bc
Show file tree
Hide file tree
Showing 23 changed files with 132 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
SASS_PATH=node_modules:src/styles
INLINE_RUNTIME_CHUNK=false
REACT_APP_SANDBOX=true
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion public/manifest.json
Original file line number Diff line number Diff line change
@@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/data/settings/Theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/extension/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -33,9 +35,17 @@ const App = () => {
const routes = useRoutes([
{ path: "/networks", element: <ManageNetworks /> },
{ path: "/network/new", element: <AddNetworkPage /> },

/* auth */
{ path: "/auth/*", element: <Auth /> },

/* default txs */
{ path: "/send", element: <SendTx /> },
{ path: "/swap", element: <SwapTx /> },
{ path: "/multisig/sign", element: <SignMultisigTxPage /> },
{ path: "/multisig/post", element: <PostMultisigTxPage /> },

/* 404 */
{ path: "*", element: <Front /> },
])

Expand Down
20 changes: 19 additions & 1 deletion src/extension/RequestContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface RequestContext {
response: TxResponse,
password?: string
) => void
multisigTx: (request: PrimitiveDefaultRequest) => void
}
}

Expand Down Expand Up @@ -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 (
<RequestProvider value={{ requests, actions }}>{children}</RequestProvider>
Expand Down
2 changes: 2 additions & 0 deletions src/extension/auth/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -24,6 +25,7 @@ const Auth = () => {
<Route path="new" element={<NewWalletPage />} />
<Route path="recover" element={<RecoverWalletPage />} />
<Route path="import" element={<ImportWalletPage />} />
<Route path="multisig/new" element={<NewMultisigWalletPage />} />

{/* manage */}
<Route path="export" element={<ExportWalletPage />} />
Expand Down
15 changes: 15 additions & 0 deletions src/extension/auth/NewMultisigWalletPage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ExtensionPage title={t("New multisig wallet")}>
<NewMultisigWalletForm />
</ExtensionPage>
)
}

export default NewMultisigWalletPage
18 changes: 15 additions & 3 deletions src/extension/auth/SwitchWallet.tsx
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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: (
<Flex gap={4} start>
{isWallet.multisig(wallet) && <MultisigBadge />}
{name}
</Flex>
),
description: address,
onClick: select,
}
})

return <ExtensionList list={list} />
Expand Down
2 changes: 1 addition & 1 deletion src/extension/components/ExtensionList.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down
11 changes: 7 additions & 4 deletions src/extension/components/ExtensionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: (
Expand All @@ -32,14 +35,14 @@ const ExtensionList = ({ list }: { list: Item[] }) => {

<Grid gap={2}>
<h1 className={styles.title}>{children}</h1>
<p className={styles.desc}>{description}</p>
{description && <p className={styles.desc}>{description}</p>}
</Grid>
</Flex>

<ChevronRightIcon fontSize="small" />
</>
),
key: children,
key: index,
}

return "to" in item ? (
Expand Down
13 changes: 10 additions & 3 deletions src/extension/components/WalletCard.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 (
<div className={styles.wallet}>
<Grid>
{name && <h1>{name}</h1>}
{name && (
<Flex gap={4} start>
{isWallet.multisig(wallet) && <MultisigBadge />}
<h1>{name}</h1>
</Flex>
)}

<Flex gap={4} className={styles.address}>
<Copy text={address}>{address}</Copy>
<WalletQR />
Expand Down
31 changes: 22 additions & 9 deletions src/extension/modules/ConfirmTx.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand All @@ -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<Values>({
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -118,16 +132,15 @@ 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 }
return submitting ? (
<Overlay>
<FlexColumn gap={20}>
<img {...SIZE} src={animation} alt={t("Submitting...")} />
{wallet && "ledger" in wallet && <p>{t("Confirm in ledger")}</p>}
{isWallet.ledger(wallet) && <p>{t("Confirm in ledger")}</p>}
</FlexColumn>
</Overlay>
) : (
Expand Down
3 changes: 2 additions & 1 deletion src/extension/modules/Front.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const Front = () => {
const { requests } = useRequest()
const { connect, tx } = requests

if (!wallet)
if (!wallet) {
return (
<ExtensionPage>
<Col>
Expand All @@ -23,6 +23,7 @@ const Front = () => {
</Col>
</ExtensionPage>
)
}

if (connect) return <ConfirmConnect {...connect} />
if (tx) return <ConfirmTx {...tx} />
Expand Down
3 changes: 1 addition & 2 deletions src/extension/modules/Welcome.module.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.component {
padding-top: 32px;
padding-bottom: 8px;
padding-top: 8px;
}

.content {
Expand Down
8 changes: 4 additions & 4 deletions src/extension/modules/Welcome.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<FlexColumn gap={24} className={styles.component}>
<img src={favicon} alt="Terra Station" width={80} height={80} />
<FlexColumn gap={8} className={styles.component}>
<img src={front} alt="Terra Station" width={120} height={145} />
<p className={styles.content}>{t("Connect to Terra blockchain")}</p>
</FlexColumn>
)
Expand Down
Binary file added src/styles/themes/Blossom/Front.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/styles/themes/Dark/Front.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/styles/themes/Light/Front.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/styles/themes/Madness/Front.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/styles/themes/Moon/Front.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/styles/themes/Whale/Front.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 7d110bc

Please sign in to comment.