diff --git a/@cosmjs/ledger-amino/build/index.d.ts b/@cosmjs/ledger-amino/build/index.d.ts deleted file mode 100644 index 155dfd14..00000000 --- a/@cosmjs/ledger-amino/build/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { LaunchpadLedger } from "./launchpadledger"; -export { LedgerSigner } from "./ledgersigner"; diff --git a/@cosmjs/ledger-amino/build/index.js b/@cosmjs/ledger-amino/build/index.js deleted file mode 100644 index 556af49c..00000000 --- a/@cosmjs/ledger-amino/build/index.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.LedgerSigner = exports.LaunchpadLedger = void 0; -var launchpadledger_1 = require("./launchpadledger"); -Object.defineProperty(exports, "LaunchpadLedger", { enumerable: true, get: function () { return launchpadledger_1.LaunchpadLedger; } }); -var ledgersigner_1 = require("./ledgersigner"); -Object.defineProperty(exports, "LedgerSigner", { enumerable: true, get: function () { return ledgersigner_1.LedgerSigner; } }); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/@cosmjs/ledger-amino/build/index.js.map b/@cosmjs/ledger-amino/build/index.js.map deleted file mode 100644 index b9632293..00000000 --- a/@cosmjs/ledger-amino/build/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAoD;AAA3C,kHAAA,eAAe,OAAA;AACxB,+CAA8C;AAArC,4GAAA,YAAY,OAAA"} \ No newline at end of file diff --git a/@cosmjs/ledger-amino/build/launchpadledger.d.ts b/@cosmjs/ledger-amino/build/launchpadledger.d.ts deleted file mode 100644 index c4a2babf..00000000 --- a/@cosmjs/ledger-amino/build/launchpadledger.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// -import { HdPath } from "@cosmjs/crypto"; -import Transport from "@ledgerhq/hw-transport"; -export interface LedgerAppErrorResponse { - readonly error_message?: string; - readonly device_locked?: boolean; -} -export interface LaunchpadLedgerOptions { - readonly hdPaths?: readonly HdPath[]; - readonly prefix?: string; - readonly testModeAllowed?: boolean; -} -export declare class LaunchpadLedger { - private readonly testModeAllowed; - private readonly hdPaths; - private readonly prefix; - private readonly app; - constructor(transport: Transport, options?: LaunchpadLedgerOptions); - getCosmosAppVersion(): Promise; - getPubkey(hdPath?: HdPath): Promise; - getPubkeys(): Promise; - getCosmosAddress(pubkey?: Uint8Array): Promise; - sign(message: Uint8Array, hdPath?: HdPath): Promise; - private verifyAppMode; - private getOpenAppName; - private verifyAppVersion; - private verifyCosmosAppIsOpen; - private verifyDeviceIsReady; - private handleLedgerErrors; -} diff --git a/@cosmjs/ledger-amino/build/launchpadledger.js b/@cosmjs/ledger-amino/build/launchpadledger.js deleted file mode 100644 index 575ac2f8..00000000 --- a/@cosmjs/ledger-amino/build/launchpadledger.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict' -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod } - } -Object.defineProperty(exports, '__esModule', { value: true }) -exports.LaunchpadLedger = void 0 -const amino_1 = require('@cosmjs/amino') -const crypto_1 = require('@cosmjs/crypto') -const encoding_1 = require('@cosmjs/encoding') -const utils_1 = require('@cosmjs/utils') -const ledger_cosmos_js_1 = __importDefault(require('ledger-cosmos-js')) -const semver_1 = __importDefault(require('semver')) -/* eslint-enable */ -function unharden(hdPath) { - return hdPath.map((n) => (n.isHardened() ? n.toNumber() - 2 ** 31 : n.toNumber())) -} -const cosmosHdPath = amino_1.makeCosmoshubPath(0) -const cosmosBech32Prefix = 'cosmos' -const requiredCosmosAppVersion = '1.5.3' -class LaunchpadLedger { - constructor(transport, options = {}) { - var _a, _b, _c - const defaultOptions = { - hdPaths: [cosmosHdPath], - prefix: cosmosBech32Prefix, - testModeAllowed: false, - } - this.testModeAllowed = - (_a = options.testModeAllowed) !== null && _a !== void 0 ? _a : defaultOptions.testModeAllowed - this.hdPaths = (_b = options.hdPaths) !== null && _b !== void 0 ? _b : defaultOptions.hdPaths - this.prefix = (_c = options.prefix) !== null && _c !== void 0 ? _c : defaultOptions.prefix - this.app = new ledger_cosmos_js_1.default(transport) - } - async getCosmosAppVersion() { - await this.verifyCosmosAppIsOpen() - utils_1.assert(this.app, 'Cosmos Ledger App is not connected') - const response = await this.app.getVersion() - this.handleLedgerErrors(response) - // eslint-disable-next-line @typescript-eslint/naming-convention - const { major, minor, patch, test_mode: testMode } = response - this.verifyAppMode(testMode) - return `${major}.${minor}.${patch}` - } - async getPubkey(hdPath) { - await this.verifyDeviceIsReady() - utils_1.assert(this.app, 'Cosmos Ledger App is not connected') - const hdPathToUse = hdPath || this.hdPaths[0] - // ledger-cosmos-js hardens the first three indices - const response = await this.app.publicKey(unharden(hdPathToUse)) - this.handleLedgerErrors(response) - return Uint8Array.from(response.compressed_pk) - } - async getPubkeys() { - return this.hdPaths.reduce( - (promise, hdPath) => - promise.then(async (pubkeys) => [...pubkeys, await this.getPubkey(hdPath)]), - Promise.resolve([]) - ) - } - async getCosmosAddress(pubkey) { - const pubkeyToUse = pubkey || (await this.getPubkey()) - return ledger_cosmos_js_1.default.getBech32FromPK(this.prefix, Buffer.from(pubkeyToUse)) - } - async sign(message, hdPath) { - await this.verifyDeviceIsReady() - utils_1.assert(this.app, 'Cosmos Ledger App is not connected') - const hdPathToUse = hdPath || this.hdPaths[0] - // ledger-cosmos-js hardens the first three indices - const response = await this.app.sign(unharden(hdPathToUse), encoding_1.fromUtf8(message)) - this.handleLedgerErrors(response, 'Transaction signing request was rejected by the user') - return crypto_1.Secp256k1Signature.fromDer(response.signature).toFixedLength() - } - verifyAppMode(testMode) { - if (testMode && !this.testModeAllowed) { - throw new Error( - `DANGER: The Cosmos Ledger app is in test mode and should not be used on mainnet!` - ) - } - } - async getOpenAppName() { - utils_1.assert(this.app, 'Cosmos Ledger App is not connected') - const response = await this.app.appInfo() - this.handleLedgerErrors(response) - return response.appName - } - async verifyAppVersion() { - const version = await this.getCosmosAppVersion() - if (!semver_1.default.gte(version, requiredCosmosAppVersion)) { - throw new Error('Outdated version: Please update Cosmos Ledger App to the latest version.') - } - } - async verifyCosmosAppIsOpen() { - const appName = await this.getOpenAppName() - if (appName.toLowerCase() === `dashboard`) { - throw new Error(`Please open the Cosmos Ledger app on your Ledger device.`) - } - // if (appName.toLowerCase() !== `cosmos`) { - // throw new Error(`Please close ${appName} and open the Cosmos Ledger app on your Ledger device.`); - // } - } - async verifyDeviceIsReady() { - await this.verifyAppVersion() - await this.verifyCosmosAppIsOpen() - } - handleLedgerErrors( - /* eslint-disable @typescript-eslint/naming-convention */ - { error_message: errorMessage = 'No errors', device_locked: deviceLocked = false }, - /* eslint-enable */ - rejectionMessage = 'Request was rejected by the user' - ) { - if (deviceLocked) { - throw new Error('Ledger’s screensaver mode is on') - } - switch (errorMessage) { - case 'U2F: Timeout': - throw new Error('Connection timed out. Please try again.') - case 'Cosmos app does not seem to be open': - throw new Error('Cosmos app is not open') - case 'Command not allowed': - throw new Error('Transaction rejected') - case 'Transaction rejected': - throw new Error(rejectionMessage) - case 'Unknown Status Code: 26628': - throw new Error('Ledger’s screensaver mode is on') - case 'Instruction not supported': - throw new Error( - `Your Cosmos Ledger App is not up to date. Please update to version ${requiredCosmosAppVersion}.` - ) - case 'No errors': - break - default: - throw new Error(`Ledger Native Error: ${errorMessage}`) - } - } -} -exports.LaunchpadLedger = LaunchpadLedger -//# sourceMappingURL=launchpadledger.js.map diff --git a/@cosmjs/ledger-amino/build/launchpadledger.js.map b/@cosmjs/ledger-amino/build/launchpadledger.js.map deleted file mode 100644 index 9e026cd9..00000000 --- a/@cosmjs/ledger-amino/build/launchpadledger.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"launchpadledger.js","sourceRoot":"","sources":["../src/launchpadledger.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/@cosmjs/ledger-amino/build/ledgersigner.d.ts b/@cosmjs/ledger-amino/build/ledgersigner.d.ts deleted file mode 100644 index 27592aad..00000000 --- a/@cosmjs/ledger-amino/build/ledgersigner.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// -import { AccountData, AminoSignResponse, OfflineAminoSigner, StdSignDoc } from "@cosmjs/amino"; -import Transport from "@ledgerhq/hw-transport"; -import { LaunchpadLedgerOptions } from "./launchpadledger"; -export declare class LedgerSigner implements OfflineAminoSigner { - private readonly ledger; - private readonly hdPaths; - private accounts?; - constructor(transport: Transport, options?: LaunchpadLedgerOptions); - getAccounts(): Promise; - signAmino(signerAddress: string, signDoc: StdSignDoc): Promise; -} diff --git a/@cosmjs/ledger-amino/build/ledgersigner.js b/@cosmjs/ledger-amino/build/ledgersigner.js deleted file mode 100644 index 23687243..00000000 --- a/@cosmjs/ledger-amino/build/ledgersigner.js +++ /dev/null @@ -1,39 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.LedgerSigner = void 0; -const amino_1 = require("@cosmjs/amino"); -const launchpadledger_1 = require("./launchpadledger"); -class LedgerSigner { - constructor(transport, options = {}) { - this.hdPaths = options.hdPaths || [amino_1.makeCosmoshubPath(0)]; - this.ledger = new launchpadledger_1.LaunchpadLedger(transport, options); - } - async getAccounts() { - if (!this.accounts) { - const pubkeys = await this.ledger.getPubkeys(); - this.accounts = await Promise.all(pubkeys.map(async (pubkey) => ({ - algo: "secp256k1", - address: await this.ledger.getCosmosAddress(pubkey), - pubkey: pubkey, - }))); - } - return this.accounts; - } - async signAmino(signerAddress, signDoc) { - const accounts = this.accounts || (await this.getAccounts()); - const accountIndex = accounts.findIndex((account) => account.address === signerAddress); - if (accountIndex === -1) { - throw new Error(`Address ${signerAddress} not found in wallet`); - } - const message = amino_1.serializeSignDoc(signDoc); - const accountForAddress = accounts[accountIndex]; - const hdPath = this.hdPaths[accountIndex]; - const signature = await this.ledger.sign(message, hdPath); - return { - signed: signDoc, - signature: amino_1.encodeSecp256k1Signature(accountForAddress.pubkey, signature), - }; - } -} -exports.LedgerSigner = LedgerSigner; -//# sourceMappingURL=ledgersigner.js.map \ No newline at end of file diff --git a/@cosmjs/ledger-amino/build/ledgersigner.js.map b/@cosmjs/ledger-amino/build/ledgersigner.js.map deleted file mode 100644 index e06fe33f..00000000 --- a/@cosmjs/ledger-amino/build/ledgersigner.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ledgersigner.js","sourceRoot":"","sources":["../src/ledgersigner.ts"],"names":[],"mappings":";;;AAAA,yCAQuB;AAIvB,uDAA4E;AAE5E,MAAa,YAAY;IAKvB,YAAmB,SAAoB,EAAE,UAAkC,EAAE;QAC3E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,yBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,GAAG,IAAI,iCAAe,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAEM,KAAK,CAAC,WAAW;QACtB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;YAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YAC/C,IAAI,CAAC,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC7B,IAAI,EAAE,WAAoB;gBAC1B,OAAO,EAAE,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC;gBACnD,MAAM,EAAE,MAAM;aACf,CAAC,CAAC,CACJ,CAAC;SACH;QAED,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAEM,KAAK,CAAC,SAAS,CAAC,aAAqB,EAAE,OAAmB;QAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC7D,MAAM,YAAY,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC;QAExF,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE;YACvB,MAAM,IAAI,KAAK,CAAC,WAAW,aAAa,sBAAsB,CAAC,CAAC;SACjE;QAED,MAAM,OAAO,GAAG,wBAAgB,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,iBAAiB,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1D,OAAO;YACL,MAAM,EAAE,OAAO;YACf,SAAS,EAAE,gCAAwB,CAAC,iBAAiB,CAAC,MAAM,EAAE,SAAS,CAAC;SACzE,CAAC;IACJ,CAAC;CACF;AA1CD,oCA0CC"} \ No newline at end of file diff --git a/@cosmjs/ledger-amino/package.json b/@cosmjs/ledger-amino/package.json deleted file mode 100644 index 1eecc2f4..00000000 --- a/@cosmjs/ledger-amino/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "@cosmjs/ledger-amino", - "version": "0.25.5", - "description": "A library for signing Amino-encoded transactions using Ledger devices", - "contributors": [ - "Will Clark " - ], - "license": "Apache-2.0", - "main": "build/index.js", - "types": "build/index.d.ts", - "files": [ - "build/", - "*.md", - "!*.spec.*", - "!**/testdata/" - ], - "repository": { - "type": "git", - "url": "https://github.com/CosmWasm/cosmjs/tree/main/packages/ledger-amino" - }, - "publishConfig": { - "access": "public" - }, - "scripts": { - "docs": "typedoc --options typedoc.js", - "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", - "format-text": "prettier --write \"./*.md\"", - "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", - "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", - "prebuild": "shx rm -rf ./build", - "build": "tsc", - "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", - "test-node": "node jasmine-testrunner.js", - "test": "yarn build-or-skip && yarn test-node", - "demo-node": "yarn build-or-skip && node ./demo/node.js", - "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", - "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.demo.config.js" - }, - "dependencies": { - "@cosmjs/amino": "^0.25.5", - "@cosmjs/utils": "^0.25.5", - "ledger-cosmos-js": "^2.1.8", - "semver": "^7.3.2" - }, - "devDependencies": { - "@cosmjs/launchpad": "^0.25.5", - "@cosmjs/stargate": "^0.25.5", - "@ledgerhq/hw-transport": "^5.25.0", - "@ledgerhq/hw-transport-node-hid": "^5.25.0", - "@ledgerhq/hw-transport-webusb": "^5.25.0", - "@types/ledgerhq__hw-transport-node-hid": "^4.22.2", - "@types/ledgerhq__hw-transport-webusb": "^4.70.1", - "@types/semver": "^7.3.4" - }, - "gitHead": "9a0f120567021d51a8f37bcaf39cc3de4b7d9a3d" -} diff --git a/assets/images/icons/usb_device.svg b/assets/images/icons/usb_device.svg new file mode 100644 index 00000000..b1180a26 --- /dev/null +++ b/assets/images/icons/usb_device.svg @@ -0,0 +1,3 @@ + + + diff --git a/components/AccountAvatar/index.tsx b/components/AccountAvatar/index.tsx index 77923a68..df2dc78e 100644 --- a/components/AccountAvatar/index.tsx +++ b/components/AccountAvatar/index.tsx @@ -1,4 +1,4 @@ -import { Box, Avatar, Typography, Link, Snackbar } from '@material-ui/core' +import { Box, Avatar, Typography, Link, Snackbar, useTheme } from '@material-ui/core' import React from 'react' import { Alert } from '@material-ui/lab' import useTranslation from 'next-translate/useTranslation' @@ -6,6 +6,9 @@ import CopyIcon from '../../assets/images/icons/icon_copy.svg' import useIconProps from '../../misc/useIconProps' import cryptocurrencies from '../../misc/cryptocurrencies' import useStyles from './styles' +import { CustomTheme } from '../../misc/theme' +import { useWalletsContext } from '../../contexts/WalletsContext' +import LedgerIcon from '../../assets/images/icons/usb_device.svg' interface AccountAvatarProps { address?: { @@ -19,6 +22,7 @@ interface AccountAvatarProps { hideAddress?: boolean disableCopyAddress?: boolean size?: 'small' | 'base' | 'large' + ledgerIconDisabled?: boolean } const AccountAvatar: React.FC = ({ @@ -27,12 +31,16 @@ const AccountAvatar: React.FC = ({ size = 'base', disableCopyAddress, address, + ledgerIconDisabled, }) => { const crypto = cryptocurrencies[account ? account.crypto : address.crypto] const { t } = useTranslation('common') const iconProps = useIconProps() + const themeStyle: CustomTheme = useTheme() const classes = useStyles() const [isCopySuccess, setIsCopySuccess] = React.useState(false) + const { wallets } = useWalletsContext() + const walletAccountInfo = { ...account, ...wallets.find((wal) => wal.id === account.walletId) } let avatarClass = '' let titleVariant: 'h3' | 'h5' | 'body1' = 'h5' @@ -53,7 +61,7 @@ const AccountAvatar: React.FC = ({ <> - + {account ? account.name : address.moniker} @@ -83,6 +91,12 @@ const AccountAvatar: React.FC = ({ ) : null} + {walletAccountInfo.type === 'ledger' ? ( + + ) : null} = ({ account }) => { +const AccountCard: React.FC = ({ account, ledgerIconDisabled }) => { const classes = useStyles() const theme = useTheme() const iconProps = useIconProps() @@ -62,7 +63,7 @@ const AccountCard: React.FC = ({ account }) => { }} > - + diff --git a/components/AccountDetailCard/index.tsx b/components/AccountDetailCard/index.tsx index 594deada..1fd3e800 100644 --- a/components/AccountDetailCard/index.tsx +++ b/components/AccountDetailCard/index.tsx @@ -213,7 +213,7 @@ const AccountDetailCard: React.FC = ({ onClose={() => setDelegateDialogOpen(false)} account={account} availableTokens={availableTokens} - validators={validators.filter(({ status }) => status === 'active')} + validators={validators} /> = ({ account }) => { { - const targetClassName = String((e.target as any).className) - if (targetClassName.includes('MuiBox-root') || targetClassName.includes('MuiCard-root')) { + if ( + String((e.target as any).className).includes('MuiButton-label') === false && + String((e.target as any).id) !== 'button' && + String((e.target as any).id) !== 'icon_star_marked_svg__Path_2_' + ) { router.push(`/account/${account.address}`) } }} @@ -105,6 +108,7 @@ const AccountStatCard: React.FC = ({ account }) => { - - - - ) -} - -export default ConfirmProposalContent diff --git a/components/CreateProposalForm/CreateProposalContent.tsx b/components/CreateProposalForm/CreateProposalContent.tsx deleted file mode 100644 index a357e28c..00000000 --- a/components/CreateProposalForm/CreateProposalContent.tsx +++ /dev/null @@ -1,283 +0,0 @@ -import React from 'react' -import { Box, Button, InputAdornment, TextField, Typography, Grid } from '@material-ui/core' -import { Autocomplete } from '@material-ui/lab' -import useTranslation from 'next-translate/useTranslation' -import keyBy from 'lodash/keyBy' -import useIconProps from '../../misc/useIconProps' -import { useGetStyles } from './styles' -import DropDownIcon from '../../assets/images/icons/icon_arrow_down_input_box.svg' -import { useWalletsContext } from '../../contexts/WalletsContext' - -interface CreateProposalContentProps { - account: Account - onNext( - proposalAccount: Account, - network: { name: string; id: string }, - type: { name: string; id: string }, - title: string, - description: string - ): void - networks: { name: string; id: string }[] -} - -const CreateProposalContent: React.FC = ({ - account, - onNext, - networks, -}) => { - const { classes } = useGetStyles() - const { t } = useTranslation('common') - const { accounts } = useWalletsContext() - - const iconProps = useIconProps() - - const types = [ - { - name: `${t('Text')}`, - id: 'Text', - }, - { - name: `${t('ParameterChange')}`, - id: 'ParameterChange', - }, - { - name: `${t('SoftwareUpgrade')}`, - id: 'SoftwareUpgrade', - }, - { - name: `${t('CommunityPoolSpend')}`, - id: 'CommunityPoolSpend', - }, - ] - - const [network, setNetwork] = React.useState<{ name: string; id: string }>() - - const [type, setType] = React.useState<{ name: string; id: string }>() - - const [proposalAccount, setProposalAccount] = React.useState(account) - - const accountsMap = keyBy(accounts, 'address') - - const networksMap = keyBy(networks, 'id') - - const typesMap = keyBy(types, 'id') - - const [description, setDescription] = React.useState('') - - const [title, setTitle] = React.useState('') - - const [memo, setMemo] = React.useState('') - - return ( - - - {t('create proposal')} - - - - {t('address')} - - - address)} - getOptionLabel={(option) => - `${accountsMap[option].name} ${accountsMap[option].address}` - } - openOnFocus - fullWidth - filterOptions={(options: string[], { inputValue }: any) => { - return options.filter((o) => - accountsMap[o].name.toLowerCase().includes(inputValue.toLowerCase()) - ) - }} - onChange={(_e, address: string) => setProposalAccount(accountsMap[address])} - renderOption={(address) => ( - - {`${accountsMap[address].name} ${accountsMap[address].address}`} - - )} - renderInput={({ InputProps, inputProps, ...params }) => ( - - - - ), - }} - /> - )} - /> - - - - - {t('network')} - - - id)} - getOptionLabel={(option) => networksMap[option].name} - openOnFocus - fullWidth - filterOptions={(options: string[], { inputValue }: any) => - options.filter((o) => - networksMap[o].name.toLowerCase().includes(inputValue.toLowerCase()) - ) - } - onChange={(_e, id: string) => setNetwork(networksMap[id])} - renderOption={(id) => ( - - {networksMap[id].name} - - )} - renderInput={({ InputProps, inputProps, ...params }) => ( - - - - ), - }} - /> - )} - /> - - - - - {t('type')} - - - id)} - getOptionLabel={(option) => typesMap[option].name} - openOnFocus - fullWidth - filterOptions={(options: string[], { inputValue }: any) => - options.filter((o) => - typesMap[o].name.toLowerCase().includes(inputValue.toLowerCase()) - ) - } - onChange={(_e, id: string) => setType(typesMap[id])} - renderOption={(id) => ( - - {typesMap[id].name} - - )} - renderInput={({ InputProps, inputProps, ...params }) => ( - - - - ), - }} - /> - )} - /> - - - - - - - {t('title')} - - setTitle(e.target.value)} - /> - - - - - {t('description')} - - setDescription(e.target.value)} - /> - - - - - {t('memo')} - - setMemo(e.target.value)} - /> - - - - - - - ) -} - -export default CreateProposalContent diff --git a/components/CreateProposalForm/index.tsx b/components/CreateProposalForm/index.tsx index bd5bc5a4..8b57c1b9 100644 --- a/components/CreateProposalForm/index.tsx +++ b/components/CreateProposalForm/index.tsx @@ -1,112 +1,303 @@ import React from 'react' -import { Dialog } from '@material-ui/core' +import { + Box, + Button, + InputAdornment, + TextField, + Typography, + Grid, + CircularProgress, + useTheme, +} from '@material-ui/core' +import { Autocomplete } from '@material-ui/lab' import useTranslation from 'next-translate/useTranslation' -import useStateHistory from '../../misc/useStateHistory' -import CreateProposalContent from './CreateProposalContent' -import ConfirmProposalContent from './ConfirmProposalContent' -import SecurityPassword from '../SecurityPasswordDialogContent' - -enum CreateProposalStage { - Create = 'select amount', - Confirm = 'select validators', -} - -export interface Proposal { - proposalAccount: Account - network: { name: string; id: string } - type: { name: string; id: string } - title: string - description: string - memo?: string -} +import keyBy from 'lodash/keyBy' +import invoke from 'lodash/invoke' +import last from 'lodash/last' +import { useRouter } from 'next/router' +import useIconProps from '../../misc/useIconProps' +import { useGetStyles } from './styles' +import DropDownIcon from '../../assets/images/icons/icon_arrow_down_input_box.svg' +import { useWalletsContext } from '../../contexts/WalletsContext' +import chains from '../../misc/chains' interface CreateProposalFormProps { account: Account - networks: { name: string; id: string }[] -} - -interface Content { - title: string - content: React.ReactNode } -const CreateProposalForm: React.FC = ({ account, networks }) => { +const CreateProposalForm: React.FC = ({ account }) => { + const { classes } = useGetStyles() + const theme = useTheme() const { t } = useTranslation('common') - const [open, setOpen] = React.useState(false) - const [loading, setLoading] = React.useState(false) - const [proposal, setProposal] = React.useState() - const [stage, setStage] = useStateHistory(CreateProposalStage.Create) + const { accounts, password } = useWalletsContext() + const { + query: { network: defaultNetwork }, + } = useRouter() - const createDraft = React.useCallback( - ( - proposalAccount: Account, - network: { name: string; id: string }, - type: { name: string; id: string }, - title: string, - description: string, - memo?: string - ) => { - setProposal({ proposalAccount, network, type, title, description, memo }) - setStage(CreateProposalStage.Confirm) - }, - [setStage] - ) + const iconProps = useIconProps() - const enterPassword = () => { - setOpen(true) - } + const types = [ + '/cosmos.gov.v1beta1.TextProposal', + '/cosmos.params.v1beta1.ParameterChangeProposal', + '/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal', + '/cosmos.distribution.v1beta1.CommunityPoolSpendProposal', + ] - const transactionData = {} + const [networkKey, setNetworkKey] = React.useState(String(defaultNetwork)) + const network = chains[networkKey] - const confirmWithPassword = React.useCallback( - async (securityPassword: string) => { - try { - setLoading(true) - // handle upload to backend later - setLoading(false) - } catch (err) { - setLoading(false) - console.log(err) - } - }, - [transactionData] - ) + const [type, setType] = React.useState('') + + const [proposalAccount, setProposalAccount] = React.useState(account) + const accountsMap = keyBy(accounts, 'address') - const content: Content = React.useMemo(() => { - switch (stage) { - case CreateProposalStage.Confirm: - return { - title: 'proposal/create proposal/confirm proposal', - content: ( - - ), - } - case CreateProposalStage.Create: - default: - return { - title: 'proposal/create proposal', - content: ( - - ), - } + const [description, setDescription] = React.useState('') + const [title, setTitle] = React.useState('') + const [memo, setMemo] = React.useState('') + const [loading, setLoading] = React.useState(false) + + const onNext = async () => { + try { + setLoading(true) + const msg = { + typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', + value: { + content: { + typeUrl: type, + value: { + title, + description, + }, + }, + initialDeposit: [], + proposer: proposalAccount.address, + }, + } + await invoke(window, 'forboleX.sendTransaction', password, proposalAccount.address, { + msgs: [msg], + memo, + }) + setLoading(false) + } catch (err) { + setLoading(false) } - }, [stage, t]) + } return ( - <> - {content.content} - - - - + + + {t('create proposal')} + + + + {t('address')} + + + address)} + getOptionLabel={(option) => + `${accountsMap[option].name} ${accountsMap[option].address}` + } + openOnFocus + fullWidth + filterOptions={(options: string[], { inputValue }: any) => { + return options.filter((o) => + accountsMap[o].name.toLowerCase().includes(inputValue.toLowerCase()) + ) + }} + onChange={(_e, address: string) => setProposalAccount(accountsMap[address])} + renderOption={(address) => ( + + {`${accountsMap[address].name} ${accountsMap[address].address}`} + + )} + renderInput={({ InputProps, inputProps, ...params }) => ( + + + + ), + }} + /> + )} + /> + + + + + {t('network')} + + + chainId)} + getOptionLabel={(option) => chains[option].name} + openOnFocus + fullWidth + filterOptions={(options: string[], { inputValue }: any) => + options.filter((o) => + chains[o].name.toLowerCase().includes(inputValue.toLowerCase()) + ) + } + onChange={(_e, id: string) => setNetworkKey(id)} + renderOption={(id) => ( + + + {chains[id].name} - {chains[id].crypto} + + + )} + renderInput={({ InputProps, inputProps, ...params }) => ( + + + + ), + }} + /> + )} + /> + + + + + {t('type')} + + + t(last(option.split('.')))} + openOnFocus + fullWidth + filterOptions={(options: string[], { inputValue }: any) => + options.filter((o) => + t(last(o.split('.'))) + .toLowerCase() + .includes(inputValue.toLowerCase()) + ) + } + onChange={(_e, id: string) => setType(id)} + renderOption={(id) => {t(last(id.split('.')))}} + renderInput={({ InputProps, ...params }) => ( + + + + ), + }} + /> + )} + /> + + + + + + + {t('title')} + + setTitle(e.target.value)} + /> + + + + + {t('description')} + + setDescription(e.target.value)} + /> + + + + + {t('memo')} + + setMemo(e.target.value)} + /> + + + + + + ) } diff --git a/components/CreateProposalForm/styles.ts b/components/CreateProposalForm/styles.ts index f6e2134e..173293f4 100644 --- a/components/CreateProposalForm/styles.ts +++ b/components/CreateProposalForm/styles.ts @@ -14,6 +14,9 @@ export const useGetStyles = () => { confirmTitle: { fontSize: theme.spacing(4), }, + input: { + backgroundColor: theme.palette.background.paper, + }, }), { name: 'HookGlobalStyles', diff --git a/components/DelegationDialog/index.tsx b/components/DelegationDialog/index.tsx index e558f3d0..1d26641f 100644 --- a/components/DelegationDialog/index.tsx +++ b/components/DelegationDialog/index.tsx @@ -91,10 +91,10 @@ const DelegationDialog: React.FC = ({ availableTokens.tokens_prices ) return { - type: 'cosmos-sdk/MsgDelegate', + typeUrl: '/cosmos.staking.v1beta1.MsgDelegate', value: { - delegator_address: account.address, - validator_address: r.validator.address, + delegatorAddress: account.address, + validatorAddress: r.validator.address, amount: { amount: Math.round(coinsToSend.amount).toString(), denom: coinsToSend.denom, diff --git a/components/DepositDialog/ConfirmAmount.tsx b/components/DepositDialog/ConfirmAmount.tsx deleted file mode 100644 index 033f9445..00000000 --- a/components/DepositDialog/ConfirmAmount.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { - Button, - DialogActions, - DialogContent, - Divider, - Typography, - useTheme, - Box, -} from '@material-ui/core' -import useTranslation from 'next-translate/useTranslation' -import React from 'react' -import dynamic from 'next/dynamic' -import { useGeneralContext } from '../../contexts/GeneralContext' -import useStyles from './styles' -import { formatCrypto, formatTokenAmount } from '../../misc/utils' - -const ReactJson = dynamic(() => import('react-json-view'), { ssr: false }) - -interface ConfirmAmountProps { - account: Account - proposal: Proposal - gasFee: TokenAmount - memo: string - amount: number - onConfirm(): void - rawTransactionData: any -} - -const ConfirmAmount: React.FC = ({ - account, - gasFee, - proposal, - memo, - onConfirm, - amount, - rawTransactionData, -}) => { - const { t, lang } = useTranslation('common') - const classes = useStyles() - const { theme: themeSetting } = useGeneralContext() - const [viewData, setViewData] = React.useState(false) - const toggleViewData = () => { - setViewData(!viewData) - } - - return ( - <> - - - - - {t('address')} - - {account.address} - - - - - {t('deposit to')} - - {`${t('proposal')} #${proposal.id}`} - - - - - {t('amount')} - - {formatCrypto(amount, account.crypto, lang)} - - - - - {t('memo')} - - {memo || 'N/A'} - - - - - {t('fee')} - - {formatTokenAmount(gasFee, account.crypto, lang)} - - - - - - - {viewData ? ( - - - - ) : null} - - - - - - ) -} - -export default ConfirmAmount diff --git a/components/DepositDialog/InputAmount.tsx b/components/DepositDialog/InputAmount.tsx index b4b8a4de..91c1800e 100644 --- a/components/DepositDialog/InputAmount.tsx +++ b/components/DepositDialog/InputAmount.tsx @@ -2,11 +2,13 @@ import { Box, Button, + CircularProgress, DialogActions, DialogContent, InputAdornment, TextField, Typography, + useTheme, } from '@material-ui/core' import { Autocomplete } from '@material-ui/lab' import useTranslation from 'next-translate/useTranslation' @@ -19,37 +21,70 @@ import useStyles from './styles' import DropDownIcon from '../../assets/images/icons/icon_arrow_down_input_box.svg' import TokenAmountInput from '../TokenAmountInput' import { getTokenAmountFromDenoms, formatCrypto } from '../../misc/utils' -import { useWalletsContext } from '../../contexts/WalletsContext' interface InputAmountProps { - account: Account + loading: boolean crypto: Cryptocurrency - onNext(voteAccount: Account, amount: number, memo?: string): void + onNext(address: string, amount: number, denom: string, memo: string): void proposal: Proposal + open: boolean availableTokens: AvailableTokens + accounts: Account[] + address: string + setAddress: React.Dispatch> +} + +const calculateRemainingTime = (timeString: string) => + intervalToDuration({ + end: new Date(timeString || null), + start: new Date(), + }) + +const formatDoubleDigits = (num: number) => `0${num}`.slice(-2) + +const Timer: React.FC<{ timeString: string }> = ({ timeString }) => { + const { t } = useTranslation('common') + const [remainingTime, setRemainingTime] = React.useState(calculateRemainingTime(timeString)) + + React.useEffect(() => { + const interval = setInterval(() => { + setRemainingTime(calculateRemainingTime(timeString)) + }, 1000) + return () => interval && clearInterval(interval) + }, []) + + return ( + + {`${t('deposit end in', { + days: remainingTime.days, + hours: remainingTime.hours, + minutes: formatDoubleDigits(remainingTime.minutes), + seconds: formatDoubleDigits(remainingTime.seconds), + })}`} + + ) } const InputAmount: React.FC = ({ + loading, crypto, - account, onNext, - availableTokens, proposal, + open, + availableTokens, + accounts, + address, + setAddress, }) => { const { t, lang } = useTranslation('common') const classes = useStyles() - const { accounts: allAccounts } = useWalletsContext() - const accounts = allAccounts.filter((a) => a.crypto === account.crypto) - const iconProps = useIconProps() - const remainingTime = intervalToDuration({ - end: new Date(proposal.depositEndTimeRaw), - start: Date.now(), - }) + const theme = useTheme() + const accountsMap = keyBy(accounts, 'address') const [memo, setMemo] = React.useState('') const [amount, setAmount] = React.useState('') - const [voteAccount, setVoteAccount] = React.useState(account) + const { availableAmount } = React.useMemo( () => ({ availableAmount: getTokenAmountFromDenoms( @@ -59,30 +94,41 @@ const InputAmount: React.FC = ({ }), [availableTokens] ) - const [denom, setDenom] = React.useState(Object.keys(availableAmount)[0]) + const [denom, setDenom] = React.useState(crypto.name) const insufficientFund = get(availableAmount, `${denom}.amount`, 0) < Number(amount) const changeAccount = (a: string) => { - setVoteAccount(accountsMap[a]) + setAddress(accountsMap[a].address) setDenom(accountsMap[a].crypto) } const remainAmount = () => { - if (proposal.totalDeposits[crypto.name].amount > proposal.minDeposit[crypto.name].amount) { + if ( + get(proposal, `totalDeposits.${crypto.name}.amount`, 0) > + get(proposal, `minDeposit.${crypto.name}.amount`, 0) + ) { return 0 } - return proposal.minDeposit[crypto.name].amount - proposal.totalDeposits[crypto.name].amount + return ( + get(proposal, `minDeposit.${crypto.name}.amount`, 0) - + get(proposal, `totalDeposits.${crypto.name}.amount`, 0) + ) } - return ( + React.useEffect(() => { + if (open) { + setAmount('') + setMemo('') + setAddress(accounts[0].address) + setDenom(crypto.name) + } + }, [open]) + + return address ? ( <> - - {`${t('deposit end in')} ${remainingTime.days} ${'days'} ${remainingTime.hours}:${ - remainingTime.minutes - }:${remainingTime.seconds}`} - + {proposal.depositEndTimeRaw ? : null} {`${t('remaining deposit amount')} `} {formatCrypto(remainAmount(), crypto.name, lang)} @@ -95,7 +141,7 @@ const InputAmount: React.FC = ({ address)} + options={accounts.map((ac) => ac.address)} getOptionLabel={(option) => `${accountsMap[option].name} \n ${accountsMap[option].address}` } @@ -106,10 +152,10 @@ const InputAmount: React.FC = ({ accountsMap[o].name.toLowerCase().includes(inputValue.toLowerCase()) ) }} - onChange={(_e, address: string) => changeAccount(address)} - renderOption={(address) => ( + onChange={(_e, adr: string) => changeAccount(adr)} + renderOption={(adr) => ( - {`${accountsMap[address].name}\n${accountsMap[address].address}`} + {`${accountsMap[adr].name}\n${accountsMap[adr].address}`} )} renderInput={({ InputProps, inputProps, ...params }) => ( @@ -119,7 +165,7 @@ const InputAmount: React.FC = ({ placeholder={t('select account')} inputProps={{ ...inputProps, - value: `${voteAccount.name} \n ${voteAccount.address}`, + value: `${accountsMap[address].name} \n ${accountsMap[address].address}`, }} // eslint-disable-next-line react/jsx-no-duplicate-props InputProps={{ @@ -143,9 +189,9 @@ const InputAmount: React.FC = ({ setAmount(e)} - onDenomChange={() => null} + onDenomChange={setDenom} availableAmount={availableAmount} /> @@ -185,15 +231,15 @@ const InputAmount: React.FC = ({ variant="contained" className={classes.button} color="primary" - disabled={!Number(amount) || insufficientFund} - onClick={() => onNext(voteAccount, Number(amount), memo)} + disabled={loading || !Number(amount) || insufficientFund} + onClick={() => onNext(address, Number(amount), denom, memo)} > - {t('next')} + {loading ? : t('next')} - ) + ) : null } export default InputAmount diff --git a/components/DepositDialog/index.tsx b/components/DepositDialog/index.tsx index 06ddd033..82c141e8 100644 --- a/components/DepositDialog/index.tsx +++ b/components/DepositDialog/index.tsx @@ -1,75 +1,41 @@ -import { Box, Dialog, DialogTitle, IconButton, Typography } from '@material-ui/core' +import { Dialog, DialogTitle, IconButton } from '@material-ui/core' import useTranslation from 'next-translate/useTranslation' import React from 'react' -import get from 'lodash/get' import { gql, useSubscription } from '@apollo/client' +import get from 'lodash/get' +import invoke from 'lodash/invoke' import CloseIcon from '../../assets/images/icons/icon_cross.svg' -import BackIcon from '../../assets/images/icons/icon_back.svg' -import DepositIcon from '../../assets/images/icons/icon_delegate_tx.svg' import useStyles from './styles' import useIconProps from '../../misc/useIconProps' import InputAmount from './InputAmount' -import useStateHistory from '../../misc/useStateHistory' -import Success from '../Success' -import SecurityPassword from '../SecurityPasswordDialogContent' -import { useWalletsContext } from '../../contexts/WalletsContext' import cryptocurrencies from '../../misc/cryptocurrencies' -import { getTokenAmountFromDenoms, formatCrypto } from '../../misc/utils' -import ConfirmAnswer from './ConfirmAmount' +import { getEquivalentCoinToSend } from '../../misc/utils' import { getLatestAccountBalance } from '../../graphql/queries/accountBalances' - -enum DepositStage { - SecurityPasswordStage = 'security password', - InputAmountStage = 'input amount', - ConfirmAnswerStage = 'confirm withdraw', - SuccessStage = 'success', -} +import { useWalletsContext } from '../../contexts/WalletsContext' interface DepositDialogProps { - network: { id: number; crypto: string; name: string; img: string } + network: Chain open: boolean onClose(): void proposal: Proposal - // crypto: Cryptocurrency } -interface Content { - title: string | React.ReactNode - content: React.ReactNode - dialogWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' -} - -const DepositDialog: React.FC = ({ - network, - open, - onClose, - proposal, - // crypto, -}) => { - const { t, lang } = useTranslation('common') +const DepositDialog: React.FC = ({ network, open, onClose, proposal }) => { + const { t } = useTranslation('common') const classes = useStyles() const iconProps = useIconProps() - const voteIconProps = useIconProps(8) - const [amount, setAmount] = React.useState(0) + const { password, accounts: allAccounts } = useWalletsContext() + const accounts = allAccounts.filter((a) => a.crypto === network.crypto) + const crypto = cryptocurrencies[network.crypto] - const [memo, setMemo] = React.useState('') const [loading, setLoading] = React.useState(false) - - const { password, accounts: allAccounts } = useWalletsContext() - const accounts = allAccounts.filter((a) => a.crypto === network?.crypto) - const [stage, setStage, toPrevStage, isPrevStageAvailable] = useStateHistory( - DepositStage.InputAmountStage - ) - const [voteAccount, setVoteAccount] = React.useState(accounts[0]) - const crypto = voteAccount - ? cryptocurrencies[voteAccount.crypto] - : Object.values(cryptocurrencies)[0] + const [address, setAddress] = React.useState('') const { data: balanceData } = useSubscription( gql` ${getLatestAccountBalance(crypto.name)} `, - { variables: { address: voteAccount ? voteAccount.address : '' } } + { variables: { address } } ) const availableTokens = get(balanceData, 'account[0].available[0]', { @@ -77,123 +43,54 @@ const DepositDialog: React.FC = ({ tokens_prices: [], }) - const defaultGasFee = voteAccount - ? getTokenAmountFromDenoms( - get(cryptocurrencies, `${voteAccount.crypto}.defaultGasFee.amount`, []), - availableTokens.tokens_prices - ) - : null - - React.useEffect(() => { - if (open) { - setAmount(undefined) - setMemo('') - setMemo('') - setLoading(false) - setStage(DepositStage.InputAmountStage, true) - } - }, [open]) - - const confirmWithPassword = React.useCallback(async (securityPassword: string) => { - try { - setLoading(true) - // TODO: handle transaction part later - setLoading(false) - setStage(DepositStage.SuccessStage, true) - } catch (err) { - setLoading(false) - console.log(err) - } - }, []) - const confirmAmount = React.useCallback( - (v: Account, a: number, m?: string) => { - setVoteAccount(v) - setAmount(a) - setMemo(m) - setStage(DepositStage.ConfirmAnswerStage) + async (depositor: string, amount: number, denom: string, memo: string) => { + try { + setLoading(true) + const coinsToSend = getEquivalentCoinToSend( + { amount, denom }, + availableTokens.coins, + availableTokens.tokens_prices + ) + const msg: TransactionMsgDeposit = { + typeUrl: '/cosmos.gov.v1beta1.MsgDeposit', + value: { + depositor, + proposalId: proposal.id, + amount: [{ amount: coinsToSend.amount.toString(), denom: coinsToSend.denom }], + }, + } + await invoke(window, 'forboleX.sendTransaction', password, depositor, { + msgs: [msg], + memo, + }) + setLoading(false) + onClose() + } catch (err) { + setLoading(false) + console.log(err) + } }, - [setStage] + [availableTokens] ) - const content: Content = React.useMemo(() => { - switch (stage) { - case DepositStage.SuccessStage: - return { - title: '', - dialogWidth: 'xs', - content: ( - - ), - } - case DepositStage.SecurityPasswordStage: - return { - title: '', - dialogWidth: 'sm', - content: ( - - ), - } - case DepositStage.ConfirmAnswerStage: - return { - title: ( - - - - {`${t('deposit')} ${formatCrypto(amount, voteAccount.crypto, lang)}`} - - - ), - dialogWidth: 'sm', - content: ( - setStage(DepositStage.SecurityPasswordStage)} - rawTransactionData="" - /> - ), - } - case DepositStage.InputAmountStage: - default: - return { - title: t('deposit'), - dialogWidth: 'sm', - content: ( - - ), - } - } - }, [stage, t]) - return ( - - {isPrevStageAvailable ? ( - - - - ) : null} + - {content.title ? {content.title} : null} - {content.content} + {t('deposit')} + ) } diff --git a/components/IBCTransferDialog/AddChannel.tsx b/components/IBCTransferDialog/AddChannel.tsx index c0204d33..9f7bf059 100644 --- a/components/IBCTransferDialog/AddChannel.tsx +++ b/components/IBCTransferDialog/AddChannel.tsx @@ -60,8 +60,8 @@ const AddChannel: React.FC = ({ onConfirm }) => { {get(chains, `${o}.name`, '')} @@ -83,8 +83,8 @@ const AddChannel: React.FC = ({ onConfirm }) => { startAdornment: chainId ? ( ) : null, endAdornment: ( diff --git a/components/IBCTransferDialog/SelectChain.tsx b/components/IBCTransferDialog/SelectChain.tsx index 6fa3888b..4fe7191e 100644 --- a/components/IBCTransferDialog/SelectChain.tsx +++ b/components/IBCTransferDialog/SelectChain.tsx @@ -53,7 +53,7 @@ const SelectChain: React.FC = ({ onConfirm, onAddChannelClick option: classes.listItem, }} options={Object.values(chains).map((chain) => chain.chainId)} - getOptionLabel={(option) => get(chains, `${option}.name`, '')} + getOptionLabel={(option) => get(chains, `${option}.name`, '') as string} openOnFocus fullWidth filterOptions={(options: string[], { inputValue }: any) => @@ -71,14 +71,14 @@ const SelectChain: React.FC = ({ onConfirm, onAddChannelClick } onChange={(e, id) => { setChainId(id) - setChannel(get(chains, `${id}.channel`, '')) + setChannel(get(chains, `${id}.channel`, '') as string) }} renderOption={(o) => ( {get(chains, `${o}.name`, '')} @@ -130,8 +130,8 @@ const SelectChain: React.FC = ({ onConfirm, onAddChannelClick startAdornment: chainId ? ( ) : null, endAdornment: ( diff --git a/components/IBCTransferDialog/index.tsx b/components/IBCTransferDialog/index.tsx index e31468ef..68b272df 100644 --- a/components/IBCTransferDialog/index.tsx +++ b/components/IBCTransferDialog/index.tsx @@ -80,14 +80,14 @@ const IBCTransferDialog: React.FC = ({ ) const msgs = [ { - type: 'cosmos-sdk/MsgTransfer', + typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', value: { - source_port: 'transfer', - source_channel: channel, + sourcePort: 'transfer', + sourceChannel: channel, token: { denom: coinsToSend.denom, amount: coinsToSend.amount.toString() }, sender: account.address, receiver: address, - timeout_timestamp: (Date.now() + 3600000) * 1000, + timeoutTimestamp: (Date.now() + 3600000) * 1000, }, }, ] diff --git a/components/Layout/LeftMenu.tsx b/components/Layout/LeftMenu.tsx index 17f04f9a..d77a77ba 100644 --- a/components/Layout/LeftMenu.tsx +++ b/components/Layout/LeftMenu.tsx @@ -1,6 +1,16 @@ import React from 'react' import useTranslation from 'next-translate/useTranslation' -import { Box, List, ListItem, ListItemIcon, ListItemText, Paper, useTheme } from '@material-ui/core' +import { + Avatar, + Box, + Button, + List, + ListItem, + ListItemIcon, + ListItemText, + Paper, + useTheme, +} from '@material-ui/core' import Link from 'next/link' import OverviewIcon from '../../assets/images/icons/icon_overview.svg' import WalletManageIcon from '../../assets/images/icons/icon_wallet_manage.svg' @@ -14,6 +24,8 @@ import useIconProps from '../../misc/useIconProps' import { MenuWidth } from '.' import { useGeneralContext } from '../../contexts/GeneralContext' import { CustomTheme } from '../../misc/theme' +import cryptocurrencies from '../../misc/cryptocurrencies' +import { useWalletsContext } from '../../contexts/WalletsContext' interface LeftMenuProps { activeItem: string @@ -27,6 +39,8 @@ const LeftMenu: React.FC = ({ activeItem, isMenuExpanded, setIsMe const iconProps = useIconProps(3) const classes = useStyles() const { theme } = useGeneralContext() + const { accounts } = useWalletsContext() + const favAccount = accounts.some((acc) => !!acc.fav) const items = React.useMemo( () => [ { @@ -103,33 +117,90 @@ const LeftMenu: React.FC = ({ activeItem, isMenuExpanded, setIsMe - {items.map((item) => { - const selected = item.href === activeItem - return ( - - - - {React.cloneElement(item.icon, { - fill: selected ? themeStyle.palette.primary.main : themeStyle.palette.grey[300], - })} - - - - - ) - })} + + {items.map((item) => { + const selected = item.href === activeItem + return ( + + + + {React.cloneElement(item.icon, { + fill: selected + ? themeStyle.palette.primary.main + : themeStyle.palette.grey[300], + })} + + + + + ) + })} + + + + + + {accounts.map((account) => { + const crypto = cryptocurrencies[account.crypto] + return account.fav ? ( + + + + + + + + + ) : ( + <> + ) + })} + ) diff --git a/components/Layout/index.tsx b/components/Layout/index.tsx index 9162a31a..d1f3c536 100644 --- a/components/Layout/index.tsx +++ b/components/Layout/index.tsx @@ -15,7 +15,7 @@ import ChromeExtDialog from '../ChromeExtDialog' export enum MenuWidth { Expanded = 32, - Collapsed = 11.5, + Collapsed = 10, } interface LayoutProps { diff --git a/components/Layout/styles.ts b/components/Layout/styles.ts index 6a081601..f5d1722c 100644 --- a/components/Layout/styles.ts +++ b/components/Layout/styles.ts @@ -7,7 +7,7 @@ const useStyles = makeStyles( top: 0, bottom: 0, left: 0, - overflow: 'auto', + overflow: 'hidden', overflowX: 'hidden', zIndex: 2, transition: 'width 0.2s ease-in-out', @@ -16,14 +16,56 @@ const useStyles = makeStyles( transition: 'margin-left 0.2s ease-in-out', }, menu: { - margin: theme.spacing(2, 2.5), + margin: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + height: '100%', }, menuItem: { height: theme.spacing(6), + paddingLeft: theme.spacing(1.5), + paddingRight: theme.spacing(1.5), marginBottom: theme.spacing(3), borderRadius: theme.spacing(1), whiteSpace: 'nowrap', }, + favMenu: { + marginTop: 'auto', + height: '30%', + }, + starredAccounts: { + transition: 'display 0.2s ease-in-out', + height: theme.spacing(6), + marginBottom: theme.spacing(3), + borderRadius: theme.spacing(1), + padding: theme.spacing(2), + paddingBottom: 0, + whiteSpace: 'nowrap', + }, + manageAccounts: { + transition: 'display 0.2s ease-in-out', + height: theme.spacing(6), + marginBottom: theme.spacing(3), + borderRadius: theme.spacing(1), + padding: theme.spacing(2), + paddingTop: 0, + overflowWrap: 'break-word', + wordWrap: 'break-word', + hyphens: 'auto', + }, + starButton: { + marginTop: theme.spacing(3), + marginLeft: theme.spacing(2), + borderRadius: theme.spacing(1), + width: 'fit-content', + }, + favMenuItem: { + height: theme.spacing(6), + borderRadius: theme.spacing(1), + whiteSpace: 'nowrap', + paddingLeft: theme.spacing(1.5), + paddingRight: theme.spacing(1.5), + }, navBar: { position: 'fixed', top: 0, @@ -33,7 +75,7 @@ const useStyles = makeStyles( display: 'flex', justifyContent: 'flex-end', alignItems: 'center', - padding: theme.spacing(5), + padding: theme.spacing(3), backgroundColor: theme.palette.background.default, zIndex: 1, }, diff --git a/components/ProposalDetail/DepositTable.tsx b/components/ProposalDetail/DepositTable.tsx index 3fdfe313..ad5f5746 100644 --- a/components/ProposalDetail/DepositTable.tsx +++ b/components/ProposalDetail/DepositTable.tsx @@ -8,6 +8,7 @@ import { Avatar, Typography, Card, + Link, } from '@material-ui/core' import useTranslation from 'next-translate/useTranslation' import React from 'react' @@ -20,7 +21,7 @@ interface DepositTableProps { proposal: Proposal crypto: Cryptocurrency tag: string - network: { id: number; crypto: string; name: string; img: string } + network: Chain } const DepositTable: React.FC = ({ tag, proposal, crypto, network }) => { @@ -77,15 +78,22 @@ const DepositTable: React.FC = ({ tag, proposal, crypto, netw {proposal.depositDetails.map((d, i) => { return ( - - - - {d.depositor.name} - + + + + + + {d.depositor.name || d.depositor.address} + + + diff --git a/components/ProposalDetail/DepositTime.tsx b/components/ProposalDetail/DepositTime.tsx index d00a97fc..43938576 100644 --- a/components/ProposalDetail/DepositTime.tsx +++ b/components/ProposalDetail/DepositTime.tsx @@ -1,4 +1,5 @@ import { Box, Typography } from '@material-ui/core' +import { differenceInDays } from 'date-fns' import useTranslation from 'next-translate/useTranslation' import React from 'react' import { useGetStyles } from './styles' @@ -11,6 +12,8 @@ const DepositTime: React.FC = ({ proposal }) => { const { classes } = useGetStyles() const { t } = useTranslation('common') + const dateDiff = differenceInDays(new Date(proposal.depositEndTime), new Date()) + return ( @@ -22,7 +25,7 @@ const DepositTime: React.FC = ({ proposal }) => { {`${t('deposited end time')}: ${proposal.depositEndTime}`} - {`(${t('in')} ${proposal.duration} ${proposal.duration > 1 ? t('days') : t('day')})`} + {`(${t('in')} ${dateDiff} ${dateDiff > 1 ? t('days') : t('day')})`} diff --git a/components/ProposalDetail/VoteTable.tsx b/components/ProposalDetail/VoteTable.tsx index 9c58c4f0..c237aa86 100644 --- a/components/ProposalDetail/VoteTable.tsx +++ b/components/ProposalDetail/VoteTable.tsx @@ -10,6 +10,7 @@ import { Card, Tabs, Tab, + Link, } from '@material-ui/core' import useTranslation from 'next-translate/useTranslation' import React from 'react' @@ -17,7 +18,7 @@ import { useGetStyles } from './styles' import { VoteDetail } from './index' import { useGeneralContext } from '../../contexts/GeneralContext' import TablePagination from '../TablePagination' -import { formatPercentage, formatCrypto } from '../../misc/utils' +// import { formatPercentage, formatCrypto } from '../../misc/utils' interface DepositTableProps { voteDetails: VoteDetail[] @@ -46,18 +47,18 @@ const VoteTable: React.FC = ({ voteDetails, crypto }) => { { label: 'voter', }, - { - label: 'voting power', - alignRight: true, - }, - { - label: 'voting power percentage', - alignRight: true, - }, - { - label: 'voting power override', - alignRight: true, - }, + // { + // label: 'voting power', + // alignRight: true, + // }, + // { + // label: 'voting power percentage', + // alignRight: true, + // }, + // { + // label: 'voting power override', + // alignRight: true, + // }, { label: 'answer', alignRight: true, @@ -98,16 +99,23 @@ const VoteTable: React.FC = ({ voteDetails, crypto }) => { return ( - - - {v.voter.name} - + + + + + {v.voter.name || v.voter.address} + + + - + {/* {formatCrypto(v.votingPower, crypto.name, lang)} @@ -121,7 +129,7 @@ const VoteTable: React.FC = ({ voteDetails, crypto }) => { {formatPercentage(v.votingPowerOverride, lang)} - + */} {t(v.answer)} diff --git a/components/ProposalDetail/VoteTime.tsx b/components/ProposalDetail/VoteTime.tsx index dc87b457..c47b8f1f 100644 --- a/components/ProposalDetail/VoteTime.tsx +++ b/components/ProposalDetail/VoteTime.tsx @@ -13,7 +13,7 @@ const VoteTime: React.FC = ({ proposal }) => { return ( - {`${t('voting time')}: ${proposal.votingStartTime} to ${proposal.votingEndTime} `} + {t('voting time', { from: proposal.votingStartTime, to: proposal.votingEndTime })} {`(${proposal.duration} ${proposal.duration > 1 ? t('days') : t('day')})`} diff --git a/components/ProposalDetail/index.tsx b/components/ProposalDetail/index.tsx index e7599a12..7bf154b2 100644 --- a/components/ProposalDetail/index.tsx +++ b/components/ProposalDetail/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Card, Typography, Avatar, Divider } from '@material-ui/core' +import { Box, Card, Typography, Avatar, Divider, Link } from '@material-ui/core' import useTranslation from 'next-translate/useTranslation' import { useGetStyles } from './styles' import ActiveStatus from './ActiveStatus' @@ -26,10 +26,11 @@ export interface VoteDetail { voter: { name: string image: string + address: string } - votingPower: number - votingPowerPercentage: number - votingPowerOverride: number + // votingPower: number + // votingPowerPercentage: number + // votingPowerOverride: number answer: string } @@ -49,7 +50,7 @@ interface ProposalDetailProps { voteSummary?: VoteSummary colors?: [string, string, string, string] voteDetails?: VoteDetail[] - network: { id: number; crypto: string; name: string; img: string } + network: Chain } const TimeContent: React.FC<{ proposal: Proposal }> = ({ proposal }) => { @@ -76,7 +77,7 @@ const ProposalDetail: React.FC = ({ return ( <> - + {`#${proposal.id}`} @@ -84,14 +85,22 @@ const ProposalDetail: React.FC = ({ {t('proposer')} - - - {proposal.proposer.name} - + + + + + + {proposal.proposer.name || proposal.proposer.address} + + + {proposal.title} @@ -108,7 +117,9 @@ const ProposalDetail: React.FC = ({ {`#${proposal.id}`} - {`${t('type')}: ${t(proposal.type)}`} + + {`${t('type')}: ${t(`${proposal.type}Proposal`)}`} + {`${t('description')}: `} {proposal.description} @@ -122,17 +133,13 @@ const ProposalDetail: React.FC = ({ ) : null} - {!proposal.isActive ? ( - - - - - - - ) : null} - {proposal.tag !== 'vote' ? ( - - ) : null} + + + + + + + { if (status === 'rejected') { return 'unbonded' } - if (status === 'removed') { + if (status === 'invalid') { return 'unknown' } return 'active' @@ -54,6 +54,10 @@ export const useGetStyles = (color?: string, status?: string) => { color: 'white', background: status === 'vote' ? theme.palette.primary.main : theme.palette.success.main, width: theme.spacing(12), + '&:hover': { + backgroundColor: + status === 'vote' ? theme.palette.primary.dark : theme.palette.success.dark, + }, }, number: { color: '#00000000', diff --git a/components/ProposalTable/Active.tsx b/components/ProposalTable/Active.tsx index c762bfa5..8f532683 100644 --- a/components/ProposalTable/Active.tsx +++ b/components/ProposalTable/Active.tsx @@ -13,8 +13,16 @@ const Active: React.FC = ({ status, onClick }) => { const { t } = useTranslation('common') return ( - onClick()}> - diff --git a/components/ProposalTable/index.tsx b/components/ProposalTable/index.tsx index cdbdf812..ac8c7abf 100644 --- a/components/ProposalTable/index.tsx +++ b/components/ProposalTable/index.tsx @@ -1,26 +1,24 @@ import React from 'react' -import { Box, Card, Typography, Avatar, Divider } from '@material-ui/core' +import { Box, Card, Typography, Avatar, Divider, Link as MLink } from '@material-ui/core' import useTranslation from 'next-translate/useTranslation' -import { useRouter } from 'next/router' +import Link from 'next/link' import { useGetStyles } from './styles' import Active from './Active' import InActive from './InActive' import VoteDialog from '../VoteDialog' import DepositDialog from '../DepositDialog' -import { useWalletsContext } from '../../contexts/WalletsContext' +import cryptocurrencies from '../../misc/cryptocurrencies' interface ProposalsTableProps { proposals: Proposal[] - network: { id: number; crypto: string; name: string; img: string } + network: Chain } const ProposalTable: React.FC = ({ proposals, network }) => { const { classes } = useGetStyles() const { t } = useTranslation('common') - const { accounts } = useWalletsContext() - const test = accounts.filter((x) => x.crypto === 'DARIC') - const router = useRouter() + const crypto = cryptocurrencies[network.crypto] const [voteDialogOpen, setVoteDialogOpen] = React.useState(false) const [depositDialogOpen, setDepositDialogOpen] = React.useState(false) @@ -41,56 +39,61 @@ const ProposalTable: React.FC = ({ proposals, network }) => {proposals.map((x) => { return ( - - - { - router.push(`/proposals/${x.id}`) - }} - > - {`#${x.id}`} - - { - router.push(`/proposals/${x.id}`) - }} - > - - {t('proposer')} - - - {x.proposer.name} + + + + + {`#${x.id}`} + + + + {t('proposer')} + e.stopPropagation()} + > + + + + {x.proposer.name || x.proposer.address} + + + + + {x.title} + + {x.description} + + + {x.tag === 'deposit' + ? t('deposit time', { from: x.submitTime, to: x.depositEndTime }) + : null} + {x.tag === 'vote' || x.tag === 'passed' + ? t('voting time', { from: x.votingStartTime, to: x.votingEndTime }) + : null} + {x.isActive ? ( + + {`(${t('in')} ${x.duration} ${x.duration > 1 ? t('days') : t('day')})`} + + ) : null} - {x.title} - - {x.description} - - - {`${t('voting time')}: ${x.votingStartTime} to ${x.votingEndTime}`} - {x.isActive ? ( - - {`(${t('in')} ${x.duration} ${x.duration > 1 ? t('days') : t('day')})`} - - ) : null} - - - - {x.tag === 'vote' || x.tag === 'deposit' ? ( - onClick(x)} /> - ) : ( - - )} + + {x.tag === 'vote' || x.tag === 'deposit' ? ( + onClick(x)} /> + ) : ( + + )} + + - - + ) })} diff --git a/components/ProposalTable/styles.ts b/components/ProposalTable/styles.ts index cf136251..2a2790ea 100644 --- a/components/ProposalTable/styles.ts +++ b/components/ProposalTable/styles.ts @@ -6,7 +6,7 @@ export const useGetStyles = (status?: string) => { if (status === 'rejected' || status === 'failed') { return 'unbonded' } - if (status === 'removed') { + if (status === 'invalid') { return 'unknown' } return 'active' @@ -65,6 +65,10 @@ export const useGetStyles = (status?: string) => { color: 'white', background: status === 'vote' ? theme.palette.primary.main : theme.palette.success.main, width: theme.spacing(12), + '&:hover': { + backgroundColor: + status === 'vote' ? theme.palette.primary.dark : theme.palette.success.dark, + }, }, }), { diff --git a/components/RedelegateDialog/index.tsx b/components/RedelegateDialog/index.tsx index 9ca8d5a8..6ebb196c 100644 --- a/components/RedelegateDialog/index.tsx +++ b/components/RedelegateDialog/index.tsx @@ -85,11 +85,11 @@ const RedelegationDialog: React.FC = ({ await invoke(window, 'forboleX.sendTransaction', password, account.address, { msgs: [ { - type: 'cosmos-sdk/MsgBeginRedelegate', + typeUrl: '/cosmos.staking.v1beta1.MsgBeginRedelegate', value: { - delegator_address: account.address, - validator_src_address: fromValidator.address, - validator_dst_address: toValidator.address, + delegatorAddress: account.address, + validatorSrcAddress: fromValidator.address, + validatorDstAddress: toValidator.address, amount: { amount: coinsToSend.amount.toString(), denom: coinsToSend.denom }, }, }, diff --git a/components/SendDialog/index.tsx b/components/SendDialog/index.tsx index 1b4021b0..05dce367 100644 --- a/components/SendDialog/index.tsx +++ b/components/SendDialog/index.tsx @@ -52,10 +52,10 @@ const SendDialog: React.FC = ({ account, availableTokens, open, availableTokens.tokens_prices ) return { - type: 'cosmos-sdk/MsgSend', + typeUrl: '/cosmos.bank.v1beta1.MsgSend', value: { - from_address: account.address, - to_address: r.address, + fromAddress: account.address, + toAddress: r.address, amount: [{ amount: coinsToSend.amount.toString(), denom: coinsToSend.denom }], }, } diff --git a/components/UndelegateDialog/index.tsx b/components/UndelegateDialog/index.tsx index 0a2f2f50..639f92ba 100644 --- a/components/UndelegateDialog/index.tsx +++ b/components/UndelegateDialog/index.tsx @@ -54,10 +54,10 @@ const UndelegationDialog: React.FC = ({ await invoke(window, 'forboleX.sendTransaction', password, account.address, { msgs: [ { - type: 'cosmos-sdk/MsgUndelegate', + typeUrl: '/cosmos.staking.v1beta1.MsgUndelegate', value: { - delegator_address: account.address, - validator_address: validator.address, + delegatorAddress: account.address, + validatorAddress: validator.address, amount: { amount: coinsToSend.amount.toString(), denom: coinsToSend.denom }, }, }, diff --git a/components/ValidatorsTable/InActive.tsx b/components/ValidatorsTable/InActive.tsx index ada90bab..a0816b32 100644 --- a/components/ValidatorsTable/InActive.tsx +++ b/components/ValidatorsTable/InActive.tsx @@ -1,20 +1,26 @@ -import { Box, Typography } from '@material-ui/core' +import { Box, Typography, Button } from '@material-ui/core' import React from 'react' +import useTranslation from 'next-translate/useTranslation' import { useGetStyles } from './styles' interface InActiveProps { status: string alignRight: boolean + onClick: () => void } -const InActive: React.FC = ({ status, alignRight }) => { +const InActive: React.FC = ({ status, alignRight, onClick }) => { const { classes } = useGetStyles(status, alignRight) + const { t } = useTranslation('common') return ( {status.charAt(0).toUpperCase() + status.slice(1)} + ) } diff --git a/components/ValidatorsTable/Table.tsx b/components/ValidatorsTable/Table.tsx index 478fea8a..10838c9b 100644 --- a/components/ValidatorsTable/Table.tsx +++ b/components/ValidatorsTable/Table.tsx @@ -186,17 +186,6 @@ const ValidatorsTable: React.FC = ({ - {/* - - - {v.location.name} - - */} {formatCrypto(v.votingPower, crypto.name, lang)} ( {formatPercentage(v.votingPower / totalVotingPower, lang)}) @@ -211,7 +200,11 @@ const ValidatorsTable: React.FC = ({ {v.isActive ? ( setDelegatingValidator(v)} /> ) : ( - + setDelegatingValidator(v)} + /> )} diff --git a/components/VoteDialog/SelectAnswer.tsx b/components/VoteDialog/SelectAnswer.tsx index 29d049ce..fb4d2fbc 100644 --- a/components/VoteDialog/SelectAnswer.tsx +++ b/components/VoteDialog/SelectAnswer.tsx @@ -1,10 +1,12 @@ import { Box, Button, + CircularProgress, DialogContent, InputAdornment, TextField, Typography, + useTheme, } from '@material-ui/core' import { Autocomplete } from '@material-ui/lab' import useTranslation from 'next-translate/useTranslation' @@ -16,32 +18,34 @@ import DropDownIcon from '../../assets/images/icons/icon_arrow_down_input_box.sv import { useWalletsContext } from '../../contexts/WalletsContext' interface SelectAnswerProps { - network: { id: number; crypto: string; name: string; img: string } + network: Chain onNext(voteAccount: Account, answer: { name: string; id: string }, memo?: string): void proposal: Proposal + loading: boolean } -const SelectAnswer: React.FC = ({ network, onNext, proposal }) => { +const SelectAnswer: React.FC = ({ network, onNext, proposal, loading }) => { const { t } = useTranslation('common') const classes = useStyles() const { accounts: allAccounts } = useWalletsContext() + const theme = useTheme() const accounts = allAccounts.filter((a) => a.crypto === network.crypto) const answers = [ { name: 'Yes', - id: '01', + id: '1', }, { name: 'No', - id: '02', + id: '2', }, { name: 'Veto', - id: '03', + id: '3', }, { name: 'Abstain', - id: '04', + id: '4', }, ] @@ -180,10 +184,10 @@ const SelectAnswer: React.FC = ({ network, onNext, proposal } variant="contained" className={classes.button} color="primary" - disabled={!!(answer === undefined)} + disabled={loading || answer === undefined} type="submit" > - {t('next')} + {loading ? : t('next')} diff --git a/components/VoteDialog/index.tsx b/components/VoteDialog/index.tsx index d1d80bef..6e7515d5 100644 --- a/components/VoteDialog/index.tsx +++ b/components/VoteDialog/index.tsx @@ -1,154 +1,67 @@ -import { Box, Dialog, DialogTitle, IconButton, Typography } from '@material-ui/core' +import { Dialog, DialogTitle, IconButton } from '@material-ui/core' import useTranslation from 'next-translate/useTranslation' import React from 'react' -import get from 'lodash/get' -import { gql, useSubscription } from '@apollo/client' +import invoke from 'lodash/invoke' import CloseIcon from '../../assets/images/icons/icon_cross.svg' -import BackIcon from '../../assets/images/icons/icon_back.svg' -import VoteIcon from '../../assets/images/icons/icon_vote.svg' import useStyles from './styles' import useIconProps from '../../misc/useIconProps' import SelectAnswer from './SelectAnswer' -import useStateHistory from '../../misc/useStateHistory' -import Success from '../Success' -import SecurityPassword from '../SecurityPasswordDialogContent' import { useWalletsContext } from '../../contexts/WalletsContext' -import cryptocurrencies from '../../misc/cryptocurrencies' -import { getTokenAmountFromDenoms } from '../../misc/utils' -import ConfirmAnswer from './ConfirmAnswer' -import { getLatestAccountBalance } from '../../graphql/queries/accountBalances' - -enum VotingStage { - SecurityPasswordStage = 'security password', - SelectAnswerStage = 'select answer', - ConfirmAnswerStage = 'confirm withdraw', - SuccessStage = 'success', -} interface VoteDialogProps { - network: { id: number; crypto: string; name: string; img: string } + network: Chain open: boolean onClose(): void proposal: Proposal } -interface Content { - title: string | React.ReactNode - content: React.ReactNode - dialogWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' -} - const VoteDialog: React.FC = ({ network, open, onClose, proposal }) => { const { t } = useTranslation('common') const classes = useStyles() const iconProps = useIconProps() - const voteIconProps = useIconProps(8) - const [answer, setAnswer] = React.useState<{ name: string; id: string }>({ - name: undefined, - id: undefined, - }) - const { password, accounts: allAccounts } = useWalletsContext() - const accounts = allAccounts.filter((a) => a.crypto === network?.crypto) - const [voteAccount, setVoteAccount] = React.useState(accounts[0]) - const [memo, setMemo] = React.useState('') - const [loading, setLoading] = React.useState(false) + const { password } = useWalletsContext() - const [stage, setStage, toPrevStage, isPrevStageAvailable] = useStateHistory( - VotingStage.SelectAnswerStage - ) - - const crypto = voteAccount - ? cryptocurrencies[voteAccount.crypto] - : Object.values(cryptocurrencies)[0] - - const { data: balanceData } = useSubscription( - gql` - ${getLatestAccountBalance(crypto.name)} - `, - { variables: { address: voteAccount ? voteAccount.address : '' } } - ) + const [loading, setLoading] = React.useState(false) React.useEffect(() => { if (open) { - setAnswer({ - name: undefined, - id: undefined, - }) - setMemo('') - setMemo('') setLoading(false) - setStage(VotingStage.SelectAnswerStage, true) } }, [open]) - const confirmWithPassword = React.useCallback(async (securityPassword: string) => { - try { - setLoading(true) - // handle transaction part later - setLoading(false) - setStage(VotingStage.SuccessStage, true) - } catch (err) { - setLoading(false) - console.log(err) - } - }, []) - - const confirmFinal = React.useCallback(() => { - setStage(VotingStage.SecurityPasswordStage) - }, [setStage]) - const chooseAnswer = React.useCallback( - (v: Account, a: { name: string; id: string }, m?: string) => { - setVoteAccount(v) - setAnswer(a) - setMemo(m) - setStage(VotingStage.ConfirmAnswerStage) + async (account: Account, vote: { name: string; id: '1' | '2' | '3' | '4' }, memo: string) => { + try { + setLoading(true) + const msg = { + typeUrl: '/cosmos.gov.v1beta1.MsgVote', + value: { + option: Number(vote.id), + proposalId: proposal.id, + voter: account.address, + }, + } + await invoke(window, 'forboleX.sendTransaction', password, account.address, { + msgs: [msg], + memo, + }) + setLoading(false) + } catch (err) { + setLoading(false) + console.log(err) + } }, - [setStage] + [proposal, password] ) - const content: Content = React.useMemo(() => { - switch (stage) { - case VotingStage.SuccessStage: - return { - title: '', - dialogWidth: 'xs', - content: , - } - case VotingStage.SecurityPasswordStage: - return { - title: '', - dialogWidth: 'sm', - content: ( - - ), - } - case VotingStage.SelectAnswerStage: - default: - return { - title: t('vote'), - dialogWidth: 'sm', - content: , - } - } - }, [stage, t]) - return ( - - {isPrevStageAvailable ? ( - - - - ) : null} + - {content.title ? {content.title} : null} - {content.content} + {t('vote')} + + , ) } diff --git a/components/WithdrawRewardsDialog/index.tsx b/components/WithdrawRewardsDialog/index.tsx index ae9ccab4..36bc5b18 100644 --- a/components/WithdrawRewardsDialog/index.tsx +++ b/components/WithdrawRewardsDialog/index.tsx @@ -49,10 +49,10 @@ const WithdrawRewardsDialog: React.FC = ({ setLoading(true) const msgs = delegations .map((r) => ({ - type: 'cosmos-sdk/MsgWithdrawDelegationReward', + typeUrl: '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward', value: { - delegator_address: account.address, - validator_address: r.address, + delegatorAddress: account.address, + validatorAddress: r.address, }, })) .filter((a) => a) diff --git a/custom.d.ts b/custom.d.ts index fa7a9259..f16ea3d4 100644 --- a/custom.d.ts +++ b/custom.d.ts @@ -144,17 +144,6 @@ interface Activity { amount?: TokenAmount } -interface VoteDetail { - voter: { - name: string - image: string - } - votingPower: number - votingPowerPercentage: number - votingPowerOverride: number - answer: string -} - interface DepositDetail { depositor: { name: string @@ -223,62 +212,135 @@ interface UpdateWalletParams { } interface TransactionMsgDelegate { - type: 'cosmos-sdk/MsgDelegate' + typeUrl: '/cosmos.staking.v1beta1.MsgDelegate' value: { - delegator_address: string - validator_address: string + delegatorAddress: string + validatorAddress: string amount: { amount: string; denom: string } } } interface TransactionMsgUndelegate { - type: 'cosmos-sdk/MsgUndelegate' + typeUrl: '/cosmos.staking.v1beta1.MsgUndelegate' value: { - delegator_address: string - validator_address: string + delegatorAddress: string + validatorAddress: string amount: { amount: string; denom: string } } } interface TransactionMsgRedelegate { - type: 'cosmos-sdk/MsgBeginRedelegate' + typeUrl: '/cosmos.staking.v1beta1.MsgBeginRedelegate' value: { - delegator_address: string - validator_src_address: string - validator_dst_address: string + delegatorAddress: string + validatorSrcAddress: string + validatorDstAddress: string amount: { amount: string; denom: string } } } interface TransactionMsgWithdrawReward { - type: 'cosmos-sdk/MsgWithdrawDelegationReward' + typeUrl: '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward' value: { - delegator_address: string - validator_address: string + delegatorAddress: string + validatorAddress: string } } interface TransactionMsgSend { - type: 'cosmos-sdk/MsgSend' + typeUrl: '/cosmos.bank.v1beta1.MsgSend' value: { - from_address: string - to_address: string + fromAddress: string + toAddress: string amount: Array<{ amount: string; denom: string }> } } interface TransactionMsgIBCTransfer { - type: 'cosmos-sdk/MsgTransfer' + typeUrl: '/ibc.applications.transfer.v1.MsgTransfer' value: { - source_port: string - source_channel: string + sourcePort: string + sourceChannel: string token: { denom: string amount: string } sender: string receiver: string - timeout_timestamp?: number + timeoutTimestamp?: number + } +} + +interface TransactionMsgSubmitProposal { + typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal' + value: { + content: + | { + typeUrl: '/cosmos.gov.v1beta1.TextProposal' + value: { + description: string + title: string + } + } + | { + typeUrl: '/cosmos.params.v1beta1.ParameterChangeProposal' + value: { + description: string + title: string + changes: { supspace: string; key: string; value: string }[] + } + } + | { + typeUrl: '/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal' + value: { + title: string + description: string + plan: { + name: string + time?: number + height?: number + info: string + upgradedClientState?: any + } + } + } + | { + typeUrl: '/cosmos.distribution.v1beta1.CommunityPoolSpendProposal' + value: { + title: string + description: string + recipient: string + amount: Array<{ amount: string; denom: string }> + } + } + initialDeposit: [ + { + amount: string + denom: string + } + ] + proposer: string + } +} + +interface TransactionMsgVote { + typeUrl: '/cosmos.gov.v1beta1.MsgVote' + value: { + option: 1 | 2 | 3 | 4 // Yes, Abstain, No, No with Veto + proposalId: number + voter: string + } +} + +interface TransactionMsgDeposit { + typeUrl: '/cosmos.gov.v1beta1.MsgDeposit' + value: { + amount: { + amount: string + denom: string + }[] + depositor: string + proposalId: number } } @@ -289,11 +351,11 @@ type TransactionMsg = | TransactionMsgWithdrawReward | TransactionMsgSend | TransactionMsgIBCTransfer + | TransactionMsgSubmitProposal + | TransactionMsgVote + | TransactionMsgDeposit interface Transaction { - account_number?: string - chain_id?: string - sequence?: string msgs: TransactionMsg[] memo: string fee?: { @@ -302,6 +364,15 @@ interface Transaction { } } +interface Chain { + name: string + image: string + channel: string + chainId: string + addressPrefix: string + crypto: string +} + type ChromeMessage = | { event: 'ping' diff --git a/graphql/hooks/useAccountsBalancesWithinPeriod.ts b/graphql/hooks/useAccountsBalancesWithinPeriod.ts index ceeb44ee..99d3709c 100644 --- a/graphql/hooks/useAccountsBalancesWithinPeriod.ts +++ b/graphql/hooks/useAccountsBalancesWithinPeriod.ts @@ -45,7 +45,10 @@ const fetchBalance = async (address: string, crypto: string, timestamp: Date) => rewards, }, timestamp: timestamp.getTime(), - availableTokens: { coins: get(balance, 'data.balance', []), tokens_prices: denoms }, + availableTokens: { + coins: get(balance, 'data.account_balance_history[0].balance', []), + tokens_prices: denoms, + }, } } diff --git a/locales/en/common.json b/locales/en/common.json index e36f7b08..814e1e3a 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -280,20 +280,20 @@ "vote proposal": "Vote Proposal", "You’re going to vote": "You’re going to vote", "select answer": "Select answer", - "voting time": "Voting Time", + "voting time": "Voting Time: {{from}} to {{to}}", "voting start time": "Voting Start Time", "voting end time": "Voting End Time", "submitted time": "Submitted Time", "deposited end time": "Deposited End Time", "in": "In", "days": "Days", - "was successfully deposited": "was successfully deposited", - "Text": "Text Proposal", - "ParameterChange": "Parameter Change Proposal", - "SoftwareUpgrade": "Software Upgrade Proposal", - "CommunityPoolSpend": "Community Pool Spend Proposal", + "successfully deposited": "{{title}} was successfully deposited", + "TextProposal": "Text Proposal", + "ParameterChangeProposal": "Parameter Change Proposal", + "SoftwareUpgradeProposal": "Software Upgrade Proposal", + "CommunityPoolSpendProposal": "Community Pool Spend Proposal", "remaining deposit amount": "Remaining deposit amount", - "deposit end in": "Deposit end in", + "deposit end in": "Deposit end in {{days}} days {{hours}}:{{minutes}}:{{seconds}}", "import account title": "Import Account", "security password": "Security Password", "select account(s) you want to add": "select account(s) you want to add", @@ -301,8 +301,8 @@ "no rewards yet": "No Rewards Yet", "delegate your token and get some rewards": "Delegate your token and get some rewards", "total": "Total Amount", - "withdraw warning ledger": "Due to the limitation of Ledgr device, using Ledger Nano S, Forbole X can olny withdraw rewards from 5 validators at a time. Using Ledger Nano X, Forbole X can olny withdraw rewards from 20 validators at a time.", - "withdraw warning secret recovery phrase": "Forbole X can olny withdraw rewards from 20 validators at a time.", + "withdraw warning ledger": "Due to the limitation of Ledgr device, using Ledger Nano S, Forbole X can olny withdraw rewards from up to 5 validators at a time. Using Ledger Nano X, Forbole X can olny withdraw rewards from 20 validators at a time.", + "withdraw warning secret recovery phrase": "Forbole X can olny withdraw rewards from up to 20 validators at a time.", "you have not added any address": "You have not added any address", "edit address": "Edit Address", "delete address": "Delete Address", @@ -372,5 +372,11 @@ "github": "Github", "chrome extention": "Chrome Extention", "please install chrome extension": "Please install chrome extension", - "install": "Install" + "install": "Install", + "starredAccounts": "Starred Accounts", + "manageAccounts": "Go to wallet manage to star your account", + "star now": "Star Now", + "proposal details": "Proposal Details", + "deposit to": "Deposit to", + "deposit time": "Deposit Time: {{from}} to {{to}}" } diff --git a/locales/zh/common.json b/locales/zh/common.json index d1becbc5..fc033a60 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -330,5 +330,8 @@ "thank you content": "[zh] We successfully received your feedback and will get back to you as soon as possible.", "forbole blog": "[zh] Forbole Blog", "big dipper blog explorer": "[zh] Big Dipper Blog Explorer", - "github": "[zh] Github" + "github": "[zh] Github", + "starredAccounts": "[zh] Starred Accounts", + "manageAccounts": "[zh] Go to wallet manage to star your account", + "star now": "[zh] Star Now" } diff --git a/misc/chains.ts b/misc/chains.ts index b394cbde..9f90adbc 100644 --- a/misc/chains.ts +++ b/misc/chains.ts @@ -1,25 +1,26 @@ -const chains = { +const chains: { [key: string]: Chain } = { 'morpheus-apollo-2': { name: 'Desmos', image: '/static/images/cryptocurrencies/dsm.png', channel: 'channel-1', chainId: 'morpheus-apollo-2', addressPrefix: 'desmos', + crypto: 'DARIC', }, - 'akashnet-2': { - name: 'Akash', - image: '/static/images/cryptocurrencies/akt.png', - channel: 'channel-116', - chainId: 'akashnet-2', - addressPrefix: 'akash', - }, - 'cosmoshub-4': { - name: 'Cosmos Hub', - image: '/static/images/cryptocurrencies/cosmos-hub.png', - channel: 'channel-0', - chainId: 'cosmoshub-4', - addressPrefix: 'cosmos', - }, + // 'akashnet-2': { + // name: 'Akash', + // image: '/static/images/cryptocurrencies/akt.png', + // channel: 'channel-116', + // chainId: 'akashnet-2', + // addressPrefix: 'akash', + // }, + // 'cosmoshub-4': { + // name: 'Cosmos Hub', + // image: '/static/images/cryptocurrencies/cosmos-hub.png', + // channel: 'channel-0', + // chainId: 'cosmoshub-4', + // addressPrefix: 'cosmos', + // }, } export default chains diff --git a/misc/cryptocurrencies.ts b/misc/cryptocurrencies.ts index 6300ea98..3d6a2109 100644 --- a/misc/cryptocurrencies.ts +++ b/misc/cryptocurrencies.ts @@ -10,18 +10,22 @@ const cryptocurrencies = { graphqlWsUrl: 'wss://gql.morpheus.desmos.network/v1/graphql', blockExplorerBaseUrl: 'https://morpheus.desmos.network', rpcEndpoint: 'https://rpc.morpheus.desmos.network', + lcdEndpoint: 'https://lcd.morpheus.desmos.network', defaultGasFee: { amount: { amount: 0.01, denom: 'udaric', }, gas: { - 'cosmos-sdk/MsgSend': '200000', - 'cosmos-sdk/MsgDelegate': '400000', - 'cosmos-sdk/MsgBeginRedelegate': '400000', - 'cosmos-sdk/MsgWithdrawDelegationReward': '200000', - 'cosmos-sdk/MsgUndelegate': '400000', - 'cosmos-sdk/MsgTransfer': '400000', + '/cosmos.bank.v1beta1.MsgSend': '200000', + '/cosmos.staking.v1beta1.MsgDelegate': '400000', + '/cosmos.staking.v1beta1.MsgBeginRedelegate': '400000', + '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward': '200000', + '/cosmos.staking.v1beta1.MsgUndelegate': '400000', + '/ibc.applications.transfer.v1.MsgTransfer': '400000', + '/cosmos.gov.v1beta1.MsgSubmitProposal': '400000', + '/cosmos.gov.v1beta1.MsgDeposit': '400000', + '/cosmos.gov.v1beta1.MsgVote': '400000', }, }, }, diff --git a/misc/getWalletAddress.ts b/misc/getWalletAddress.ts index 6a017f57..5db13d30 100644 --- a/misc/getWalletAddress.ts +++ b/misc/getWalletAddress.ts @@ -1,6 +1,6 @@ import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' import { stringToPath } from '@cosmjs/crypto' -import { LedgerSigner } from '../@cosmjs/ledger-amino' +import { LedgerSigner } from '@cosmjs/ledger-amino' import cryptocurrencies from './cryptocurrencies' const getWalletAddress = async ( diff --git a/misc/globalCss.ts b/misc/globalCss.ts index 43bc64a9..3fdb7329 100644 --- a/misc/globalCss.ts +++ b/misc/globalCss.ts @@ -65,10 +65,11 @@ const GlobalCss = withStyles( color: 'white', }, }, - 'a:-webkit-any-link': { - '&:hover': { - color: 'initial', - }, + '.MuiAvatar-colorDefault': { + backgroundColor: theme.palette.grey[200], + }, + '.MuiAvatar-fallback': { + fill: theme.palette.grey[300], }, }, }), diff --git a/misc/signAndBroadcastTransaction.ts b/misc/signAndBroadcastTransaction.ts index fa74a037..371ca925 100644 --- a/misc/signAndBroadcastTransaction.ts +++ b/misc/signAndBroadcastTransaction.ts @@ -1,35 +1,81 @@ +/* eslint-disable import/no-extraneous-dependencies */ import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' import { stringToPath } from '@cosmjs/crypto' import { SigningStargateClient } from '@cosmjs/stargate' -import camelCase from 'lodash/camelCase' import set from 'lodash/set' import get from 'lodash/get' -// eslint-disable-next-line import/no-extraneous-dependencies +import cloneDeep from 'lodash/cloneDeep' +import { TextProposal } from 'cosmjs-types/cosmos/gov/v1beta1/gov' +import { ParameterChangeProposal } from 'cosmjs-types/cosmos/params/v1beta1/params' +import { SoftwareUpgradeProposal } from 'cosmjs-types/cosmos/upgrade/v1beta1/upgrade' +import { CommunityPoolSpendProposal } from 'cosmjs-types/cosmos/distribution/v1beta1/distribution' import Long from 'long' -import { LedgerSigner } from '../@cosmjs/ledger-amino' +import { LedgerSigner } from '@cosmjs/ledger-amino' import cryptocurrencies from './cryptocurrencies' import sendMsgToChromeExt from './sendMsgToChromeExt' -const typeUrlMap: any = { - 'cosmos-sdk/MsgDelegate': '/cosmos.staking.v1beta1.MsgDelegate', - 'cosmos-sdk/MsgUndelegate': '/cosmos.staking.v1beta1.MsgUndelegate', - 'cosmos-sdk/MsgBeginRedelegate': '/cosmos.staking.v1beta1.MsgBeginRedelegate', - 'cosmos-sdk/MsgWithdrawDelegationReward': - '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward', - 'cosmos-sdk/MsgSend': '/cosmos.bank.v1beta1.MsgSend', - 'cosmos-sdk/MsgTransfer': '/ibc.applications.transfer.v1.MsgTransfer', -} +const formatTransactionMsg = (msg: TransactionMsg) => { + const transformedMsg = cloneDeep(msg) + if (transformedMsg.typeUrl === '/ibc.applications.transfer.v1.MsgTransfer') { + set( + transformedMsg, + 'value.timeoutTimestamp', + new Long(get(transformedMsg, 'value.timeoutTimestamp', 0)) + ) + } + if ( + transformedMsg.typeUrl === '/cosmos.gov.v1beta1.MsgDeposit' || + transformedMsg.typeUrl === '/cosmos.gov.v1beta1.MsgVote' + ) { + set(transformedMsg, 'value.proposalId', new Long(get(transformedMsg, 'value.proposalId', 0))) + } -const formatTransactionMsg = (msg: any) => { - const transformedMsg: any = {} - if (msg.type === 'cosmos-sdk/MsgTransfer') { - set(msg, 'value.timeout_timestamp', new Long(get(msg, 'value.timeout_timestamp', 0))) + if (get(msg, 'value.content.typeUrl') === '/cosmos.gov.v1beta1.TextProposal') { + set( + transformedMsg, + 'value.content.value', + Uint8Array.from( + TextProposal.encode(TextProposal.fromPartial(get(msg, 'value.content.value'))).finish() + ) + ) + } else if ( + get(msg, 'value.content.typeUrl') === '/cosmos.params.v1beta1.ParameterChangeProposal' + ) { + set( + transformedMsg, + 'value.content.value', + Uint8Array.from( + ParameterChangeProposal.encode( + ParameterChangeProposal.fromPartial(get(msg, 'value.content.value')) + ).finish() + ) + ) + } else if ( + get(msg, 'value.content.typeUrl') === '/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal' + ) { + set( + transformedMsg, + 'value.content.value', + Uint8Array.from( + SoftwareUpgradeProposal.encode( + SoftwareUpgradeProposal.fromPartial(get(msg, 'value.content.value')) + ).finish() + ) + ) + } else if ( + get(msg, 'value.content.typeUrl') === '/cosmos.distribution.v1beta1.CommunityPoolSpendProposal' + ) { + set( + transformedMsg, + 'value.content.value', + Uint8Array.from( + CommunityPoolSpendProposal.encode( + CommunityPoolSpendProposal.fromPartial(get(msg, 'value.content.value')) + ).finish() + ) + ) } - transformedMsg.typeUrl = typeUrlMap[msg.type] - transformedMsg.value = {} - Object.keys(msg.value).forEach((k) => { - transformedMsg.value[camelCase(k)] = msg.value[k] - }) + return transformedMsg } @@ -40,11 +86,11 @@ const signAndBroadcastCosmosTransaction = async ( transactionData: any, ledgerTransport?: any ): Promise => { - let signer const signerOptions = { hdPaths: [stringToPath(`m/44'/${cryptocurrencies[crypto].coinType}'/0'/0/${index}`)], prefix: cryptocurrencies[crypto].prefix, } + let signer if (!ledgerTransport) { signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions) } else { diff --git a/misc/utils.ts b/misc/utils.ts index 4bb990ad..36faa0ce 100644 --- a/misc/utils.ts +++ b/misc/utils.ts @@ -373,7 +373,7 @@ export const transformTransactions = ( if (t.type.includes('MsgWithdrawDelegatorReward')) { return { ref: `#${get(t, 'transaction_hash', '')}`, - tab: 'staking', + tab: 'distribution', tag: 'withdrawReward', date: `${format( new Date(get(t, 'transaction.block.timestamp')), @@ -453,7 +453,7 @@ const getTag = (status: string) => { return 'rejected' } if (status === 'PROPOSAL_STATUS_INVALID') { - return 'removed' + return 'invalid' } if (status === 'PROPOSAL_STATUS_PASSED') { return 'passed' @@ -471,25 +471,33 @@ const getTag = (status: string) => { } export const transformProposals = (proposalData: any): Proposal[] => { - return get(proposalData, 'proposal', []).map((p) => ({ - id: get(p, 'id'), - proposer: { - name: get(p, 'proposer.validator_infos[0].validator.validator_descriptions[0].moniker'), - image: get(p, 'proposer.validator_infos[0].validator.validator_descriptions[0].avatar_url'), - address: get(p, 'proposer.address'), - }, - title: get(p, 'title'), - description: get(p, 'description'), - type: get(p, 'proposal_type'), - votingStartTime: `${format(new Date(get(p, 'voting_start_time')), 'dd MMM yyyy HH:mm')} UTC`, - votingEndTime: `${format(new Date(get(p, 'voting_end_time')), 'dd MMM yyyy HH:mm')} UTC`, - isActive: !!( - get(p, 'status') === 'PROPOSAL_STATUS_VOTING' || - get(p, 'status') === 'PROPOSAL_STATUS_DEPOSIT' - ), - tag: getTag(get(p, 'status')), - duration: differenceInDays(new Date(get(p, 'voting_end_time')), Date.now()), - })) + return get(proposalData, 'proposal', []) + .map((p) => ({ + id: get(p, 'id', ''), + proposer: { + name: get(p, 'proposer.validator_infos[0].validator.validator_descriptions[0].moniker', ''), + image: get( + p, + 'proposer.validator_infos[0].validator.validator_descriptions[0].avatar_url', + '' + ), + address: get(p, 'proposer.address', ''), + }, + title: get(p, 'title', ''), + description: get(p, 'description', ''), + type: get(p, 'proposal_type', ''), + votingStartTime: `${format(new Date(get(p, 'voting_start_time')), 'dd MMM yyyy HH:mm')} UTC`, + votingEndTime: `${format(new Date(get(p, 'voting_end_time')), 'dd MMM yyyy HH:mm')} UTC`, + depositEndTime: `${format(new Date(get(p, 'deposit_end_time')), 'dd MMM yyyy HH:mm')} UTC`, + depositEndTimeRaw: get(p, 'deposit_end_time'), + isActive: !!( + get(p, 'status', '') === 'PROPOSAL_STATUS_VOTING' || + get(p, 'status', '') === 'PROPOSAL_STATUS_DEPOSIT' + ), + tag: getTag(get(p, 'status', '')), + duration: differenceInDays(new Date(get(p, 'voting_end_time')), Date.now()), + })) + .sort((a, b) => (a.id < b.id ? 1 : -1)) } export const transformProposal = ( @@ -563,10 +571,10 @@ export const transformVoteSummary = (proposalResult: any): any => { let yes = 0 get(proposalResult, 'proposal_tally_result', []).forEach((p) => { - abstain += get(p, 'abstain') - no += get(p, 'no') - veto += get(p, 'no_with_veto') - yes += get(p, 'yes') + abstain += get(p, 'abstain', 0) / 10 ** 6 + no += get(p, 'no', 0) / 10 ** 6 + veto += get(p, 'no_with_veto', 0) / 10 ** 6 + yes += get(p, 'yes', 0) / 10 ** 6 }) const totalAmount = abstain + no + veto + yes @@ -622,9 +630,9 @@ export const transformVoteDetail = (voteDetail: any): any => { image: get(d, 'account.validator_infos[0].validator.validator_descriptions[0].avatar_url'), address: get(d, 'voter_address'), }, - votingPower: 0, - votingPowerPercentage: 0.1, - votingPowerOverride: 0.1, + // votingPower: 0, + // votingPowerPercentage: 0.1, + // votingPowerOverride: 0.1, answer: getVoteAnswer(get(d, 'option')), })) } diff --git a/next-env.d.ts b/next-env.d.ts index 7b7aa2c7..9bc3dd46 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,2 +1,6 @@ /// /// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js index 7bfb9336..68e823a8 100644 --- a/next.config.js +++ b/next.config.js @@ -8,10 +8,6 @@ module.exports = { test: /\.svg$/, use: ['@svgr/webpack'], }) - config.module.rules.push({ - test: /\.(eot|gif|md)$/, - loaders: ['style-loader', 'css-loader', 'less-loader'], - }) return config }, diff --git a/package.json b/package.json index 64400dd6..3d4ec74d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "forbole-x", - "version": "0.3.1", + "version": "0.4.0", "private": true, "scripts": { "dev": "next dev", @@ -9,14 +9,15 @@ "prettier": "prettier --config .prettierrc --write '**/*.{ts,tsx,json,yaml}'", "lint": "eslint '**/*.{ts,tsx}'", "test": "jest", - "e2e": "cypress run" + "e2e": "cypress run", + "postinstall": "patch-package" }, "dependencies": { "@apollo/client": "^3.3.12", - "@cosmjs/crypto": "^0.25.4", - "@cosmjs/ledger-amino": "^0.25.5", - "@cosmjs/proto-signing": "^0.25.4", - "@cosmjs/stargate": "^0.25.4", + "@cosmjs/crypto": "^0.25.6", + "@cosmjs/ledger-amino": "^0.25.6", + "@cosmjs/proto-signing": "^0.25.6", + "@cosmjs/stargate": "^0.26.0-alpha2", "@ledgerhq/hw-transport-webhid": "^6.1.0", "@material-ui/core": "^4.11.2", "@material-ui/icons": "^4.11.2", @@ -26,9 +27,10 @@ "graphql": "^15.5.0", "isomorphic-ws": "^4.0.1", "lodash": "^4.17.21", - "next": "^10.0.4", + "next": "^11.1.0", "next-translate": "^1.0.1", "nodemailer": "^6.6.3", + "patch-package": "^6.4.7", "postcss": "^8.2.15", "qrcode.react": "^1.0.1", "query-string": "^7.0.0", diff --git a/pages/_app.tsx b/pages/_app.tsx index cb46c2ec..f739eff7 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -2,7 +2,7 @@ import React from 'react' import Head from 'next/head' import { AppProps } from 'next/app' import CssBaseline from '@material-ui/core/CssBaseline' -import { ThemeProvider, createMuiTheme } from '@material-ui/core/styles' +import { ThemeProvider, createTheme } from '@material-ui/core/styles' import useTranslation from 'next-translate/useTranslation' import { ApolloProvider } from '@apollo/client' import { useRouter } from 'next/router' @@ -19,9 +19,9 @@ function InnerApp({ Component, pageProps }: AppProps) { const muiTheme = React.useMemo(() => { if (theme === 'dark') { - return createMuiTheme(darkTheme) + return createTheme(darkTheme) } - return createMuiTheme(lightTheme) + return createTheme(lightTheme) }, [theme]) React.useEffect(() => { diff --git a/pages/proposals/proposals/[id].tsx b/pages/proposals/[chainId]/[id].tsx similarity index 79% rename from pages/proposals/proposals/[id].tsx rename to pages/proposals/[chainId]/[id].tsx index 32782167..93768763 100644 --- a/pages/proposals/proposals/[id].tsx +++ b/pages/proposals/[chainId]/[id].tsx @@ -1,11 +1,10 @@ -import { Breadcrumbs, Link as MLink } from '@material-ui/core' +import { Breadcrumbs, Link as MLink, Typography } from '@material-ui/core' import useTranslation from 'next-translate/useTranslation' import Link from 'next/link' import { useRouter } from 'next/router' import { gql, useSubscription } from '@apollo/client' import React from 'react' import get from 'lodash/get' -import AccountAvatar from '../../../components/AccountAvatar' import Layout from '../../../components/Layout' import { useWalletsContext } from '../../../contexts/WalletsContext' import cryptocurrencies from '../../../misc/cryptocurrencies' @@ -18,17 +17,14 @@ import { } from '../../../graphql/queries/proposals' import { transformProposal, transformVoteSummary, transformVoteDetail } from '../../../misc/utils' import { getLatestAccountBalance } from '../../../graphql/queries/accountBalances' +import chains from '../../../misc/chains' -interface ProposalProps { - network: { id: number; crypto: string; name: string; img: string } -} - -const Proposal: React.FC = ({ network }) => { +const Proposal: React.FC = () => { const router = useRouter() const { t } = useTranslation('common') const { accounts } = useWalletsContext() const account = accounts.find((a) => a.address === router.query.address) - const { id } = router.query + const { id, chainId } = router.query const crypto = account ? cryptocurrencies[account.crypto] : Object.values(cryptocurrencies)[0] const { data: proposalData } = useSubscription( gql` @@ -70,20 +66,19 @@ const Proposal: React.FC = ({ network }) => { const proposal = transformProposal(proposalData, balanceData, depositParamsData) const voteSummary = transformVoteSummary(proporslReaultData) const voteDetail = transformVoteDetail(voteDetailData) + const network = chains[String(chainId)] return ( - - {t('wallet manage')} - - - - ) : null + + + {t('proposals')} + + {t('proposal details')} + } > = ({ network }) => { - const router = useRouter() - const { t } = useTranslation('common') - const { accounts } = useWalletsContext() - const account = accounts.find((a) => a.address === router.query.address) - const { id } = router.query - const crypto = account ? cryptocurrencies[account.crypto] : Object.values(cryptocurrencies)[0] - const { data: proposalData } = useSubscription( - gql` - ${getProposal(crypto.name)} - `, - { - variables: { - id, - }, - } - ) - const { data: depositParamsData } = useSubscription( - gql` - ${getDepositParams(crypto.name)} - ` - ) - - const { data: balanceData } = useSubscription( - gql` - ${getLatestAccountBalance(crypto.name)} - `, - { variables: { address: get(proposalData, 'proposal[0].proposer_address') } } - ) - - const { data: proporslReaultData } = useSubscription( - gql` - ${getProposalResult(crypto.name)} - `, - { variables: { id } } - ) - - const { data: voteDetailData } = useSubscription( - gql` - ${getVoteDetail(crypto.name)} - `, - { variables: { id } } - ) - - const proposal = transformProposal(proposalData, balanceData, depositParamsData) - const voteSummary = transformVoteSummary(proporslReaultData) - const voteDetail = transformVoteDetail(voteDetailData) - - return ( - - - {t('wallet manage')} - - - - ) : null - } - > - - - ) -} - -export default Proposal diff --git a/pages/proposals/create.tsx b/pages/proposals/create.tsx index f85fd253..21e8dc7f 100644 --- a/pages/proposals/create.tsx +++ b/pages/proposals/create.tsx @@ -5,24 +5,11 @@ import Layout from '../../components/Layout' const CreateProposal: React.FC = () => { const { accounts } = useWalletsContext() - // TODO const account = accounts[0] - // how to query networks? - const networks = [ - { - name: 'Cosmoshub - ATOM', - id: '01', - }, - { - name: 'Desmoshub - DARIC', - id: '02', - }, - ] - return ( - {account ? : null} + {account ? : null} ) } diff --git a/pages/proposals/index.tsx b/pages/proposals/index.tsx index 6fad2bd1..9a7272ae 100644 --- a/pages/proposals/index.tsx +++ b/pages/proposals/index.tsx @@ -1,16 +1,15 @@ import React from 'react' import { Box, Button, Menu, MenuItem, Typography, Avatar, useTheme } from '@material-ui/core' import useTranslation from 'next-translate/useTranslation' -import groupBy from 'lodash/groupBy' -import { useRouter } from 'next/router' import { gql, useSubscription } from '@apollo/client' +import Link from 'next/link' import Layout from '../../components/Layout' import ProposalTable from '../../components/ProposalTable' -import cryptocurrencies from '../../misc/cryptocurrencies' import DropDownIcon from '../../assets/images/icons/icon_arrow_down_input_box.svg' import useIconProps from '../../misc/useIconProps' import { getProposals } from '../../graphql/queries/proposals' import { transformProposals } from '../../misc/utils' +import chains from '../../misc/chains' const Proposals: React.FC = () => { const { t } = useTranslation('common') @@ -18,67 +17,13 @@ const Proposals: React.FC = () => { const theme = useTheme() const [accountMenuAnchor, setAccountMenuAnchor] = React.useState() - const router = useRouter() - const networkList = [ - { - id: 3, - crypto: 'DARIC', - name: 'Desmos', - img: 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/logos/desmos.svg?sanitize=true', - }, - // { - // id: 0, - // crypto: 'AKT', - // name: 'Akash', - // img: 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/logos/akash.svg?sanitize=true', - // }, - // { - // id: 1, - // crypto: 'BAND', - // name: 'Band Protocol', - // img: 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/logos/band.svg?sanitize=true', - // }, - // { - // id: 2, - // crypto: 'ATOM', - // name: 'Cosmos', - // img: 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/logos/cosmoshub.svg?sanitize=true', - // }, - - // { - // id: 4, - // crypto: 'NGM', - // name: 'e-Money', - // img: 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/logos/e-money.svg?sanitize=true', - // }, - // { - // id: 5, - // crypto: 'FLOW', - // name: 'Flow', - // img: 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/logos/flow.svg?sanitize=true', - // }, - ] - - const networksMap = React.useMemo( - () => - groupBy( - networkList.map((a, index) => ({ ...a, index })), - 'id' - ), - [networkList] - ) - - const [activeNetworkIndex, setActiveNetworkIndex] = React.useState(0) - const activeNetwork = networkList[activeNetworkIndex] - - const crypto = activeNetwork - ? cryptocurrencies[activeNetwork.crypto] - : Object.values(cryptocurrencies)[0] + const [activeNetworkKey, setActiveNetworkKey] = React.useState(Object.keys(chains)[0]) + const activeNetwork = chains[activeNetworkKey] const { data: proposalData } = useSubscription( gql` - ${getProposals(crypto.name)} + ${getProposals(activeNetwork.crypto)} ` ) @@ -88,94 +33,78 @@ const Proposals: React.FC = () => { {t('proposal')} - {networkList ? ( - - - setAccountMenuAnchor(undefined)} - > - {networkList.map((w) => ( - - {networksMap[w.id].map((a) => ( - { - setActiveNetworkIndex(a.index) - setAccountMenuAnchor(undefined) - }} - > - - - - {w.name} - - - - ))} - - ))} - - - ) : null} - + + setAccountMenuAnchor(undefined)} + > + {Object.values(chains).map((a) => ( + { + setActiveNetworkKey(a.chainId) + setAccountMenuAnchor(undefined) + }} + > + + + {a.name} + + + ))} + + + + + + diff --git a/pages/wallets.tsx b/pages/wallets.tsx index 68d8df8f..d2d8540d 100644 --- a/pages/wallets.tsx +++ b/pages/wallets.tsx @@ -9,13 +9,14 @@ import AddAccountButton from '../components/AddAccountButton/index' import EditWalletButton from '../components/EditWalletButton/index' import CreateWalletDialog, { CommonStage } from '../components/CreateWalletDialog' import { CustomTheme } from '../misc/theme' +import LedgerIcon from '../assets/images/icons/usb_device.svg' const Wallets: React.FC = () => { const { t } = useTranslation('common') const { wallets, accounts } = useWalletsContext() const accountsMap = React.useMemo(() => groupBy(accounts, 'walletId'), [accounts]) const [isCreateWalletDialogOpen, setIsCreateWalletDialogOpen] = React.useState(false) - const theme: CustomTheme = useTheme() + const themeStyle: CustomTheme = useTheme() return ( @@ -23,7 +24,7 @@ const Wallets: React.FC = () => { {t('wallet')}