From 9e0069c9800868ef5942a3d7f2273cd8563c32b8 Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Thu, 5 Dec 2024 02:55:46 +0200 Subject: [PATCH] Added prompts SendTransaction, SignCis3Message, SignMessage Updated MainPage, TokenDetails --- .../browser-wallet/src/assets/svgX/clock.svg | 3 + .../src/assets/svgX/concordium-logo.svg | 4 +- .../src/assets/svgX/percent.svg | 3 + .../src/popup/popupX/constants/routes.ts | 9 + .../ExternalRequestLayout.tsx | 5 +- .../MainLayout/Header/Header.scss | 33 +++- .../Header/components/Connection.tsx | 50 +++++- .../Header/components/MenuTiles.tsx | 4 +- .../page-layouts/MainLayout/Header/i18n/en.ts | 4 + .../popup/popupX/pages/MainPage/MainPage.scss | 11 +- .../popup/popupX/pages/MainPage/MainPage.tsx | 42 +++-- .../popup/popupX/pages/MainPage/i18n/en.ts | 4 +- .../pages/TokenDetails/TokenDetails.scss | 19 +- .../pages/TokenDetails/TokenDetails.tsx | 22 ++- .../pages/TokenDetails/TokenDetailsCcd.tsx | 46 ++--- .../popupX/pages/TokenDetails/i18n/en.ts | 3 +- .../DisplayTransactionPayload.tsx | 159 ++++++++++++++++ .../SendTransaction/SendTransaction.scss | 22 +++ .../SendTransaction/SendTransaction.tsx | 162 +++++++++++++++++ .../pages/prompts/SendTransaction/i18n/en.ts | 31 ++++ .../pages/prompts/SendTransaction/index.ts | 1 + .../SignCis3Message/SignCis3Message.scss | 25 +++ .../SignCis3Message/SignCis3Message.tsx | 169 ++++++++++++++++++ .../pages/prompts/SignCis3Message/i18n/en.ts | 16 ++ .../pages/prompts/SignCis3Message/index.ts | 1 + .../prompts/SignMessage/SignMessage.scss | 14 ++ .../pages/prompts/SignMessage/SignMessage.tsx | 86 +++++++++ .../pages/prompts/SignMessage/i18n/en.ts | 16 ++ .../popupX/pages/prompts/SignMessage/index.ts | 1 + .../shared/BinaryDisplay/BinaryDisplay.scss | 23 +++ .../shared/BinaryDisplay/BinaryDisplay.tsx | 83 +++++++++ .../popupX/shared/BinaryDisplay/index.ts | 1 + .../src/popup/popupX/shared/Card/Card.scss | 2 +- .../src/popup/popupX/shared/Form/Form.scss | 15 +- .../popupX/shared/Form/TextArea/TextArea.tsx | 53 ++++++ .../popupX/shared/Form/TextArea/index.ts | 1 + .../popupX/shared/Parameter/Parameter.scss | 23 +++ .../popupX/shared/Parameter/Parameter.tsx | 16 ++ .../popup/popupX/shared/Parameter/index.ts | 1 + .../popup/popupX/shared/TabBar/TabBar.scss | 60 +++++++ .../src/popup/popupX/shared/TabBar/TabBar.tsx | 33 ++++ .../src/popup/popupX/shared/TabBar/index.ts | 1 + .../src/popup/popupX/shared/Text/Text.tsx | 34 +++- .../src/popup/popupX/shared/i18n/en.ts | 14 ++ .../src/popup/popupX/shell/Routes.tsx | 47 ++++- .../src/popup/popupX/styles/_elements.scss | 6 + .../src/popup/popupX/styles/_typography.scss | 10 +- .../src/popup/shell/i18n/locales/en.ts | 5 +- 48 files changed, 1305 insertions(+), 88 deletions(-) create mode 100644 packages/browser-wallet/src/assets/svgX/clock.svg create mode 100644 packages/browser-wallet/src/assets/svgX/percent.svg create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/SendTransaction.scss create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/SendTransaction.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/i18n/en.ts create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/SignCis3Message.scss create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/SignCis3Message.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/i18n/en.ts create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/SignMessage.scss create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/SignMessage.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/i18n/en.ts create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/BinaryDisplay.scss create mode 100644 packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/BinaryDisplay.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/shared/Form/TextArea/TextArea.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/shared/Form/TextArea/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/shared/Parameter/Parameter.scss create mode 100644 packages/browser-wallet/src/popup/popupX/shared/Parameter/Parameter.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/shared/Parameter/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/shared/TabBar/TabBar.scss create mode 100644 packages/browser-wallet/src/popup/popupX/shared/TabBar/TabBar.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/shared/TabBar/index.ts diff --git a/packages/browser-wallet/src/assets/svgX/clock.svg b/packages/browser-wallet/src/assets/svgX/clock.svg new file mode 100644 index 000000000..abd0138be --- /dev/null +++ b/packages/browser-wallet/src/assets/svgX/clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/browser-wallet/src/assets/svgX/concordium-logo.svg b/packages/browser-wallet/src/assets/svgX/concordium-logo.svg index a030b53fe..0435b9717 100644 --- a/packages/browser-wallet/src/assets/svgX/concordium-logo.svg +++ b/packages/browser-wallet/src/assets/svgX/concordium-logo.svg @@ -1,4 +1,4 @@ - - + + diff --git a/packages/browser-wallet/src/assets/svgX/percent.svg b/packages/browser-wallet/src/assets/svgX/percent.svg new file mode 100644 index 000000000..e52ed7e6c --- /dev/null +++ b/packages/browser-wallet/src/assets/svgX/percent.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/browser-wallet/src/popup/popupX/constants/routes.ts b/packages/browser-wallet/src/popup/popupX/constants/routes.ts index 6e4a9c3ae..4eb53779f 100644 --- a/packages/browser-wallet/src/popup/popupX/constants/routes.ts +++ b/packages/browser-wallet/src/popup/popupX/constants/routes.ts @@ -304,6 +304,15 @@ export const relativeRoutes = { endIdentityIssuance: { path: 'end-identity-issuance', }, + signCIS3Message: { + path: 'signCIS3Message', + }, + signMessage: { + path: 'signMessage', + }, + sendTransaction: { + path: 'sendTransaction', + }, }, }; diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.tsx index 6e13c8cbb..6c6df95f3 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.tsx +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.tsx @@ -2,15 +2,14 @@ import React, { useMemo } from 'react'; import { Outlet } from 'react-router-dom'; import Toast from '@popup/shared/Toast/Toast'; import clsx from 'clsx'; -import { Connection, Fullscreen } from '@popup/popupX/page-layouts/MainLayout/Header/components'; +import { Connection } from '@popup/popupX/page-layouts/MainLayout/Header/components'; import FullscreenPromptLayout from '@popup/popupX/page-layouts/FullscreenPromptLayout'; function Header({ isScrolling }: { isScrolling: boolean }) { return (
- - +
); diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss index 73b6c3b31..f25528d9f 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss @@ -38,7 +38,8 @@ &__fullscreen { display: flex; - width: 100%; + width: 0; + flex: 1 1 0px; &_button { display: flex; @@ -61,9 +62,10 @@ &__connection { display: flex; + flex: 1 1 0px; align-items: center; white-space: nowrap; - width: 100%; + width: 0; height: rem(24px); padding: rem(1px); box-sizing: border-box; @@ -93,6 +95,27 @@ display: flex; align-items: center; + &.waiting { + circle { + fill: $color-orange-info; + } + } + + &.connected { + max-width: 90%; + + circle { + fill: $color-green-success; + } + + .capture__main_small { + color: $color-white; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + .capture__main_small { padding: 0 rem(10px) rem(2px) rem(6px); } @@ -143,6 +166,12 @@ &.wide { width: rem(160px); } + + svg { + path { + fill: $color-white; + } + } } } } diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/Connection.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/Connection.tsx index 68d7e519d..5f26d6231 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/Connection.tsx +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/Connection.tsx @@ -1,19 +1,61 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import Info from '@assets/svgX/info.svg'; import Dot from '@assets/svgX/dot.svg'; +import { useAtomValue } from 'jotai'; +import { storedAllowlistAtom } from '@popup/store/account'; +import { useCurrentOpenTabUrl } from '@popup/shared/utils/tabs'; +import { useSelectedCredential } from '@popup/shared/utils/account-helpers'; +import { useTranslation } from 'react-i18next'; +import clsx from 'clsx'; +import { Location, useLocation } from 'react-router-dom'; +import { relativeRoutes } from '@popup/popupX/constants/routes'; +import Text from '@popup/popupX/shared/Text'; type Props = { - hideConnection: boolean; + hideConnection?: boolean; }; +interface ConnectionLocation extends Location { + state: { + payload: { + url: string; + }; + }; +} + export default function Connection({ hideConnection }: Props) { + const { t } = useTranslation('x', { keyPrefix: 'header.connection' }); + const [isAccountConnectedToSite, setAccountConnectedToSite] = useState(); + const allowlist = useAtomValue(storedAllowlistAtom); + const { pathname, state } = useLocation() as ConnectionLocation; + const currentOpenTabUrl = useCurrentOpenTabUrl(); + const url = state?.payload?.url ? new URL(state.payload.url).origin : currentOpenTabUrl; + const selectedCred = useSelectedCredential(); + const accountAddress = selectedCred?.address; + const hostname = url ? new URL(url).hostname : ''; + const waitingForConnection = pathname.includes(relativeRoutes.prompt.connectionRequest.path); + const connectionText = + (waitingForConnection && t('waiting')) || (isAccountConnectedToSite && hostname) || t('siteNotConnected'); + + useEffect(() => { + if (accountAddress && !allowlist.loading && url) { + const allowlistForUrl = allowlist.value[url]; + setAccountConnectedToSite(allowlistForUrl?.includes(accountAddress) ?? false); + } + }, [accountAddress, allowlist, url]); + if (hideConnection) return null; return (
- + - No website connected + {connectionText}
diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuTiles.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuTiles.tsx index df9f6e9d9..a89d38dd5 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuTiles.tsx +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuTiles.tsx @@ -4,7 +4,7 @@ import Browsers from '@assets/svgX/browsers.svg'; import TextColumns from '@assets/svgX/text-columns.svg'; import Password from '@assets/svgX/password.svg'; import WebId from '@assets/svgX/web-id.svg'; -import Plant from '@assets/svgX/plant.svg'; +import Percent from '@assets/svgX/percent.svg'; import LinkSimple from '@assets/svgX/link-simple-horizontal.svg'; import Info from '@assets/svgX/info2.svg'; import Restore from '@assets/svgX/arrow-counter-clock.svg'; @@ -69,7 +69,7 @@ export default function MenuTiles({ menuOpen, setMenuOpen }: MenuTilesProps) { - + {t('earn')} diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/i18n/en.ts index 09cbbd344..45c4f9e96 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/i18n/en.ts +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/i18n/en.ts @@ -17,6 +17,10 @@ const t = { sortDesc: 'Sort Z-A', searchBy: 'Search by name or address', }, + connection: { + siteNotConnected: 'No website connected', + waiting: 'Waiting', + }, }; export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/MainPage/MainPage.scss b/packages/browser-wallet/src/popup/popupX/pages/MainPage/MainPage.scss index 677fc32ba..62b5ce2d0 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/MainPage/MainPage.scss +++ b/packages/browser-wallet/src/popup/popupX/pages/MainPage/MainPage.scss @@ -7,6 +7,7 @@ &__balance { display: flex; flex-direction: column; + align-items: flex-start; } &__action-buttons { @@ -71,6 +72,9 @@ &__amount, &__exchange-rate { display: flex; + width: 100%; + justify-content: flex-end; + align-items: center; } .capture__main_small { @@ -81,14 +85,15 @@ color: $color-white; } - .label__main:last-child, - .capture__main_small:last-child { + .balance-rate { + display: flex; + flex-direction: column; + gap: rem(8px); margin-left: auto; } svg { margin-left: rem(8px); - margin-top: rem(-2px); opacity: 0.5; width: rem(16px); height: rem(16px); diff --git a/packages/browser-wallet/src/popup/popupX/pages/MainPage/MainPage.tsx b/packages/browser-wallet/src/popup/popupX/pages/MainPage/MainPage.tsx index 55c8eed00..00815fb1b 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/MainPage/MainPage.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/MainPage/MainPage.tsx @@ -17,9 +17,9 @@ import Text from '@popup/popupX/shared/Text'; import Button from '@popup/popupX/shared/Button'; import { withSelectedCredential } from '@popup/popupX/shared/utils/hoc'; import Arrow from '@assets/svgX/arrow-right.svg'; -import FileText from '@assets/svgX/file-text.svg'; +import Clock from '@assets/svgX/clock.svg'; import ConcordiumLogo from '@assets/svgX/concordium-logo.svg'; -import Plant from '@assets/svgX/plant.svg'; +import Percent from '@assets/svgX/percent.svg'; import Gear from '@assets/svgX/gear.svg'; import { formatTokenAmount } from '@popup/popupX/shared/utils/helpers'; @@ -59,8 +59,7 @@ function displayCcdAsEur(microCcdPerEur: Ratio, microCcd: bigint, decimals: numb } function Balance({ credential }: { credential: WalletCredential }) { - const chainParameters = useBlockChainParameters(); - const microCcdPerEur = chainParameters?.microGTUPerEuro; + const { t } = useTranslation('x', { keyPrefix: 'mainPage' }); const accountInfo = useAccountInfo(credential); if (!accountInfo) { @@ -68,13 +67,16 @@ function Balance({ credential }: { credential: WalletCredential }) { } const ccdBalance = displayAsCcd(accountInfo.accountAmount.microCcdAmount, false, true); - const eurBalance = - microCcdPerEur && displayCcdAsEur(microCcdPerEur, accountInfo.accountAmount.microCcdAmount, 2, true); + const ccdAvailableBalance = displayAsCcd(accountInfo.accountAvailableBalance.microCcdAmount, false, true); return (
- {microCcdPerEur ? eurBalance : ccdBalance} - {microCcdPerEur ? ccdBalance : ''} + + {ccdBalance} + + + {ccdAvailableBalance} {t('atDisposal')} +
); } @@ -98,15 +100,16 @@ function TokenItem({ thumbnail, symbol, balance, balanceBase, staked, microCcdPe
{symbol} - {staked && } - {balance} + {staked && } + + {balance} + {isNoExchange ? null : ( +
+ {displayCcdAsEur(microCcdPerEur, balanceBase, 2)} +
+ )} +
- {isNoExchange ? null : ( -
- {displayCcdAsEur(microCcdPerEur, 1000000n, 6)} - {displayCcdAsEur(microCcdPerEur, balanceBase, 2)} -
- )}
); @@ -120,6 +123,7 @@ function MainPageConfirmedAccount({ credential }: MainPageConfirmedAccountProps) const nav = useNavigate(); const navToSend = () => nav(generatePath(absoluteRoutes.home.sendFunds.path, { account: credential.address })); const navToReceive = () => nav(relativeRoutes.home.receive.path); + const navToEarn = () => nav(absoluteRoutes.settings.earn.path); const navToTransactionLog = () => nav(relativeRoutes.home.transactionLog.path.replace(':account', credential.address)); const navToTokenDetails = (contractIndex: string) => @@ -144,7 +148,8 @@ function MainPageConfirmedAccount({ credential }: MainPageConfirmedAccountProps)
} label={t('receive')} onClick={navToReceive} className="receive" /> } label={t('send')} onClick={navToSend} className="send" /> - } label={t('transactions')} onClick={navToTransactionLog} /> + } label={t('earn')} onClick={navToEarn} /> + } label={t('activity')} onClick={navToTransactionLog} />
@@ -192,7 +197,8 @@ function MainPagePendingAccount() {
} label={t('receive')} disabled className="receive" /> } label={t('send')} disabled className="send" /> - } label={t('transactions')} disabled /> + } label={t('earn')} disabled /> + } label={t('activity')} disabled />
diff --git a/packages/browser-wallet/src/popup/popupX/pages/MainPage/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/MainPage/i18n/en.ts index 12665aea0..2b3f0e4c1 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/MainPage/i18n/en.ts +++ b/packages/browser-wallet/src/popup/popupX/pages/MainPage/i18n/en.ts @@ -1,10 +1,12 @@ const t = { receive: 'Receive', send: 'Send', - transactions: 'Transactions', + earn: 'Earn', + activity: 'Activity', manageTokenList: 'Manage token list', pendingAccount: 'Creating account', pendingSubText: 'Ready within a few minutes', + atDisposal: 'at disposal', }; export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetails.scss b/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetails.scss index dcbd201eb..ff306e9da 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetails.scss +++ b/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetails.scss @@ -1,15 +1,15 @@ .token-details-x { &__stake { display: flex; - justify-content: space-between; + flex-direction: column; margin-top: rem(12px); margin-bottom: rem(16px); padding-top: rem(12px); - border-top: 1px solid rgba($color-white, 0.1); &_group { display: flex; - flex-direction: column; + flex-direction: row; + justify-content: space-between; .capture__additional_small { color: $color-white; @@ -33,6 +33,19 @@ } } + &__token { + display: flex; + align-items: center; + margin-bottom: rem(8px); + + .token-icon, + svg { + width: rem(20px); + height: rem(20px); + margin-right: rem(8px); + } + } + .card-x { margin-top: rem(16px); } diff --git a/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetails.tsx b/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetails.tsx index bacdaeee8..ea451f365 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetails.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetails.tsx @@ -15,10 +15,11 @@ import { getMetadataDecimals, trunctateSymbol } from '@shared/utils/token-helper import { useUpdateAtom } from 'jotai/utils'; import { WalletCredential } from '@shared/storage/types'; import Arrow from '@assets/svgX/arrow-right.svg'; -import FileText from '@assets/svgX/file-text.svg'; +import Clock from '@assets/svgX/clock.svg'; import Notebook from '@assets/svgX/notebook.svg'; import Eye from '@assets/svgX/eye-slash.svg'; import { AccountAddress, ContractAddress } from '@concordium/web-sdk'; +import Img from '@popup/shared/Img'; import { SendFundsLocationState } from '../SendFunds/SendFunds'; const SUB_INDEX = '0'; @@ -57,9 +58,9 @@ function TokenDetails({ credential }: { credential: WalletCredential }) { return ( - + {renderBalance(balance)} {trunctateSymbol(metadata.symbol || '')} - +
} @@ -68,13 +69,18 @@ function TokenDetails({ credential }: { credential: WalletCredential }) { className="receive" /> } label={t('send')} onClick={() => navToSend()} className="send" /> - } - label={t('transactions')} - onClick={() => navToTransactionLog()} - /> + } label={t('activity')} onClick={() => navToTransactionLog()} />
+
+ {metadata.symbol} + {metadata.name} +
diff --git a/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetailsCcd.tsx b/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetailsCcd.tsx index a851b9269..eb0363b8e 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetailsCcd.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/TokenDetailsCcd.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { AccountAddress, AccountInfoType } from '@concordium/web-sdk'; +import { AccountAddress } from '@concordium/web-sdk'; import { relativeRoutes, absoluteRoutes, sendFundsRoute } from '@popup/popupX/constants/routes'; import Page from '@popup/popupX/shared/Page'; import Text from '@popup/popupX/shared/Text'; @@ -12,8 +12,9 @@ import { displayAsCcd, getPublicAccountAmounts, PublicAccountAmounts } from 'wal import { WalletCredential } from '@shared/storage/types'; import { withSelectedCredential } from '@popup/popupX/shared/utils/hoc'; import Arrow from '@assets/svgX/arrow-right.svg'; -import FileText from '@assets/svgX/file-text.svg'; -import Plant from '@assets/svgX/plant.svg'; +import Clock from '@assets/svgX/clock.svg'; +import Percent from '@assets/svgX/percent.svg'; +import ConcordiumLogo from '@assets/svgX/concordium-logo.svg'; import { TokenPickerVariant } from '@popup/popupX/shared/Form/TokenAmount/View'; const zeroBalance: Omit = { @@ -24,7 +25,6 @@ const zeroBalance: Omit = { }; function useCcdInfo(credential: WalletCredential) { - const { t } = useTranslation('x', { keyPrefix: 'tokenDetails' }); const [balances, setBalances] = useState>(zeroBalance); const accountInfo = useAccountInfo(credential); @@ -36,21 +36,13 @@ function useCcdInfo(credential: WalletCredential) { } }, [accountInfo]); - type AccountTypeMap = { [TYPE in AccountInfoType]: string }; - const tokenDetails = useMemo(() => { if (accountInfo) { - const type: AccountTypeMap = { - [AccountInfoType.Delegator]: t('delegated'), - [AccountInfoType.Baker]: t('validated'), - [AccountInfoType.Simple]: '', - }; return { total: displayAsCcd(balances.total, false, true), atDisposal: displayAsCcd(balances.atDisposal, false, true), staked: displayAsCcd(balances.staked, false, true), cooldown: displayAsCcd(balances.cooldown, false, true), - type: type[accountInfo.type], }; } return { total: null, atDisposal: null }; @@ -76,22 +68,22 @@ function TokenDetailsCcd({ credential }: { credential: WalletCredential }) { return ( - {tokenDetails.total} + + {tokenDetails.total} +
- {t('atDisposal')} - {tokenDetails.atDisposal} + {t('earning')} + {tokenDetails.staked}
- {tokenDetails.type && ( -
- {tokenDetails.type} - {tokenDetails.staked} -
- )}
{t('cooldown')} {tokenDetails.cooldown}
+
+ {t('atDisposal')} + {tokenDetails.atDisposal} +
} label={t('send')} onClick={() => navToSend()} className="send" /> - } - label={t('transactions')} - onClick={() => navToTransactionLog()} - /> - } label={t('earn')} onClick={() => navToEarn()} /> + } label={t('earn')} onClick={() => navToEarn()} /> + } label={t('activity')} onClick={() => navToTransactionLog()} />
+
+ + CCD +
diff --git a/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/i18n/en.ts index 3d48c69d3..4875f6848 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/i18n/en.ts +++ b/packages/browser-wallet/src/popup/popupX/pages/TokenDetails/i18n/en.ts @@ -1,8 +1,9 @@ const t = { receive: 'Receive', send: 'Send', - transactions: 'Transactions', + activity: 'Activity', earn: 'Earn', + earning: 'Earning', description: 'Description', decimals: 'Decimals', indexSubindex: 'Contract index, subindex', diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload.tsx new file mode 100644 index 000000000..84678750a --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload.tsx @@ -0,0 +1,159 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { + AccountTransactionPayload, + AccountTransactionType, + CcdAmount, + DeployModulePayload, + InitContractPayload, + RegisterDataPayload, + sha256, + SimpleTransferPayload, + UpdateContractPayload, +} from '@concordium/web-sdk'; +import { SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; +import { useTranslation } from 'react-i18next'; +import { chunkString, displayAsCcd } from 'wallet-common-helpers'; +import * as JSONBig from 'json-bigint'; +import { decode } from 'cbor'; +import Card from '@popup/popupX/shared/Card'; +import Parameter from '@popup/popupX/shared/Parameter'; + +export function DisplayParameters({ parameters }: { parameters?: SmartContractParameters }) { + const hasParameters = parameters !== undefined && parameters !== null; + if (!hasParameters) return null; + return ; +} + +/** + * Displays an overview of a simple transfer. + */ +function DisplaySimpleTransfer({ payload }: { payload: SimpleTransferPayload }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + return ( + <> + + + + ); +} + +/** + * Displays an overview of a update contract transaction. + */ +function DisplayUpdateContract({ payload }: { payload: Omit }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + return ( + <> + + + + + + ); +} + +/** + * Displays an overview of a init contract transaction. + */ +function DisplayInitContract({ payload }: { payload: Omit }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + + return ( + <> + + + + + + ); +} + +/** + * Displays an overview of a register data. + */ +function DisplayRegisterData({ payload }: { payload: RegisterDataPayload }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + const [decoded, setDecoded] = useState(); + + useEffect(() => { + try { + setDecoded(decode(payload.data.data)); + } catch { + // display raw if unable to decode + } + }, []); + + const title = `${t('data')}${!decoded ? t('rawData') : ''}`; + const value = decoded || payload.data.toJSON(); + + return ; +} + +/** + * Displays an overview of a deploy module transaction. + */ +function DisplayDeployModule({ payload }: { payload: DeployModulePayload }) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX.payload' }); + const hash = useMemo(() => sha256([payload.source]).toString('hex'), []); + const { version } = payload; + + return ( + <> + {version && } + + + ); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function displayValue(value: any) { + if (CcdAmount.instanceOf(value)) { + return displayAsCcd(value.microCcdAmount); + } + return value.toString(); +} + +/** + * Displays an overview of any transaction payload. + */ +function DisplayGenericPayload({ payload }: { payload: AccountTransactionPayload }) { + return ( + <> + {Object.entries(payload).map(([key, value]) => ( + + ))} + + ); +} + +export default function DisplayTransactionPayload({ + payload, + type, +}: { + type: AccountTransactionType; + payload: AccountTransactionPayload; +}) { + switch (type) { + case AccountTransactionType.Transfer: + return ; + case AccountTransactionType.Update: + return ; + case AccountTransactionType.InitContract: + return ; + case AccountTransactionType.RegisterData: + return ; + case AccountTransactionType.DeployModule: + return ; + default: + return ; + } +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/SendTransaction.scss b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/SendTransaction.scss new file mode 100644 index 000000000..3e86d406f --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/SendTransaction.scss @@ -0,0 +1,22 @@ +.send-transaction-x { + .text__main { + color: $color-mineral-2; + + &_medium { + color: $color-white; + } + + .white { + color: $color-white; + } + } + + .card-x.grey { + margin-top: rem(16px); + margin-bottom: rem(32px); + + .row.details:has(+ .parameter-x) { + border: unset; + } + } +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/SendTransaction.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/SendTransaction.tsx new file mode 100644 index 000000000..72d925429 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/SendTransaction.tsx @@ -0,0 +1,162 @@ +import React, { useCallback, useContext, useEffect, useMemo } from 'react'; +import { BackgroundSendTransactionPayload } from '@shared/utils/types'; +import { useLocation } from 'react-router-dom'; +import { Trans, useTranslation } from 'react-i18next'; +import { useAtomValue, useSetAtom } from 'jotai'; +import { addToastAtom } from '@popup/state'; +import { grpcClientAtom } from '@popup/store/settings'; +import { fullscreenPromptContext } from '@popup/popupX/page-layouts/FullscreenPromptLayout'; +import { useUpdateAtom } from 'jotai/utils'; +import { addPendingTransactionAtom } from '@popup/store/transactions'; +import { useBlockChainParameters } from '@popup/shared/BlockChainParametersProvider'; +import { usePrivateKey } from '@popup/shared/utils/account-helpers'; +import { parsePayload } from '@shared/utils/payload-helpers'; +import * as JSONBig from 'json-bigint'; +import { SmartContractParameters } from '@concordium/browser-wallet-api-helpers'; +import { convertEnergyToMicroCcd, getEnergyCost } from '@shared/utils/energy-helpers'; +import { AccountAddress } from '@concordium/web-sdk'; +import { displayAsCcd, getPublicAccountAmounts } from 'wallet-common-helpers'; +import { + createPendingTransactionFromAccountTransaction, + getDefaultExpiry, + getTransactionAmount, + getTransactionTypeName, + sendTransaction, +} from '@popup/shared/utils/transaction-helpers'; +import Page from '@popup/popupX/shared/Page'; +import Text from '@popup/popupX/shared/Text'; +import { displayUrl } from '@popup/shared/utils/string-helpers'; +import Button from '@popup/popupX/shared/Button'; +import Card from '@popup/popupX/shared/Card'; +import DisplayTransactionPayload, { + DisplayParameters, +} from '@popup/popupX/pages/prompts/SendTransaction/DisplayTransactionPayload'; + +interface Location { + state: { + payload: BackgroundSendTransactionPayload; + }; +} + +interface Props { + onSubmit(hash: string): void; + onReject(): void; +} + +export default function SendTransaction({ onSubmit, onReject }: Props) { + const { state } = useLocation() as Location; + const { t } = useTranslation('x', { keyPrefix: 'prompts.sendTransactionX' }); + const addToast = useSetAtom(addToastAtom); + const client = useAtomValue(grpcClientAtom); + const { withClose, onClose } = useContext(fullscreenPromptContext); + const addPendingTransaction = useUpdateAtom(addPendingTransactionAtom); + const chainParameters = useBlockChainParameters(); + + const { accountAddress, url } = state.payload; + const key = usePrivateKey(accountAddress); + + const { type: transactionType, payload } = useMemo( + () => + parsePayload( + state.payload.type, + state.payload.payload, + state.payload.parameters, + state.payload.schema, + state.payload.schemaVersion + ), + [JSON.stringify(state.payload)] + ); + const parameters = useMemo( + () => + state.payload.parameters === undefined + ? undefined + : (JSONBig.parse(state.payload.parameters) as SmartContractParameters), + [state.payload.parameters] + ); + + const cost = useMemo(() => { + if (chainParameters) { + const energy = getEnergyCost(transactionType, payload); + return convertEnergyToMicroCcd(energy, chainParameters); + } + return undefined; + }, [transactionType, chainParameters]); + + useEffect(() => onClose(onReject), [onClose, onReject]); + + const handleSubmit = useCallback(async () => { + if (!accountAddress) { + throw new Error(t('errors.missingAccount')); + } + if (!key) { + throw new Error(t('errors.missingKey')); + } + + const sender = AccountAddress.fromBase58(accountAddress); + const accountInfo = await client.getAccountInfo(sender); + if ( + getPublicAccountAmounts(accountInfo).atDisposal < + getTransactionAmount(transactionType, payload) + (cost || 0n) + ) { + throw new Error(t('errors.insufficientFunds')); + } + + const nonce = await client.getNextAccountNonce(sender); + + if (!nonce) { + throw new Error(t('errors.missingNonce')); + } + + const header = { + expiry: getDefaultExpiry(), + sender, + nonce: nonce.nonce, + }; + const transaction = { payload, header, type: transactionType }; + + const hash = await sendTransaction(client, transaction, key); + const pending = createPendingTransactionFromAccountTransaction(transaction, hash, cost); + await addPendingTransaction(pending); + + return hash; + }, [payload, key, cost]); + + return ( + + + + + }} + values={{ dApp: displayUrl(url) }} + /> + + + + {getTransactionTypeName(transactionType)} + + + + + + + + + + { + handleSubmit() + .then(withClose(onSubmit)) + .catch((e) => addToast(e.message)); + }} + /> + + + ); +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/i18n/en.ts new file mode 100644 index 000000000..7cbc8d215 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/i18n/en.ts @@ -0,0 +1,31 @@ +const t = { + reject: 'Reject', + sign: 'Sign & Submit', + signTransaction: '<1>{{dApp}} suggests a transaction', + signRequest: 'Signature request', + errors: { + missingAccount: 'Missing account address', + missingKey: 'Missing key for the chosen address', + insufficientFunds: 'Account has insufficient funds for the transaction', + missingNonce: 'No nonce was found for the chosen account', + }, + payload: { + amount: 'Amount', + receiver: 'Receiver', + contractIndex: 'Contract index (subindex)', + receiveName: 'Contract and function name', + maxEnergy: 'Max energy allowed', + nrg: 'NRG', + noParameter: 'No parameters', + sender: 'Sender account', + cost: 'Estimated transaction fee', + unknown: 'Unknown', + moduleReference: 'Module reference', + contractName: 'Contract name', + data: 'Data', + rawData: ': (Unable to be decoded)', + version: 'Version', + }, +}; + +export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/index.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/index.ts new file mode 100644 index 000000000..152d81622 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SendTransaction/index.ts @@ -0,0 +1 @@ +export { default } from './SendTransaction'; diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/SignCis3Message.scss b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/SignCis3Message.scss new file mode 100644 index 000000000..1ae85a917 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/SignCis3Message.scss @@ -0,0 +1,25 @@ +.sign-cis3-x { + .page__main { + .text__main { + margin-bottom: rem(24px); + } + + .text__main, + .capture__main_small { + color: $color-mineral-2; + + .white { + color: $color-white; + } + } + + .card-x { + margin-top: rem(16px); + margin-bottom: rem(32px); + + .row.details:has(+ .parameter-x) { + border: unset; + } + } + } +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/SignCis3Message.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/SignCis3Message.tsx new file mode 100644 index 000000000..60da6e618 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/SignCis3Message.tsx @@ -0,0 +1,169 @@ +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useSetAtom } from 'jotai'; +import Page from '@popup/popupX/shared/Page'; +import Text from '@popup/popupX/shared/Text'; +import Button from '@popup/popupX/shared/Button'; +import { usePrivateKey } from '@popup/shared/utils/account-helpers'; +import { fullscreenPromptContext } from '@popup/popupX/page-layouts/FullscreenPromptLayout'; +import Card from '@popup/popupX/shared/Card'; +import { + AccountAddress, + AccountTransactionSignature, + buildBasicAccountSigner, + ContractAddress, + ContractName, + deserializeTypeValue, + EntrypointName, + serializeTypeValue, + signMessage, +} from '@concordium/web-sdk'; +import { addToastAtom } from '@popup/state'; +import { useLocation } from 'react-router-dom'; +import { SignMessageObject } from '@concordium/browser-wallet-api-helpers'; +import { Buffer } from 'buffer'; +import { stringify } from 'json-bigint'; +import Parameter from '@popup/popupX/shared/Parameter'; +import { displayUrl } from '@popup/shared/utils/string-helpers'; + +const SERIALIZATION_HELPER_SCHEMA = + 'FAAFAAAAEAAAAGNvbnRyYWN0X2FkZHJlc3MMBQAAAG5vbmNlBQkAAAB0aW1lc3RhbXANCwAAAGVudHJ5X3BvaW50FgEHAAAAcGF5bG9hZBABAg=='; + +async function parseMessage(message: SignMessageObject) { + return stringify( + deserializeTypeValue(Buffer.from(message.data, 'hex'), Buffer.from(message.schema, 'base64')), + undefined, + 2 + ); +} + +function useMessageDetails({ + payloadMessage, + cis3ContractDetails, +}: { + payloadMessage: SignMessageObject; + cis3ContractDetails: Cis3ContractDetailsObject; +}) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.signCis3' }); + const { contractAddress, contractName, entrypointName, nonce, expiryTimeSignature } = cis3ContractDetails; + const [parsedMessage, setParsedMessage] = useState(''); + const expiry = new Date(expiryTimeSignature).toString(); + + useEffect(() => { + parseMessage(payloadMessage) + .then((m) => setParsedMessage(m)) + .catch(() => setParsedMessage(t('unableToDeserialize'))); + }, []); + + return { + mainDetails: [ + [t('contractIndex'), `${contractAddress.index.toString()} (${contractAddress.subindex.toString()})`], + [t('receiveName'), `${contractName.value.toString()}.${entrypointName.value.toString()}`], + [t('nonce'), nonce.toString()], + [t('expiry'), expiry], + ], + parsedMessage, + }; +} + +function serializeMessage(payloadMessage: SignMessageObject, cis3ContractDetails: Cis3ContractDetailsObject) { + const { contractAddress, entrypointName, nonce, expiryTimeSignature } = cis3ContractDetails; + const message = { + contract_address: { + index: Number(contractAddress.index), + subindex: Number(contractAddress.subindex), + }, + nonce: Number(nonce), + timestamp: expiryTimeSignature, + entry_point: EntrypointName.toString(entrypointName), + payload: Array.from(Buffer.from(payloadMessage.data, 'hex')), + }; + + return serializeTypeValue(message, Buffer.from(SERIALIZATION_HELPER_SCHEMA, 'base64')); +} + +interface Location { + state: { + payload: { + accountAddress: string; + message: SignMessageObject; + url: string; + cis3ContractDetails: Cis3ContractDetailsObject; + }; + }; +} + +type Cis3ContractDetailsObject = { + contractAddress: ContractAddress.Type; + contractName: ContractName.Type; + entrypointName: EntrypointName.Type; + nonce: bigint | number; + expiryTimeSignature: string; +}; + +type Props = { + onSubmit(signature: AccountTransactionSignature): void; + onReject(): void; +}; + +export default function SignCis3Message({ onSubmit, onReject }: Props) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.signCis3' }); + const { state } = useLocation() as Location; + const { withClose } = useContext(fullscreenPromptContext); + const { accountAddress, message, url, cis3ContractDetails } = state.payload; + const { mainDetails, parsedMessage } = useMessageDetails({ payloadMessage: message, cis3ContractDetails }); + const key = usePrivateKey(accountAddress); + const addToast = useSetAtom(addToastAtom); + const onClick = useCallback(async () => { + if (!key) { + throw new Error('Missing key for the chosen address'); + } + + return signMessage( + AccountAddress.fromBase58(accountAddress), + serializeMessage(message, cis3ContractDetails).buffer, + buildBasicAccountSigner(key) + ); + }, [state.payload.message, state.payload.accountAddress, key]); + + return ( + + + + + }} + values={{ dApp: displayUrl(url) }} + /> + + + }} + values={{ dApp: displayUrl(url) }} + /> + + + {mainDetails.map(([title, value]) => ( + + ))} + + + + + + { + onClick() + .then(withClose(onSubmit)) + .catch((e) => addToast(e.message)); + }} + /> + + + ); +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/i18n/en.ts new file mode 100644 index 000000000..d660cc96d --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/i18n/en.ts @@ -0,0 +1,16 @@ +const t = { + signRequest: 'Signature request', + signTransaction: '<1>{{dApp}} requests a signature on a message', + connectionDetails: + "<1>{{dApp}} has provided the raw message and a schema to render it. We've rendered the message but you should only sign it if you trust <1>{{dApp}}", + unableToDeserialize: 'Unable to render message', + contractIndex: 'Contract index (subindex)', + receiveName: 'Contract and function name', + parameter: 'Parameter', + nonce: 'Nonce', + expiry: 'Expiry time', + reject: 'Reject', + sign: 'Sign & Submit', +}; + +export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/index.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/index.ts new file mode 100644 index 000000000..68f93d061 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignCis3Message/index.ts @@ -0,0 +1 @@ +export { default } from './SignCis3Message'; diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/SignMessage.scss b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/SignMessage.scss new file mode 100644 index 000000000..26f6f75c5 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/SignMessage.scss @@ -0,0 +1,14 @@ +.sign-message-x { + .text__main { + color: $color-mineral-2; + margin-bottom: rem(24px); + + .white { + color: $color-white; + } + } + + .binary-display-x { + margin-bottom: rem(24px); + } +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/SignMessage.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/SignMessage.tsx new file mode 100644 index 000000000..9e3487fbd --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/SignMessage.tsx @@ -0,0 +1,86 @@ +import { Trans, useTranslation } from 'react-i18next'; +import { useLocation } from 'react-router-dom'; +import React, { useCallback, useContext } from 'react'; +import { fullscreenPromptContext } from '@popup/popupX/page-layouts/FullscreenPromptLayout'; +import { usePrivateKey } from '@popup/shared/utils/account-helpers'; +import { useSetAtom } from 'jotai'; +import { addToastAtom } from '@popup/state'; +import { AccountAddress, AccountTransactionSignature, buildBasicAccountSigner, signMessage } from '@concordium/web-sdk'; +import Page from '@popup/popupX/shared/Page'; +import Text from '@popup/popupX/shared/Text'; +import Button from '@popup/popupX/shared/Button'; +import { Buffer } from 'buffer'; +import { displayUrl } from '@popup/shared/utils/string-helpers'; +import BinaryDisplay from '@popup/popupX/shared/BinaryDisplay'; + +type Props = { + onSubmit(signature: AccountTransactionSignature): void; + onReject(): void; +}; + +interface Location { + state: { + payload: { + accountAddress: string; + message: string | MessageObject; + url: string; + }; + }; +} + +type MessageObject = { + schema: string; + data: string; +}; + +export default function SignMessage({ onSubmit, onReject }: Props) { + const { state } = useLocation() as Location; + const { t } = useTranslation('x', { keyPrefix: 'prompts.signMessageX' }); + const { withClose } = useContext(fullscreenPromptContext); + const { accountAddress, url } = state.payload; + const key = usePrivateKey(accountAddress); + const addToast = useSetAtom(addToastAtom); + const { message } = state.payload; + const messageIsAString = typeof message === 'string'; + + const onClick = useCallback(async () => { + if (!key) { + throw new Error('Missing key for the chosen address'); + } + + return signMessage( + AccountAddress.fromBase58(accountAddress), + messageIsAString ? message : Buffer.from(message.data, 'hex'), + buildBasicAccountSigner(key) + ); + }, [state.payload.message, state.payload.accountAddress, key]); + + return ( + + + + + }} + values={{ dApp: displayUrl(url) }} + /> + + + + + + + { + onClick() + .then(withClose(onSubmit)) + .catch((e) => addToast(e.message)); + }} + /> + + + ); +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/i18n/en.ts new file mode 100644 index 000000000..d660cc96d --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/i18n/en.ts @@ -0,0 +1,16 @@ +const t = { + signRequest: 'Signature request', + signTransaction: '<1>{{dApp}} requests a signature on a message', + connectionDetails: + "<1>{{dApp}} has provided the raw message and a schema to render it. We've rendered the message but you should only sign it if you trust <1>{{dApp}}", + unableToDeserialize: 'Unable to render message', + contractIndex: 'Contract index (subindex)', + receiveName: 'Contract and function name', + parameter: 'Parameter', + nonce: 'Nonce', + expiry: 'Expiry time', + reject: 'Reject', + sign: 'Sign & Submit', +}; + +export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/index.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/index.ts new file mode 100644 index 000000000..e6f80df6f --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/SignMessage/index.ts @@ -0,0 +1 @@ +export { default } from './SignMessage'; diff --git a/packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/BinaryDisplay.scss b/packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/BinaryDisplay.scss new file mode 100644 index 000000000..091e86e7c --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/BinaryDisplay.scss @@ -0,0 +1,23 @@ +.binary-display-x { + .tab-bar { + margin-top: rem(16px); + } + + .white { + color: $color-white; + } + + .sign-message { + &__binary-text-area { + border: 1px solid $color-input-border; + border-top: unset; + border-radius: 0 0 rem(12px) rem(12px); + background: $color-transaction-bg; + + .form-input__area { + min-height: rem(165px); + font-size: rem(12px); + } + } + } +} diff --git a/packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/BinaryDisplay.tsx b/packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/BinaryDisplay.tsx new file mode 100644 index 000000000..28d2abf1a --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/shared/BinaryDisplay/BinaryDisplay.tsx @@ -0,0 +1,83 @@ +import React, { useMemo, useState } from 'react'; +import Text from '@popup/popupX/shared/Text'; +import { Trans, useTranslation } from 'react-i18next'; +import { TextArea } from '@popup/popupX/shared/Form/TextArea'; +import { stringify } from 'json-bigint'; +import { deserializeTypeValue } from '@concordium/web-sdk'; +import { Buffer } from 'buffer'; +import { displayUrl } from '@popup/shared/utils/string-helpers'; +import TabBar from '@popup/popupX/shared/TabBar'; +import clsx from 'clsx'; +import Button from '@popup/popupX/shared/Button'; + +type MessageObject = { + schema: string; + data: string; +}; + +function StringRender({ message }: { message: string }) { + return ( +
+