Skip to content

Commit

Permalink
Merge pull request #266 from terra-money/is-sprint-24
Browse files Browse the repository at this point in the history
Is sprint 24
  • Loading branch information
mwmerz authored Sep 1, 2023
2 parents 46b2de3 + 061e4cf commit 60ea38b
Show file tree
Hide file tree
Showing 17 changed files with 1,308 additions and 64 deletions.
973 changes: 929 additions & 44 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@mui/icons-material": "^5.8.0",
"@mui/material": "^5.9.1",
"@sentry/react": "^7.53.1",
"@terra-money/feather.js": "^1.0.8",
"@terra-money/feather.js": "^1.0.11",
"@terra-money/ledger-station-js": "^1.3.7",
"@terra-money/log-finder-ruleset": "^3.0.3",
"@terra-money/msg-reader": "^3.0.1",
Expand Down
4 changes: 2 additions & 2 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"manifest_version": 3,
"name": "Station Wallet",
"version": "7.4.4",
"version_name": "7.4.4",
"version": "7.4.5",
"version_name": "7.4.5",
"background": {
"service_worker": "background.js"
},
Expand Down
5 changes: 4 additions & 1 deletion src/app/sections/AdvancedSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useDevMode, useReplaceKeplr } from "utils/localStorage"
import {
useDevMode,
//useReplaceKeplr
} from "utils/localStorage"
import SettingsSelectorToggle from "components/layout/SettingsSelectorToggle"
import { FlexColumn, GasAdjustment } from "components/layout"
import { TooltipIcon } from "components/display"
Expand Down
157 changes: 157 additions & 0 deletions src/app/sections/CoinTypeMnemonicForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { SeedKey } from "@terra-money/feather.js"
import useAuth from "auth/hooks/useAuth"
import { addWallet, deleteWallet, getDecryptedKey } from "auth/scripts/keystore"
import validate from "auth/scripts/validate"
import { TooltipIcon } from "components/display"
import {
Form,
FormError,
FormHelp,
FormItem,
FormWarning,
Input,
Submit,
} from "components/form"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { wordsFromAddress } from "utils/bech32"

interface Values {
index: number
mnemonic: string
password: string
}

const CoinTypeMnemonicForm = ({ close }: { close: () => void }) => {
const { t } = useTranslation()
const [error, setError] = useState<Error>()
const { wallet, connect } = useAuth()

const form = useForm<Values>({
mode: "onChange",
defaultValues: { index: 0 },
})
const { register, handleSubmit, formState, watch } = form
const { errors, isValid } = formState
const { index } = watch()

const submit = ({ password, mnemonic, index }: Values) => {
try {
if (!wallet) throw new Error("No wallet connected")

const pk = getDecryptedKey({ name: wallet.name, password })
if (!pk) throw new Error("Incorrect password")

const seed = SeedKey.seedFromMnemonic(mnemonic)
const key330 = new SeedKey({
seed,
coinType: 330,
index,
})
const key118 = new SeedKey({
seed,
coinType: 118,
index,
})
const key60 = new SeedKey({
seed,
coinType: 60,
index,
})

const isLegacy =
wordsFromAddress(key118.accAddress("terra")) === wallet.words["330"]

if (
!isLegacy &&
wordsFromAddress(key330.accAddress("terra")) !== wallet.words["330"]
) {
throw new Error("Wrong mnemonic or index")
}

deleteWallet(wallet.name)
addWallet({
name: wallet.name,
index,
legacy: isLegacy,
words: {
"330": wordsFromAddress(
(isLegacy ? key118 : key330).accAddress("terra")
),
"118": wordsFromAddress(key118.accAddress("terra")),
"60": wordsFromAddress(key60.accAddress("terra")),
},
pubkey: {
// @ts-expect-error
"330": (isLegacy ? key118 : key330).publicKey.key,
// @ts-expect-error
"118": key118.publicKey.key,
// @ts-expect-error
"60": key60.publicKey.key,
},
seed,
password,
})
connect(wallet.name)
close()
} catch (error) {
setError(error as Error)
}
}

return (
<Form onSubmit={handleSubmit(submit)}>
<FormItem>
<FormHelp>
<p>Station now supports Injective!</p>
<p>
Adding Injective support will allow Station users to be able to
carry out transactions utilizing an Injective address tied to their
Station wallet.
</p>
<p>
If you are using an older version of Station, you will be prompted
for your mnemonic seed. This key will be used in generating a unique
Injective address which will be tied to your Station wallet.
</p>
</FormHelp>
</FormItem>

<FormItem label={t("Password")} error={errors.password?.message}>
<Input {...register("password", { required: true })} type="password" />
</FormItem>

<FormItem label={t("Mnemonic seed")} error={errors.mnemonic?.message}>
<Input
type="password"
{...register("mnemonic", { validate: validate.mnemonic })}
/>
</FormItem>

<FormItem /* do not translate this */
label="Index"
error={errors.index?.message}
extra={
<TooltipIcon
content={t("BIP 44 index number. For advanced users only")}
/>
}
>
<Input
{...register("index", {
valueAsNumber: true,
validate: validate.index,
})}
/>
{index !== 0 && <FormWarning>{t("Default index is 0")}</FormWarning>}
</FormItem>

{error && <FormError>{error.message}</FormError>}

<Submit disabled={!isValid} />
</Form>
)
}

export default CoinTypeMnemonicForm
103 changes: 103 additions & 0 deletions src/app/sections/CoinTypePasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { SeedKey } from "@terra-money/feather.js"
import useAuth from "auth/hooks/useAuth"
import { getDecryptedKey, updateStoredWallet } from "auth/scripts/keystore"
import {
Form,
FormError,
FormHelp,
FormItem,
Input,
Submit,
} from "components/form"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { wordsFromAddress } from "utils/bech32"

