Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/wallet improvements #165

Merged
merged 6 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions assets/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -103,8 +103,9 @@
"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",
"SENDING_TO": "Sending to",
"ENABLE_WALLET_LOCK": "Enable wallet lock",
"MINUTES": "Minutes",
"LOCK_INTERVAL": "Lock interval",
Expand All @@ -113,5 +114,18 @@
"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",
"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",
"TOKEN_IMPORT_ERROR": "Couldn't get token data.",
"TRANSACTION_ERROR": "The transaction failed. Click here to see the details."
}
1 change: 1 addition & 0 deletions library/src/constants/api-actions.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
4 changes: 4 additions & 0 deletions library/src/model/account-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface AccountInfo {
address: string
ensName?: string
}
10 changes: 10 additions & 0 deletions library/src/wallet.ts
Original file line number Diff line number Diff line change
@@ -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<AccountInfo> {
return this.messages.sendMessage(ApiActions.GET_USER_INFO)
}

/**
* Returns account balance of current user in wei
* @returns current balance in wei
Expand Down
1 change: 1 addition & 0 deletions src/constants/background-actions.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions src/constants/dapp-actions.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]

Expand All @@ -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,
]
2 changes: 2 additions & 0 deletions src/constants/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/',
},
]
4 changes: 4 additions & 0 deletions src/constants/whitelisted-dapps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
}
13 changes: 10 additions & 3 deletions src/listeners/message-listeners/account.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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,
Expand All @@ -60,6 +59,7 @@ function saveTransaction(
data: transaction.data,
gas: transactionContent.gasUsed.toString(),
gasPrice: transactionContent.effectiveGasPrice.toString(),
hash: transactionContent.transactionHash,
},
token,
},
Expand Down Expand Up @@ -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<void> {
export async function transferTokens({
token,
to,
value,
rpcUrl,
}: TokenTransferRequest): Promise<providers.TransactionReceipt> {
const [{ ensUserName, localUserName }, fdp] = await Promise.all([
session.load(),
fdpStorageProvider.getService(),
Expand All @@ -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([
Expand Down
32 changes: 32 additions & 0 deletions src/listeners/message-listeners/auth.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
LoginData,
RegisterData,
RegisterResponse,
UserInfo,
UsernameCheckData,
UserResponse,
} from '../../model/internal-messages.model'
Expand All @@ -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()

Expand Down Expand Up @@ -182,6 +187,29 @@ export function logout(): Promise<void> {
return session.close()
}

export async function getUserInfo(data, sender: chrome.runtime.MessageSender): Promise<UserInfo> {
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,
Expand Down Expand Up @@ -229,6 +257,10 @@ const messageHandler = createMessageHandler([
action: BackgroundAction.LOGOUT,
handler: logout,
},
{
action: BackgroundAction.GET_USER_INFO,
handler: getUserInfo,
},
])

export default messageHandler
18 changes: 13 additions & 5 deletions src/messaging/content-api.messaging.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -86,8 +86,11 @@ export async function getTokenBalance(token: Token, rpcUrl: string): Promise<Big
return BigNumber.from(balance)
}

export function sendTransaction(transaction: InternalTransaction): Promise<void> {
return sendMessage<InternalTransaction, void>(BackgroundAction.SEND_TRANSACTION_INTERNAL, transaction)
export function sendTransaction(transaction: InternalTransaction): Promise<providers.TransactionReceipt> {
return sendMessage<InternalTransaction, providers.TransactionReceipt>(
BackgroundAction.SEND_TRANSACTION_INTERNAL,
transaction,
)
}

export async function estimateGasPrice(transaction: InternalTransaction): Promise<BigNumber> {
Expand All @@ -108,8 +111,13 @@ export async function estimateTokenGasPrice(tokenTransferRequest: TokenTransferR
return BigNumber.from(price)
}

export function transferTokens(tokenTransferRequest: TokenTransferRequest): Promise<void> {
return sendMessage<TokenTransferRequest, void>(BackgroundAction.TRANSFER_TOKENS, tokenTransferRequest)
export function transferTokens(
tokenTransferRequest: TokenTransferRequest,
): Promise<providers.TransactionReceipt> {
return sendMessage<TokenTransferRequest, providers.TransactionReceipt>(
BackgroundAction.TRANSFER_TOKENS,
tokenTransferRequest,
)
}

export function getWalletTransactions(networkLabel: string): Promise<Transactions> {
Expand Down
5 changes: 5 additions & 0 deletions src/model/internal-messages.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,8 @@ export interface TokenTransferRequest extends TokenRequest {
to: string
value: string
}

export interface UserInfo {
address: Address
ensName?: string
}
1 change: 1 addition & 0 deletions src/model/storage/dapps.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface PodPermission {
export interface Dapp {
podPermissions: Record<string, PodPermission>
fullStorageAccess: boolean
accountInfoAccess: boolean
dappId: DappId
}

Expand Down
1 change: 1 addition & 0 deletions src/model/storage/network.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface Network {
fdsRegistrar?: Address
publicResolver?: Address
custom: boolean
blockExplorerUrl?: string
}
3 changes: 2 additions & 1 deletion src/model/storage/wallet.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Address, BigNumberString, HexStringVariate } from '../general.types'
import { Address, BigNumberString, HexString, HexStringVariate } from '../general.types'

export type TransactionDirection = 'sent' | 'received'

Expand All @@ -13,6 +13,7 @@ export interface Transaction {
gas: BigNumberString
gasPrice: BigNumberString
data?: HexStringVariate
hash: HexString<64>
}
token?: Token
}
Expand Down
1 change: 1 addition & 0 deletions src/services/storage/storage-factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function dappFactory(dappId: DappId): Dapp {
return {
podPermissions: {},
fullStorageAccess: false,
accountInfoAccess: false,
dappId,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>(false)
const [closeTimeoutHandle, setCloseTimeoutHandle] = useState<NodeJS.Timeout>(null)

Expand All @@ -34,7 +35,7 @@ const ClipboardButton = ({ text }: ClipboardButtonProps) => {

return (
<>
<IconButton size="large" onClick={onClick} data-testid="copy-btn">
<IconButton onClick={onClick} size={size || 'medium'} data-testid="copy-btn">
<ContentCopy />
</IconButton>
<Snackbar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { Typography } from '@mui/material'

export interface ErrorMessageProps {
children: React.ReactNode
onClick?: React.MouseEventHandler<HTMLSpanElement>
}

const ErrorMessage = ({ children }: ErrorMessageProps) => {
const ErrorMessage = ({ children, onClick }: ErrorMessageProps) => {
return (
<Typography
variant="body1"
Expand All @@ -14,7 +15,9 @@ const ErrorMessage = ({ children }: ErrorMessageProps) => {
sx={{
color: (theme) => theme.palette.error.main,
marginTop: '20px',
cursor: onClick ? 'pointer' : 'auto',
}}
onClick={onClick}
>
{children}
</Typography>
Expand Down
47 changes: 47 additions & 0 deletions src/ui/common/components/error-modal/error-modal.component.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Modal
open={open}
onClose={onClose}
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 'min(90%, 400px)',
backgroundColor: '#fff !important',
boxShadow: 24,
p: 4,
}}
components={{
Backdrop: null,
}}
>
<Box sx={{ height: '80%' }}>
<Typography variant="h6" component="h2">
{intl.get('ERROR_DESCRIPTION')}
</Typography>
<IconButton onClick={onClose} sx={{ position: 'absolute', top: 5, right: 5 }}>
<Close />
</IconButton>
<Typography color="error" sx={{ mt: 2, height: '100%', overflow: 'auto' }}>
{error}
</Typography>
</Box>
</Modal>
)
}

export default ErrorModal
Loading