From e485ef0d462fb8d43a723b42eebf425c5785c8fe Mon Sep 17 00:00:00 2001 From: Vladan Date: Tue, 22 Aug 2023 16:35:06 +0200 Subject: [PATCH 1/6] feat: wallet improvements --- assets/locales/en-US.json | 14 +- src/constants/networks.ts | 2 + .../message-listeners/account.listener.ts | 13 +- src/messaging/content-api.messaging.ts | 18 ++- src/model/storage/network.model.ts | 1 + src/model/storage/wallet.model.ts | 3 +- .../clipboard-button.component.tsx | 5 +- .../error-message/error-message.component.tsx | 5 +- .../error-modal/error-modal.component.tsx | 47 +++++++ .../send/address-select.component.tsx | 6 +- .../components/send/transaction-completed.tsx | 33 ++++- .../send/transaction-confirmation.tsx | 4 +- .../components/send/wallet-send.component.tsx | 41 ++++-- .../tokens/token-import.component.tsx | 19 ++- .../tokens/token-info.component.tsx | 22 ++- .../tokens/tokens.component.tsx | 2 +- .../transaction-details.component.tsx | 125 ++++++++++++++++++ .../transaction-list.component.tsx | 60 +++++---- .../components/wallet-overview.component.tsx | 18 ++- src/ui/common/utils/ethers.ts | 13 ++ .../settings/pages/network/network-form.tsx | 15 ++- 21 files changed, 399 insertions(+), 67 deletions(-) create mode 100644 src/ui/common/components/error-modal/error-modal.component.tsx create mode 100644 src/ui/common/components/wallet/components/transaction-history/transaction-details.component.tsx diff --git a/assets/locales/en-US.json b/assets/locales/en-US.json index b106e38..76897ee 100644 --- a/assets/locales/en-US.json +++ b/assets/locales/en-US.json @@ -31,7 +31,7 @@ "TRY_AGAIN": "Try Again", "COPY_TO_CLIPBOARD_MESSAGE": "Text copied to clipboard", "INVALID_PASSWORD": "Invalid password.", - "INVALID_USERNAME": "Invalid username.", + "INVALID_USERNAME": "Account on selected network does not exist.", "GENERAL_ERROR_MESSAGE": "Couldn't complete request at the moment.", "USERNAME_NOT_AVAILABLE": "Username is not available", "CANNOT_CHECK_USERNAME": "Cannot check username. Network currently unavailable", @@ -103,6 +103,7 @@ "CLEAR_ACTIVITY_DATA_CONFIRMATION": "Do you really want to clear all your wallet data?", "SELECT": "Select", "SELECT_ADDRESS": "Choose address to send to", + "SELECT_ADDRESS_TOKEN": "Choose address to send {tokenName} to", "PROCEED": "Proceed", "SENDING_TO": "Sednding to", "ENABLE_WALLET_LOCK": "Enable wallet lock", @@ -113,5 +114,14 @@ "IMPORT_TOKEN": "Import Token", "IMPORT_TOKEN_INSTRUCTIONS": "Enter contract address to import token", "IMPORT_TOKEN_SUCCESS": "Token is successfully imported", - "NOT_ENOUGH_BALANCE": "Your current balance is too low for this transaction" + "NOT_ENOUGH_BALANCE": "Your current balance is too low for this transaction", + "NETWORK_UNAVAILABLE_ERROR": "Network RPC is not reachable.", + "ERROR_DESCRIPTION": "Error description", + "BLOCK_EXPLORER_URL_LABEL": "Block Explorer URL (Optional)", + "TRANSACTION_DETAILS": "Transaction Details", + "RECIPIENT": "Recipient", + "GAS_CONST": "Gas Cost", + "TRANSCTION_HASH": "Transaction hash", + "DATA": "Data", + "TIME": "Time" } diff --git a/src/constants/networks.ts b/src/constants/networks.ts index 1704612..ed2b94e 100644 --- a/src/constants/networks.ts +++ b/src/constants/networks.ts @@ -15,10 +15,12 @@ export const networks: Network[] = [ ...extractNetworkConfig(Environments.SEPOLIA), label: 'Sepolia', custom: false, + blockExplorerUrl: 'https://sepolia.etherscan.io/tx/', }, { ...extractNetworkConfig(Environments.GOERLI), label: 'Görli', custom: false, + blockExplorerUrl: 'https://goerli.etherscan.io/tx/', }, ] diff --git a/src/listeners/message-listeners/account.listener.ts b/src/listeners/message-listeners/account.listener.ts index b04d5c4..aeeeb70 100644 --- a/src/listeners/message-listeners/account.listener.ts +++ b/src/listeners/message-listeners/account.listener.ts @@ -19,7 +19,6 @@ import { TokenCheckRequest, TokenRequest, TokenTransferRequest, - Transaction, } from '../../model/internal-messages.model' import { SessionFdpStorageProvider } from '../../services/fdp-storage/session-fdp-storage.provider' import { Dialog } from '../../services/dialog.service' @@ -42,7 +41,7 @@ const wallet = new WalletService() const fdpStorageProvider = new SessionFdpStorageProvider() function saveTransaction( - transaction: Transaction | InternalTransaction, + transaction: InternalTransaction, transactionContent: providers.TransactionReceipt, accountName: string, networkLabel: string, @@ -60,6 +59,7 @@ function saveTransaction( data: transaction.data, gas: transactionContent.gasUsed.toString(), gasPrice: transactionContent.effectiveGasPrice.toString(), + hash: transactionContent.transactionHash, }, token, }, @@ -253,7 +253,12 @@ export async function getTokenBalance({ token: { address }, rpcUrl }: TokenReque return balance.toString() } -export async function transferTokens({ token, to, value, rpcUrl }: TokenTransferRequest): Promise { +export async function transferTokens({ + token, + to, + value, + rpcUrl, +}: TokenTransferRequest): Promise { const [{ ensUserName, localUserName }, fdp] = await Promise.all([ session.load(), fdpStorageProvider.getService(), @@ -272,6 +277,8 @@ export async function transferTokens({ token, to, value, rpcUrl }: TokenTransfer networks.find(({ rpc }) => rpc === rpcUrl).label, token, ) + + return receipt } const messageHandler = createMessageHandler([ diff --git a/src/messaging/content-api.messaging.ts b/src/messaging/content-api.messaging.ts index 3d704d2..faec0cf 100644 --- a/src/messaging/content-api.messaging.ts +++ b/src/messaging/content-api.messaging.ts @@ -1,4 +1,4 @@ -import { BigNumber } from 'ethers' +import { BigNumber, providers } from 'ethers' import BackgroundAction from '../constants/background-actions.enum' import { Address, BigNumberString, DappId } from '../model/general.types' import { @@ -86,8 +86,11 @@ export async function getTokenBalance(token: Token, rpcUrl: string): Promise { - return sendMessage(BackgroundAction.SEND_TRANSACTION_INTERNAL, transaction) +export function sendTransaction(transaction: InternalTransaction): Promise { + return sendMessage( + BackgroundAction.SEND_TRANSACTION_INTERNAL, + transaction, + ) } export async function estimateGasPrice(transaction: InternalTransaction): Promise { @@ -108,8 +111,13 @@ export async function estimateTokenGasPrice(tokenTransferRequest: TokenTransferR return BigNumber.from(price) } -export function transferTokens(tokenTransferRequest: TokenTransferRequest): Promise { - return sendMessage(BackgroundAction.TRANSFER_TOKENS, tokenTransferRequest) +export function transferTokens( + tokenTransferRequest: TokenTransferRequest, +): Promise { + return sendMessage( + BackgroundAction.TRANSFER_TOKENS, + tokenTransferRequest, + ) } export function getWalletTransactions(networkLabel: string): Promise { diff --git a/src/model/storage/network.model.ts b/src/model/storage/network.model.ts index 32e4194..ec2fc2d 100644 --- a/src/model/storage/network.model.ts +++ b/src/model/storage/network.model.ts @@ -7,4 +7,5 @@ export interface Network { fdsRegistrar?: Address publicResolver?: Address custom: boolean + blockExplorerUrl?: string } diff --git a/src/model/storage/wallet.model.ts b/src/model/storage/wallet.model.ts index 1bd4cb3..ad1d45a 100644 --- a/src/model/storage/wallet.model.ts +++ b/src/model/storage/wallet.model.ts @@ -1,4 +1,4 @@ -import { Address, BigNumberString, HexStringVariate } from '../general.types' +import { Address, BigNumberString, HexString, HexStringVariate } from '../general.types' export type TransactionDirection = 'sent' | 'received' @@ -13,6 +13,7 @@ export interface Transaction { gas: BigNumberString gasPrice: BigNumberString data?: HexStringVariate + hash: HexString<64> } token?: Token } diff --git a/src/ui/common/components/clipboard-button/clipboard-button.component.tsx b/src/ui/common/components/clipboard-button/clipboard-button.component.tsx index 18d567f..60ca448 100644 --- a/src/ui/common/components/clipboard-button/clipboard-button.component.tsx +++ b/src/ui/common/components/clipboard-button/clipboard-button.component.tsx @@ -5,9 +5,10 @@ import ContentCopy from '@mui/icons-material/ContentCopy' export interface ClipboardButtonProps { text: string + size?: 'large' | 'medium' | 'small' } -const ClipboardButton = ({ text }: ClipboardButtonProps) => { +const ClipboardButton = ({ text, size }: ClipboardButtonProps) => { const [open, setOpen] = useState(false) const [closeTimeoutHandle, setCloseTimeoutHandle] = useState(null) @@ -34,7 +35,7 @@ const ClipboardButton = ({ text }: ClipboardButtonProps) => { return ( <> - + } -const ErrorMessage = ({ children }: ErrorMessageProps) => { +const ErrorMessage = ({ children, onClick }: ErrorMessageProps) => { return ( { sx={{ color: (theme) => theme.palette.error.main, marginTop: '20px', + cursor: onClick ? 'pointer' : 'auto', }} + onClick={onClick} > {children} diff --git a/src/ui/common/components/error-modal/error-modal.component.tsx b/src/ui/common/components/error-modal/error-modal.component.tsx new file mode 100644 index 0000000..034eb6e --- /dev/null +++ b/src/ui/common/components/error-modal/error-modal.component.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import intl from 'react-intl-universal' +import { IconButton, Modal, Typography } from '@mui/material' +import { Box } from '@mui/system' +import Close from '@mui/icons-material/Close' + +export interface ErrorModalProps { + open: boolean + onClose: () => void + error: string +} + +const ErrorModal = ({ open, onClose, error }: ErrorModalProps) => { + return ( + + + + {intl.get('ERROR_DESCRIPTION')} + + + + + + {error} + + + + ) +} + +export default ErrorModal diff --git a/src/ui/common/components/wallet/components/send/address-select.component.tsx b/src/ui/common/components/wallet/components/send/address-select.component.tsx index f16daa9..11f7b38 100644 --- a/src/ui/common/components/wallet/components/send/address-select.component.tsx +++ b/src/ui/common/components/wallet/components/send/address-select.component.tsx @@ -7,10 +7,12 @@ import FieldSpinner from '../../../field-spinner/field-spinner.component' import { Address } from '../../../../../../model/general.types' import { addressRegex } from '../../../../utils/ethers' import { useWalletLock } from '../../hooks/wallet-lock.hook' +import { Token } from '../../../../../../model/storage/wallet.model' interface AddressSelectProps { addresses: Address[] disabled: boolean + token?: Token onSubmit: (address: string) => void } @@ -18,7 +20,7 @@ interface FormFields { address: Address } -const AddressSelect = ({ addresses, disabled, onSubmit }: AddressSelectProps) => { +const AddressSelect = ({ addresses, disabled, token, onSubmit }: AddressSelectProps) => { useWalletLock() const { register, @@ -30,7 +32,7 @@ const AddressSelect = ({ addresses, disabled, onSubmit }: AddressSelectProps) => return (
onSubmit(address))}> - {intl.get('SELECT_ADDRESS')}: + {token ? intl.get('SELECT_ADDRESS_TOKEN', { tokenName: token.name }) : intl.get('SELECT_ADDRESS')}: ( diff --git a/src/ui/common/components/wallet/components/send/transaction-completed.tsx b/src/ui/common/components/wallet/components/send/transaction-completed.tsx index c81c816..cec4549 100644 --- a/src/ui/common/components/wallet/components/send/transaction-completed.tsx +++ b/src/ui/common/components/wallet/components/send/transaction-completed.tsx @@ -4,12 +4,27 @@ import CheckCircle from '@mui/icons-material/CheckCircle' import { Button, Typography } from '@mui/material' import { FlexColumnDiv } from '../../../utils/utils' import { useNavigate } from 'react-router-dom' +import { providers } from 'ethers' +import { Token } from '../../../../../../model/storage/wallet.model' +import { BigNumberString } from '../../../../../../model/general.types' +import { constructBlockExplorerUrl, displayAddress } from '../../../../utils/ethers' +import ClipboardButton from '../../../clipboard-button/clipboard-button.component' export interface TransactionCompletedProps { + value: BigNumberString + token?: Token + transaction: providers.TransactionReceipt + blockExplorerUrl?: string onReset: () => void } -const TransactionCompleted = ({ onReset }: TransactionCompletedProps) => { +const TransactionCompleted = ({ + value, + token, + transaction, + blockExplorerUrl, + onReset, +}: TransactionCompletedProps) => { const navigate = useNavigate() return ( @@ -19,6 +34,22 @@ const TransactionCompleted = ({ onReset }: TransactionCompletedProps) => { {intl.get('TRANSACTION_COMPLETE')} + + {value} {token ? `${token.symbol} (${token.name})` : 'ETH'} + + + {blockExplorerUrl ? ( + + {displayAddress(transaction.transactionHash)} + + ) : ( +
{displayAddress(transaction.transactionHash)}
+ )} + +
- - )} + + {token.name === selectedToken?.name ? ( + + ) : ( + {token.symbol} + )} + + + + ))} + + ) } diff --git a/src/ui/common/components/wallet/components/wallet-overview.component.tsx b/src/ui/common/components/wallet/components/wallet-overview.component.tsx index bcda9f2..f252f78 100644 --- a/src/ui/common/components/wallet/components/wallet-overview.component.tsx +++ b/src/ui/common/components/wallet/components/wallet-overview.component.tsx @@ -2,10 +2,11 @@ import React, { useEffect, useState } from 'react' import intl from 'react-intl-universal' import { BigNumber } from 'ethers' import Send from '@mui/icons-material/Send' +import ArrowBackIosNew from '@mui/icons-material/ArrowBackIosNew' import { FlexColumnDiv, FlexDiv } from '../../utils/utils' import { getAccountBalance, getTokenBalance } from '../../../../../messaging/content-api.messaging' import { UserResponse } from '../../../../../model/internal-messages.model' -import { Button, CircularProgress, Divider, MenuItem, Select, Typography } from '@mui/material' +import { Button, CircularProgress, Divider, IconButton, MenuItem, Select, Typography } from '@mui/material' import { Network } from '../../../../../model/storage/network.model' import { useNetworks } from '../../../hooks/networks.hooks' import { displayAddress, displayBalance } from '../../../utils/ethers' @@ -18,12 +19,14 @@ import TransactionHistory from './transaction-history/transaction-history.compon import { Token } from '../../../../../model/storage/wallet.model' import ErrorModal from '../../error-modal/error-modal.component' import { useUser } from '../../../hooks/user.hooks' +import { useWalletLock } from '../hooks/wallet-lock.hook' interface WalletOverviewProps { user: UserResponse + onLock: () => void } -const WalletOverview = ({ user: { address, network } }: WalletOverviewProps) => { +const WalletOverview = ({ user: { address, network }, onLock }: WalletOverviewProps) => { const { walletNetwork, setWalletNetwork, selectedToken, setSelectedToken } = useWallet() const [selectedNetwork, setSelectedNetwork] = useState(walletNetwork || network) const [balance, setBalance] = useState(null) @@ -33,6 +36,7 @@ const WalletOverview = ({ user: { address, network } }: WalletOverviewProps) => const { networks } = useNetworks() const { user } = useUser() const navigate = useNavigate() + const { checkLockError } = useWalletLock() const loadData = async (network: Network, token: Token) => { try { @@ -45,6 +49,11 @@ const WalletOverview = ({ user: { address, network } }: WalletOverviewProps) => setBalance(balance) } catch (error) { console.error(error) + const locked = await checkLockError(error) + + if (locked) { + onLock() + } setError(String(error)) } } @@ -126,9 +135,16 @@ const WalletOverview = ({ user: { address, network } }: WalletOverviewProps) => ) : ( <> {balance ? ( - - {displayBalance(balance, selectedToken)} - + + {selectedToken && ( + setSelectedToken(null)}> + + + )} + + {displayBalance(balance, selectedToken)} + + ) : ( )} diff --git a/src/ui/common/components/wallet/components/wallet.component.tsx b/src/ui/common/components/wallet/components/wallet.component.tsx index 4481f23..a843a7c 100644 --- a/src/ui/common/components/wallet/components/wallet.component.tsx +++ b/src/ui/common/components/wallet/components/wallet.component.tsx @@ -32,7 +32,11 @@ const Wallet = () => {
{user && locked !== null && - (locked ? setLocked(false)} /> : )} + (locked ? ( + setLocked(false)} /> + ) : ( + setLocked(true)} /> + ))} {error && {error}} ) diff --git a/src/ui/common/components/wallet/hooks/wallet-lock.hook.ts b/src/ui/common/components/wallet/hooks/wallet-lock.hook.ts index e778f03..3a854bf 100644 --- a/src/ui/common/components/wallet/hooks/wallet-lock.hook.ts +++ b/src/ui/common/components/wallet/hooks/wallet-lock.hook.ts @@ -1,19 +1,38 @@ import { useEffect } from 'react' import { refreshWalletLock } from '../../../../../messaging/content-api.messaging' import { useNavigate } from 'react-router-dom' +import { errorMessages } from '../../../../../constants/errors' export function useWalletLock() { const navigate = useNavigate() - const checkLock = async () => { + const checkLock = async (): Promise => { try { await refreshWalletLock() + + return false } catch (error) { setTimeout(() => navigate('..')) + + return true } } + const checkLockError = async (error: unknown): Promise => { + try { + if (error.toString().includes(errorMessages.WALLET_LOCKED)) { + return await checkLock() + } + } catch (error) {} + + return false + } + useEffect(() => { checkLock() }, []) + + return { + checkLockError, + } } From 5a9a4fa5d062b32c32c95a23e7a8ba604774bdbb Mon Sep 17 00:00:00 2001 From: Vladan Date: Thu, 24 Aug 2023 16:22:51 +0200 Subject: [PATCH 5/6] feat: fetching account info --- assets/locales/en-US.json | 4 ++- library/src/constants/api-actions.enum.ts | 1 + library/src/model/account-info.ts | 4 +++ library/src/wallet.ts | 10 ++++++ src/constants/background-actions.enum.ts | 1 + src/constants/dapp-actions.enum.ts | 2 ++ src/constants/whitelisted-dapps.ts | 4 +++ .../message-listeners/auth.listener.ts | 32 +++++++++++++++++++ src/model/internal-messages.model.ts | 5 +++ src/model/storage/dapps.model.ts | 1 + src/services/storage/storage-factories.ts | 1 + .../dapp-permissions-form.component.tsx | 14 ++++++++ test/dapp-library.spec.ts | 28 ++++++++++++++++ test/dapps/wallet/index.html | 4 +++ test/dapps/wallet/index.js | 17 ++++++++++ test/test-utils/account.ts | 4 --- test/wallet.spec.ts | 6 ++-- 17 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 library/src/model/account-info.ts diff --git a/assets/locales/en-US.json b/assets/locales/en-US.json index 76897ee..71d97fd 100644 --- a/assets/locales/en-US.json +++ b/assets/locales/en-US.json @@ -123,5 +123,7 @@ "GAS_CONST": "Gas Cost", "TRANSCTION_HASH": "Transaction hash", "DATA": "Data", - "TIME": "Time" + "TIME": "Time", + "DIALOG_DAPP_ACCOUNT_INFO": "dApp with ID {dappId} asks to access your account information (address and ENS name).\nDo you allow access to your account information?", + "ACCOUNT_INFORMATION_ACCESS": "Account Information Access" } diff --git a/library/src/constants/api-actions.enum.ts b/library/src/constants/api-actions.enum.ts index c377fa2..873fae3 100644 --- a/library/src/constants/api-actions.enum.ts +++ b/library/src/constants/api-actions.enum.ts @@ -3,5 +3,6 @@ export enum ApiActions { SIGNER_SIGN_MESSAGE = 'signer.signMessage', SEND_TRANSACTION = 'send-transaction', GET_USER_BALANCE = 'get-user-balance', + GET_USER_INFO = 'get-user-info', ECHO = 'echo', } diff --git a/library/src/model/account-info.ts b/library/src/model/account-info.ts new file mode 100644 index 0000000..25533d5 --- /dev/null +++ b/library/src/model/account-info.ts @@ -0,0 +1,4 @@ +export interface AccountInfo { + address: string + ensName?: string +} diff --git a/library/src/wallet.ts b/library/src/wallet.ts index 757dc6e..974cfbf 100644 --- a/library/src/wallet.ts +++ b/library/src/wallet.ts @@ -1,9 +1,19 @@ import { ApiActions } from './constants/api-actions.enum' import { BlossomMessages } from './messages/blossom-messages' +import { AccountInfo } from './model/account-info' export class Wallet { constructor(private messages: BlossomMessages) {} + /** + * Returns user's account information, like account address and + * ENS name (if available). + * @returns AccountInfo object + */ + public getAccountInfo(): Promise { + return this.messages.sendMessage(ApiActions.GET_USER_INFO) + } + /** * Returns account balance of current user in wei * @returns current balance in wei diff --git a/src/constants/background-actions.enum.ts b/src/constants/background-actions.enum.ts index 481e09f..9b617a3 100644 --- a/src/constants/background-actions.enum.ts +++ b/src/constants/background-actions.enum.ts @@ -9,6 +9,7 @@ enum BackgroundAction { GENERATE_WALLET = 'generate-wallet', OPEN_AUTH_PAGE = 'open-auth-page', GET_CURRENT_USER = 'get-current-user', + GET_USER_INFO = 'get-user-info', GET_LOCAL_ACCOUNTS = 'get-local-accounts', GET_BALANCE = 'get-balance', GET_USER_BALANCE = 'get-user-balance', diff --git a/src/constants/dapp-actions.enum.ts b/src/constants/dapp-actions.enum.ts index e3ae616..80c32ab 100644 --- a/src/constants/dapp-actions.enum.ts +++ b/src/constants/dapp-actions.enum.ts @@ -5,6 +5,7 @@ export const DAPP_ACTIONS: string[] = [ BackgroundAction.SIGNER_SIGN_MESSAGE, BackgroundAction.SEND_TRANSACTION, BackgroundAction.GET_USER_BALANCE, + BackgroundAction.GET_USER_INFO, BackgroundAction.ECHO, ] @@ -13,5 +14,6 @@ export const E2E_ACTIONS: string[] = [ BackgroundAction.SIGNER_SIGN_MESSAGE, BackgroundAction.SEND_TRANSACTION, BackgroundAction.GET_USER_BALANCE, + BackgroundAction.GET_USER_INFO, BackgroundAction.ECHO, ] diff --git a/src/constants/whitelisted-dapps.ts b/src/constants/whitelisted-dapps.ts index 9e55a4c..352c80b 100644 --- a/src/constants/whitelisted-dapps.ts +++ b/src/constants/whitelisted-dapps.ts @@ -13,3 +13,7 @@ export const whitelistedDapps: Array<{ url: string; dappId: string }> = [ { url: 'https://fairdrive.dev.fairdatasociety.org/apps/slidezz', dappId: 'slidezz-dev' }, { url: 'https://fairdrive.fairdatasociety.org/apps/slidezz', dappId: 'slidezz' }, ] + +if (process.env.ENVIRONMENT === 'development') { + whitelistedDapps.push({ url: 'http://localhost:3000', dappId: 'fairdrive-local' }) +} diff --git a/src/listeners/message-listeners/auth.listener.ts b/src/listeners/message-listeners/auth.listener.ts index 2c1abee..7065ce4 100644 --- a/src/listeners/message-listeners/auth.listener.ts +++ b/src/listeners/message-listeners/auth.listener.ts @@ -15,6 +15,7 @@ import { LoginData, RegisterData, RegisterResponse, + UserInfo, UsernameCheckData, UserResponse, } from '../../model/internal-messages.model' @@ -24,9 +25,13 @@ import { SessionService } from '../../services/session.service' import { Storage } from '../../services/storage/storage.service' import { openTab } from '../../utils/tabs' import { createMessageHandler } from './message-handler' +import { getDappId } from './listener.utils' +import { Dialog } from '../../services/dialog.service' +import { errorMessages } from '../../constants/errors' let fdpStorageProvider = new SessionlessFdpStorageProvider() const storage = new Storage() +const dialogs = new Dialog() const session = new SessionService() const account = new AccountService() @@ -182,6 +187,29 @@ export function logout(): Promise { return session.close() } +export async function getUserInfo(data, sender: chrome.runtime.MessageSender): Promise { + const [sessionData, dappId] = await Promise.all([session.load(), getDappId(sender)]) + + const dapp = await storage.getDappBySession(dappId, sessionData) + + if (!dapp.accountInfoAccess) { + const confirmed = await dialogs.ask('DIALOG_DAPP_ACCOUNT_INFO', { dappId }) + + if (!confirmed) { + throw new Error(errorMessages.ACCESS_DENIED) + } + + await storage.updateDappBySession(dappId, { accountInfoAccess: true }, sessionData) + } + + const { ensUserName: ensName, address } = sessionData + + return { + ensName, + address, + } +} + const messageHandler = createMessageHandler([ { action: BackgroundAction.LOGIN, @@ -229,6 +257,10 @@ const messageHandler = createMessageHandler([ action: BackgroundAction.LOGOUT, handler: logout, }, + { + action: BackgroundAction.GET_USER_INFO, + handler: getUserInfo, + }, ]) export default messageHandler diff --git a/src/model/internal-messages.model.ts b/src/model/internal-messages.model.ts index 29cdb06..c6572ed 100644 --- a/src/model/internal-messages.model.ts +++ b/src/model/internal-messages.model.ts @@ -120,3 +120,8 @@ export interface TokenTransferRequest extends TokenRequest { to: string value: string } + +export interface UserInfo { + address: Address + ensName?: string +} diff --git a/src/model/storage/dapps.model.ts b/src/model/storage/dapps.model.ts index 0212160..76f4bc8 100644 --- a/src/model/storage/dapps.model.ts +++ b/src/model/storage/dapps.model.ts @@ -16,6 +16,7 @@ export interface PodPermission { export interface Dapp { podPermissions: Record fullStorageAccess: boolean + accountInfoAccess: boolean dappId: DappId } diff --git a/src/services/storage/storage-factories.ts b/src/services/storage/storage-factories.ts index e066692..b1b5ecc 100644 --- a/src/services/storage/storage-factories.ts +++ b/src/services/storage/storage-factories.ts @@ -41,6 +41,7 @@ export function dappFactory(dappId: DappId): Dapp { return { podPermissions: {}, fullStorageAccess: false, + accountInfoAccess: false, dappId, } } diff --git a/src/ui/settings/pages/permissions/dapp-permissions/dapp-permissions-form.component.tsx b/src/ui/settings/pages/permissions/dapp-permissions/dapp-permissions-form.component.tsx index 4c7984f..f0f41e4 100644 --- a/src/ui/settings/pages/permissions/dapp-permissions/dapp-permissions-form.component.tsx +++ b/src/ui/settings/pages/permissions/dapp-permissions/dapp-permissions-form.component.tsx @@ -18,6 +18,7 @@ const DappPermissionsForm = ({ dapp, disabled, onUpdate }: DappPermissionsFormPr ...dapp.podPermissions, }) const [fullStorageAccess, setFullStorageAccess] = useState(dapp.fullStorageAccess) + const [accountInfoAccess, setAccountInfoAccess] = useState(dapp.accountInfoAccess) const { dappId } = dapp const onPodPermissionDelete = (podName: string) => { @@ -32,6 +33,7 @@ const DappPermissionsForm = ({ dapp, disabled, onUpdate }: DappPermissionsFormPr onUpdate({ ...dapp, fullStorageAccess, + accountInfoAccess, podPermissions, }) } @@ -44,6 +46,18 @@ const DappPermissionsForm = ({ dapp, disabled, onUpdate }: DappPermissionsFormPr {dappId} + setAccountInfoAccess(!accountInfoAccess)} + /> + } + disabled={disabled} + label={intl.get('ACCOUNT_INFORMATION_ACCESS')} + sx={{ margin: '20px 0 0 0' }} + /> { expect(`0.${balance.substring(0, 2)}`).toEqual(expectedBalance) } + test("Shouldn't get account info if user didn't allow", async () => { + await click(page, 'get-account-info-btn-1') + + await wait(5000) + + const blossomPage = await getPageByTitle('Blossom') + + await click(blossomPage, 'dialog-cancel-btn') + + expect(await waitForElementText(page, '#account-info-1[complete="true"]')).toEqual( + 'Error: Blossom: Access denied', + ) + }) + + test('Should get account info', async () => { + await click(page, 'get-account-info-btn-2') + + await wait(5000) + + const blossomPage = await getPageByTitle('Blossom') + + await click(blossomPage, 'dialog-confirm-btn') + + const wallet = Wallet.fromMnemonic(mnemonic) + + expect(await waitForElementText(page, '#account-info-2[complete="true"]')).toEqual(wallet.address) + }) + test('Should get initial balance', async () => { await click(page, 'get-balance-btn') diff --git a/test/dapps/wallet/index.html b/test/dapps/wallet/index.html index 9bbfb08..d74adeb 100644 --- a/test/dapps/wallet/index.html +++ b/test/dapps/wallet/index.html @@ -9,6 +9,10 @@ + +

Empty

+ +

Empty

Empty

diff --git a/test/dapps/wallet/index.js b/test/dapps/wallet/index.js index 6885999..769a3c3 100644 --- a/test/dapps/wallet/index.js +++ b/test/dapps/wallet/index.js @@ -6,6 +6,15 @@ function setText(id, text) { element.setAttribute('complete', 'true') } +async function getAccountInfo(elementId) { + try { + const { address } = await blossom.wallet.getAccountInfo() + setText(elementId, address) + } catch (error) { + setText(elementId, error.toString()) + } +} + async function getBalance(elementId) { try { const balance = await blossom.wallet.getUserBalance() @@ -25,6 +34,14 @@ async function sendTransaction(elementId) { } } +function getAccountInfo1() { + return getAccountInfo('account-info-1') +} + +function getAccountInfo2() { + return getAccountInfo('account-info-2') +} + function getInitialBalance() { return getBalance('balance') } diff --git a/test/test-utils/account.ts b/test/test-utils/account.ts index fe31c07..93fc5e4 100644 --- a/test/test-utils/account.ts +++ b/test/test-utils/account.ts @@ -147,7 +147,3 @@ export async function register(username: string, password: string): Promise { - return waitForElementTextByTestId(page, 'address') -} diff --git a/test/wallet.spec.ts b/test/wallet.spec.ts index d74ce31..ea9caa7 100644 --- a/test/wallet.spec.ts +++ b/test/wallet.spec.ts @@ -1,6 +1,6 @@ import { ElementHandle, Page } from 'puppeteer' import { openExtensionOptionsPage } from './test-utils/extension.util' -import { getWalletAddress, login, registerExisting } from './test-utils/account' +import { login, registerExisting } from './test-utils/account' import { click, dataTestId, @@ -14,7 +14,7 @@ import { } from './test-utils/page' import { getRandomString } from './test-utils/extension.util' import deployContracts, { transferToken } from './config/contract-deployment' -import { BigNumber } from 'ethers' +import { BigNumber, Wallet } from 'ethers' import { sendFunds } from './test-utils/ethers' import { PRIVATE_KEY } from './config/constants' @@ -138,7 +138,7 @@ describe('Wallet tokens tests', () => { await deployContracts() await login(username, password) page = await openExtensionOptionsPage(blossomId, 'wallet.html') - walletAddress = await getWalletAddress(page) + walletAddress = Wallet.fromMnemonic(mnemonic).address await sendFunds(PRIVATE_KEY, walletAddress, '0.1') await transferToken( global.__TEST_TOKEN_ADDRESS__, From 3e5654c77d37e82a2d30133626110d31d288d715 Mon Sep 17 00:00:00 2001 From: Vladan Date: Fri, 25 Aug 2023 16:42:39 +0200 Subject: [PATCH 6/6] feat: improve error messages ui --- assets/locales/en-US.json | 6 ++++-- .../wallet/components/send/transaction-confirmation.tsx | 7 ++++++- .../tokens/token-import.component.tsx | 9 ++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/assets/locales/en-US.json b/assets/locales/en-US.json index 71d97fd..3d3459a 100644 --- a/assets/locales/en-US.json +++ b/assets/locales/en-US.json @@ -105,7 +105,7 @@ "SELECT_ADDRESS": "Choose address to send to", "SELECT_ADDRESS_TOKEN": "Choose address to send {tokenName} to", "PROCEED": "Proceed", - "SENDING_TO": "Sednding to", + "SENDING_TO": "Sending to", "ENABLE_WALLET_LOCK": "Enable wallet lock", "MINUTES": "Minutes", "LOCK_INTERVAL": "Lock interval", @@ -125,5 +125,7 @@ "DATA": "Data", "TIME": "Time", "DIALOG_DAPP_ACCOUNT_INFO": "dApp with ID {dappId} asks to access your account information (address and ENS name).\nDo you allow access to your account information?", - "ACCOUNT_INFORMATION_ACCESS": "Account Information Access" + "ACCOUNT_INFORMATION_ACCESS": "Account Information Access", + "TOKEN_IMPORT_ERROR": "Couldn't get token data.", + "TRANSACTION_ERROR": "The transaction failed. Click here to see the details." } diff --git a/src/ui/common/components/wallet/components/send/transaction-confirmation.tsx b/src/ui/common/components/wallet/components/send/transaction-confirmation.tsx index f6293f7..bbf6301 100644 --- a/src/ui/common/components/wallet/components/send/transaction-confirmation.tsx +++ b/src/ui/common/components/wallet/components/send/transaction-confirmation.tsx @@ -11,6 +11,7 @@ import { BigNumber } from 'ethers' import ErrorMessage from '../../../error-message/error-message.component' import { useWalletLock } from '../../hooks/wallet-lock.hook' import { Token } from '../../../../../../model/storage/wallet.model' +import ErrorModal from '../../../error-modal/error-modal.component' export interface TransactionConfirmationProps { address: Address @@ -36,6 +37,7 @@ const TransactionConfirmation = ({ onSubmit, }: TransactionConfirmationProps) => { const [gasPrice, setGasPrice] = useState(BigNumber.from(0)) + const [errorModalOpen, setErrorModalOpen] = useState(false) useWalletLock() const realValue = useMemo(() => { @@ -44,7 +46,9 @@ const TransactionConfirmation = ({ return ( <> - {error && {error}} + {error && ( + setErrorModalOpen(true)}>{intl.get('TRANSACTION_ERROR')} + )} @@ -103,6 +107,7 @@ const TransactionConfirmation = ({ + setErrorModalOpen(false)} error={error} /> ) } diff --git a/src/ui/common/components/wallet/components/transaction-history/tokens/token-import.component.tsx b/src/ui/common/components/wallet/components/transaction-history/tokens/token-import.component.tsx index 83c1fa2..0c33f15 100644 --- a/src/ui/common/components/wallet/components/transaction-history/tokens/token-import.component.tsx +++ b/src/ui/common/components/wallet/components/transaction-history/tokens/token-import.component.tsx @@ -21,6 +21,7 @@ import TokenInfo from './token-info.component' import { useNavigate } from 'react-router-dom' import Header from '../../../../header/header.component' import { BigNumber } from 'ethers' +import ErrorModal from '../../../../error-modal/error-modal.component' interface FormFields { address: Address @@ -38,6 +39,7 @@ const TokenImport = () => { const [importDone, setImportDone] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + const [errorModalOpen, setErrorModalOpen] = useState(false) const { user } = useUser() const { walletNetwork } = useWallet() const navigate = useNavigate() @@ -126,7 +128,11 @@ const TokenImport = () => { data-testid="address-input" /> {token && } - {error && {error}} + {error && ( + setErrorModalOpen(true)}> + {intl.get('TOKEN_IMPORT_ERROR')} + + )}