interface Values {
password: string
}

const CoinTypePasswordForm = ({ close }: { close: () => void }) => {
const { t } = useTranslation()
const [error, setError] = useState<Error>()
const { wallet, connect } = useAuth()

const form = useForm<Values>({ mode: "onChange" })
const { register, handleSubmit, formState } = form
const { errors, isValid } = formState

const submit = ({ password }: Values) => {
try {
if (!wallet) throw new Error("No wallet connected")

const pk = getDecryptedKey({ name: wallet.name, password })
if (!pk) throw new Error("Incorrect password")

if ("seed" in pk) {
const key330 = new SeedKey({
seed: Buffer.from(pk.seed, "hex"),
coinType: pk.legacy ? 118 : 330,
index: pk.index || 0,
})
const key118 = new SeedKey({
seed: Buffer.from(pk.seed, "hex"),
coinType: 118,
index: pk.index || 0,
})
const key60 = new SeedKey({
seed: Buffer.from(pk.seed, "hex"),
coinType: 60,
index: pk.index || 0,
})

const words = {
...wallet.words,
"60": wordsFromAddress(key60.accAddress("terra")),
}
const pubkey = {
// @ts-expect-error
"330": key330.publicKey.key,
// @ts-expect-error
"118": key118.publicKey.key,
// @ts-expect-error
"60": key60.publicKey.key,
}
updateStoredWallet({
name: wallet.name,
words,
pubkey,
})
connect(wallet.name)
close()
}
} catch (error) {
setError(error as Error)
}
}

return (
<Form onSubmit={handleSubmit(submit)}>
<FormItem>
<FormHelp>
<p>Station now supports Injective!</p>
<p>
Adding Injective support will allow Station users to be able to
carry out transactions utilizing an Injective address tied to their
Station wallet.
</p>
</FormHelp>
</FormItem>

<FormItem label={t("Password")} error={errors.password?.message}>
<Input {...register("password", { required: true })} type="password" />
</FormItem>

{error && <FormError>{error.message}</FormError>}

<Submit disabled={!isValid} />
</Form>
)
}

export default CoinTypePasswordForm
41 changes: 41 additions & 0 deletions src/app/sections/EnableCoinType.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useTranslation } from "react-i18next"
import { ModalButton } from "components/feedback"
import { Button } from "components/general"
import is from "auth/scripts/is"
import CoinTypePasswordForm from "./CoinTypePasswordForm"
import { useState } from "react"
import { useAuth } from "auth"
import CoinTypeMnemonicForm from "./CoinTypeMnemonicForm"

const EnableCoinType = () => {
const { t } = useTranslation()
const { wallet } = useAuth()

const [modalKey, setModalKey] = useState(0)
const closeModal = () => setModalKey((k) => k + 1)

// if wallet is ledger or multisig, return null
if (!wallet || is.ledger(wallet) || is.multisig(wallet)) return null
// if wallet already has injective address enabled, return null
if (wallet.words?.["60"]) return null

return (
<ModalButton
modalKey={modalKey.toString()}
title={t("Enable Injective")}
renderButton={(open) => (
<Button size="small" color="primary" onClick={open}>
Enable Injective
</Button>
)}
>
{is.seed(wallet) ? (
<CoinTypePasswordForm close={closeModal} />
) : (
<CoinTypeMnemonicForm close={closeModal} />
)}
</ModalButton>
)
}

export default EnableCoinType
2 changes: 2 additions & 0 deletions src/auth/auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ interface SingleWallet {
words: {
"330": string
"118"?: string
"60"?: string
}
pubkey?: {
"330": string
"118"?: string
"60"?: string
}
name: string
lock?: boolean
Expand Down
12 changes: 8 additions & 4 deletions src/auth/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,10 @@ const useAuth = () => {
if ("seed" in pk) {
const key = new SeedKey({
seed: Buffer.from(pk.seed, "hex"),
coinType: pk.legacy ? 118 : parseInt(networks[chainID].coinType),
coinType:
pk.legacy && parseInt(networks[chainID].coinType) === 330
? 118
: parseInt(networks[chainID].coinType),
index: pk.index || 0,
})
return await key.createSignatureAmino(doc)
Expand Down Expand Up @@ -270,9 +273,10 @@ const useAuth = () => {
if ("seed" in pk) {
const key = new SeedKey({
seed: Buffer.from(pk.seed, "hex"),
coinType: pk.legacy
? 118
: parseInt(networks[txOptions?.chainID].coinType),
coinType:
pk.legacy && parseInt(networks[txOptions?.chainID].coinType) === 330
? 118
: parseInt(networks[txOptions?.chainID].coinType),
index: pk.index || 0,
})
const w = lcd.wallet(key)
Expand Down
8 changes: 4 additions & 4 deletions src/auth/hooks/useNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ export const useNetwork = (): Record<ChainID, InterchainNetwork> => {
return filterEnabledNetworks({ [terra?.chainID]: terra })
}

if (wallet && !wallet?.words?.["118"]) {
const chains330 = Object.values(
if (wallet) {
const enabledChains = Object.values(
withCustomLCDs(
networks[network as NetworkName] as Record<ChainID, InterchainNetwork>
) ?? {}
).filter(({ coinType }) => coinType === "330")
).filter(({ coinType }) => !!wallet?.words?.[coinType])

return filterEnabledNetworks(
chains330.reduce((acc, chain) => {
enabledChains.reduce((acc, chain) => {
acc[chain?.chainID] = chain
return acc
}, {} as Record<ChainID, InterchainNetwork>)
Expand Down
Loading

0 comments on commit 60ea38b

Please sign in to comment.