From 458e9cafc3ea0933d9b9670d6c9c64fb9211a44e Mon Sep 17 00:00:00 2001 From: Joe Tsang <30622993+jtsang586@users.noreply.github.com> Date: Tue, 7 Jun 2022 13:08:24 +0100 Subject: [PATCH 01/27] fix: increase timeout (#513) --- apps/trading-e2e/src/support/pages/base-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/trading-e2e/src/support/pages/base-page.ts b/apps/trading-e2e/src/support/pages/base-page.ts index 85aeebc68..084ad09ef 100644 --- a/apps/trading-e2e/src/support/pages/base-page.ts +++ b/apps/trading-e2e/src/support/pages/base-page.ts @@ -10,7 +10,7 @@ export default class BasePage { dialogText = 'dialog-text'; closeDialog() { - cy.getByTestId(this.closeDialogBtn, { timeout: 8000 })?.click({ + cy.getByTestId(this.closeDialogBtn, { timeout: 12000 })?.click({ force: true, }); } From a66be425be297f86e237e834ab7f56e52e0dd705 Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Tue, 7 Jun 2022 11:24:43 -0700 Subject: [PATCH 02/27] fix(#315): misc styling and ui fixes for the token app * chore: add callout loaders and input lozenges * fix: text colors and nav heading * fix: text color for home links * chore: fix spacing of wallets * chore: fix missing translation keys * chore: add loader to pending associatino tx callout, fix spacing of text within callout * chore: make sure etherscan links open in a new tab * fix: redemption page * fix: spacing of rewards tables list * fix: link styles on withdraw page * fix: styles for withdrawal table * fix: footer links * fix: staking page links and spacing * fix: translations * fix: spacing of callout title, spacing of staking connect step * fix: vesting page title * fix: proposals list spacing * fix: proposal page and vote details * chore: update translation of metamask wallet connection button * chore: delete unused files * chore: dont nest buttons inside links * chore: lint * fix: title test after text change --- apps/token-e2e/src/integration/app.test.ts | 2 +- apps/token/src/app.tsx | 9 +- .../src/components/app-footer/app-footer.tsx | 9 +- .../bullet-header/bullet-header.tsx | 2 +- .../eth-wallet-container.tsx | 39 ----- .../components/eth-wallet-container/index.ts | 1 - .../src/components/eth-wallet/eth-wallet.tsx | 20 +-- .../graphql-provider/graphql-provider.tsx | 12 -- .../src/components/graphql-provider/index.ts | 1 - apps/token/src/components/heading/heading.tsx | 2 +- apps/token/src/components/loader/index.ts | 1 - apps/token/src/components/loader/loader.tsx | 35 ---- .../locked-progress/locked-progress.tsx | 2 +- apps/token/src/components/nav/nav.tsx | 77 +-------- .../page-templates/template-sidebar.tsx | 2 +- .../src/components/stateful-button/index.ts | 1 - .../stateful-button/stateful-button.tsx | 14 -- .../components/token-input/token-input.tsx | 47 +++--- .../components/transaction-button/index.ts | 1 - .../transaction-button/transaction-button.tsx | 159 ------------------ .../transaction-complete.tsx | 11 +- .../transaction-callout/transaction-error.tsx | 7 +- .../transaction-pending.tsx | 13 +- .../transaction-requested.tsx | 2 +- .../transactions-modal/transactions-modal.tsx | 1 + .../vega-wallet/download-wallet-prompt.tsx | 27 +-- .../components/vega-wallet/vega-wallet.tsx | 15 +- .../components/wallet-card/wallet-card.tsx | 21 ++- apps/token/src/hooks/use-copy-to-clipboard.ts | 38 ----- apps/token/src/i18n/translations/dev.json | 15 +- apps/token/src/routes/claim/complete.tsx | 2 + apps/token/src/routes/contracts/index.tsx | 3 +- .../proposal-terms-json.tsx | 2 +- .../proposal-votes-table.tsx | 1 + .../proposals-list/proposals-list.tsx | 107 ++++++------ .../components/vote-details/vote-buttons.tsx | 23 +-- .../components/vote-details/vote-details.tsx | 12 +- .../components/vote-details/vote-progress.tsx | 10 +- apps/token/src/routes/home/index.tsx | 22 ++- .../home/token-details/token-details.tsx | 8 +- .../home/redemption-information.tsx | 41 +++-- apps/token/src/routes/redemption/index.tsx | 4 +- .../src/routes/redemption/redemption.tsx | 2 +- .../src/routes/redemption/tranche/index.tsx | 35 ++-- .../src/routes/rewards/home/reward-info.tsx | 4 +- .../staking/associate/associate-info.tsx | 9 +- .../staking/associate/associate-page.tsx | 1 + .../associate/associate-transaction.tsx | 33 ++-- .../disassociate/disassociate-page.tsx | 4 +- apps/token/src/routes/staking/node-list.tsx | 2 +- .../src/routes/staking/pending-stake.tsx | 4 +- .../src/routes/staking/stake-pending.tsx | 4 +- .../token/src/routes/staking/staking-form.tsx | 6 +- .../token/src/routes/staking/staking-node.tsx | 3 +- apps/token/src/routes/staking/staking.tsx | 44 ++--- .../src/routes/staking/validator-table.tsx | 3 +- apps/token/src/routes/tranches/tranche.tsx | 3 +- apps/token/src/routes/withdraw/index.tsx | 8 +- apps/token/src/routes/withdrawals/index.tsx | 83 ++++----- .../src/components/callout/callout.tsx | 19 ++- .../ui-toolkit/src/components/input/input.tsx | 2 +- .../components/lozenge/lozenge.stories.tsx | 5 - .../src/components/lozenge/lozenge.tsx | 25 +-- libs/web3/src/lib/web3-connect-dialog.tsx | 24 ++- libs/withdraws/src/lib/withdrawals-table.tsx | 2 +- 65 files changed, 422 insertions(+), 724 deletions(-) delete mode 100644 apps/token/src/components/eth-wallet-container/eth-wallet-container.tsx delete mode 100644 apps/token/src/components/eth-wallet-container/index.ts delete mode 100644 apps/token/src/components/graphql-provider/graphql-provider.tsx delete mode 100644 apps/token/src/components/graphql-provider/index.ts delete mode 100644 apps/token/src/components/loader/index.ts delete mode 100644 apps/token/src/components/loader/loader.tsx delete mode 100644 apps/token/src/components/stateful-button/index.ts delete mode 100644 apps/token/src/components/stateful-button/stateful-button.tsx delete mode 100644 apps/token/src/components/transaction-button/index.ts delete mode 100644 apps/token/src/components/transaction-button/transaction-button.tsx delete mode 100644 apps/token/src/hooks/use-copy-to-clipboard.ts diff --git a/apps/token-e2e/src/integration/app.test.ts b/apps/token-e2e/src/integration/app.test.ts index 267fb2b73..d4ba0c282 100644 --- a/apps/token-e2e/src/integration/app.test.ts +++ b/apps/token-e2e/src/integration/app.test.ts @@ -6,7 +6,7 @@ describe('token', () => { it('should always have a header title based on environment', () => { cy.get('[data-testid="header-title"]', { timeout: 8000 }).should( 'have.text', - `${fairgroundSet ? 'Fairground token' : '$VEGA TOKEN'}` + `${fairgroundSet ? 'Fairground token' : 'VEGA TOKEN'}` ); }); }); diff --git a/apps/token/src/app.tsx b/apps/token/src/app.tsx index aaa961d8e..6d971b0b9 100644 --- a/apps/token/src/app.tsx +++ b/apps/token/src/app.tsx @@ -1,14 +1,12 @@ import './i18n'; - import React from 'react'; +import { ApolloProvider } from '@apollo/client'; import { BrowserRouter as Router } from 'react-router-dom'; - import { AppLoader } from './app-loader'; import { AppBanner } from './components/app-banner'; import { AppFooter } from './components/app-footer'; import { BalanceManager } from './components/balance-manager'; import { EthWallet } from './components/eth-wallet'; -import { GraphQlProvider } from './components/graphql-provider'; import { TemplateSidebar } from './components/page-templates/template-sidebar'; import { TransactionModal } from './components/transactions-modal'; import { VegaWallet } from './components/vega-wallet'; @@ -21,11 +19,12 @@ import { Connectors } from './lib/web3-connectors'; import { VegaWalletDialogs } from './components/vega-wallet-dialogs'; import { VegaWalletProvider } from '@vegaprotocol/wallet'; import { EnvironmentProvider } from '@vegaprotocol/react-helpers'; +import { client } from './lib/apollo-client'; function App() { const sideBar = React.useMemo(() => [, ], []); return ( - + @@ -55,7 +54,7 @@ function App() { - + ); } diff --git a/apps/token/src/components/app-footer/app-footer.tsx b/apps/token/src/components/app-footer/app-footer.tsx index 80d0e7469..3618b77c6 100644 --- a/apps/token/src/components/app-footer/app-footer.tsx +++ b/apps/token/src/components/app-footer/app-footer.tsx @@ -1,3 +1,4 @@ +import { Link } from '@vegaprotocol/ui-toolkit'; import { Trans } from 'react-i18next'; import { Links } from '../../config'; @@ -13,8 +14,12 @@ export const AppFooter = () => { i18nKey="footerLinksText" components={{ /* eslint-disable */ - feedbackLink: , - githubLink: , + feedbackLink: ( + + ), + githubLink: ( + + ), /* eslint-enable */ }} /> diff --git a/apps/token/src/components/bullet-header/bullet-header.tsx b/apps/token/src/components/bullet-header/bullet-header.tsx index eed46c796..9f76c2bb4 100644 --- a/apps/token/src/components/bullet-header/bullet-header.tsx +++ b/apps/token/src/components/bullet-header/bullet-header.tsx @@ -9,7 +9,7 @@ interface BulletHeaderProps { export const BulletHeader = ({ tag, children, style }: BulletHeaderProps) => { return React.createElement( tag, - { className: 'mt-24 pt-8 pb-20 uppercase', style }, + { className: 'mt-24 pt-8 pb-20 uppercase text-white', style }, <> {children} diff --git a/apps/token/src/components/eth-wallet-container/eth-wallet-container.tsx b/apps/token/src/components/eth-wallet-container/eth-wallet-container.tsx deleted file mode 100644 index 943c40bdf..000000000 --- a/apps/token/src/components/eth-wallet-container/eth-wallet-container.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useWeb3React } from '@web3-react/core'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -import { - AppStateActionType, - useAppState, -} from '../../contexts/app-state/app-state-context'; -import { Ethereum } from '../icons'; -import { Button } from '@vegaprotocol/ui-toolkit'; - -interface EthWalletContainerProps { - children: (address: string) => React.ReactElement; -} - -export const EthWalletContainer = ({ children }: EthWalletContainerProps) => { - const { t } = useTranslation(); - const { appDispatch } = useAppState(); - const { account } = useWeb3React(); - - if (!account) { - return ( - - ); - } - - return children(account); -}; diff --git a/apps/token/src/components/eth-wallet-container/index.ts b/apps/token/src/components/eth-wallet-container/index.ts deleted file mode 100644 index 669004a23..000000000 --- a/apps/token/src/components/eth-wallet-container/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './eth-wallet-container'; diff --git a/apps/token/src/components/eth-wallet/eth-wallet.tsx b/apps/token/src/components/eth-wallet/eth-wallet.tsx index 2605b374c..5e70d1589 100644 --- a/apps/token/src/components/eth-wallet/eth-wallet.tsx +++ b/apps/token/src/components/eth-wallet/eth-wallet.tsx @@ -163,15 +163,15 @@ const ConnectedKey = () => { /> )} - - + - - + @@ -187,7 +187,7 @@ export const EthWallet = () => { return ( -

{t('ethereumKey')}

+

{t('ethereumKey')}

{account && (
{truncateMiddle(account)}
@@ -215,8 +215,8 @@ export const EthWallet = () => { {account ? ( ) : ( - + )} {account && ( diff --git a/apps/token/src/components/graphql-provider/graphql-provider.tsx b/apps/token/src/components/graphql-provider/graphql-provider.tsx deleted file mode 100644 index 7a4a34995..000000000 --- a/apps/token/src/components/graphql-provider/graphql-provider.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { ApolloProvider } from '@apollo/client'; -import React from 'react'; - -import { client } from '../../lib/apollo-client'; - -export const GraphQlProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - return {children}; -}; diff --git a/apps/token/src/components/graphql-provider/index.ts b/apps/token/src/components/graphql-provider/index.ts deleted file mode 100644 index eb5712fd1..000000000 --- a/apps/token/src/components/graphql-provider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './graphql-provider'; diff --git a/apps/token/src/components/heading/heading.tsx b/apps/token/src/components/heading/heading.tsx index 31c843164..4c5b91950 100644 --- a/apps/token/src/components/heading/heading.tsx +++ b/apps/token/src/components/heading/heading.tsx @@ -7,7 +7,7 @@ export const Heading = ({ title }: HeadingProps) => { return (
-

+

{title}

diff --git a/apps/token/src/components/loader/index.ts b/apps/token/src/components/loader/index.ts deleted file mode 100644 index 3f3b9e663..000000000 --- a/apps/token/src/components/loader/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './loader'; diff --git a/apps/token/src/components/loader/loader.tsx b/apps/token/src/components/loader/loader.tsx deleted file mode 100644 index b5d53d40c..000000000 --- a/apps/token/src/components/loader/loader.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -interface LoaderProps { - invert?: boolean; -} - -export const Loader = ({ invert = false }: LoaderProps) => { - const [, forceRender] = React.useState(false); - - React.useEffect(() => { - const interval = setInterval(() => { - forceRender((x) => !x); - }, 100); - - return () => clearInterval(interval); - }, []); - - return ( - - {new Array(9).fill(null).map((_, i) => { - return ( - 0.5 ? 1 : 0, - }} - className={`block w-5 h-5 opacity-0 ${ - invert ? 'bg-black' : 'bg-white' - }`} - /> - ); - })} - - ); -}; diff --git a/apps/token/src/components/locked-progress/locked-progress.tsx b/apps/token/src/components/locked-progress/locked-progress.tsx index caa264ab5..45ffa1d23 100644 --- a/apps/token/src/components/locked-progress/locked-progress.tsx +++ b/apps/token/src/components/locked-progress/locked-progress.tsx @@ -15,7 +15,7 @@ const ProgressContents = ({ }) => (
{children} diff --git a/apps/token/src/components/nav/nav.tsx b/apps/token/src/components/nav/nav.tsx index 99e82c981..dda04475e 100644 --- a/apps/token/src/components/nav/nav.tsx +++ b/apps/token/src/components/nav/nav.tsx @@ -5,14 +5,13 @@ import debounce from 'lodash/debounce'; import React from 'react'; import * as Dialog from '@radix-ui/react-dialog'; import { useTranslation } from 'react-i18next'; -import { Link, NavLink } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import { Flags } from '../../config'; import { AppStateActionType, useAppState, } from '../../contexts/app-state/app-state-context'; -import vegaWhite from '../../images/vega_white.png'; import { Routes } from '../../routes/router-config'; import { EthWallet } from '../eth-wallet'; import { VegaWallet } from '../vega-wallet'; @@ -36,14 +35,14 @@ export const Nav = () => { return (
{isDesktop && } -
+
{!isDesktop && }
{isDesktop ? ( @@ -61,76 +60,10 @@ const NavHeader = ({ fairground }: { fairground: boolean }) => { const { t } = useTranslation(); return ( -
- - {fairground ? ( - - - - - - - - - - - - - - - - - - - - - ) : ( - Vega - )} - +

diff --git a/apps/token/src/components/page-templates/template-sidebar.tsx b/apps/token/src/components/page-templates/template-sidebar.tsx index c8a959f65..971ce54cc 100644 --- a/apps/token/src/components/page-templates/template-sidebar.tsx +++ b/apps/token/src/components/page-templates/template-sidebar.tsx @@ -12,7 +12,7 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
); } diff --git a/apps/token/src/routes/staking/validator-table.tsx b/apps/token/src/routes/staking/validator-table.tsx index 557551422..300940d56 100644 --- a/apps/token/src/routes/staking/validator-table.tsx +++ b/apps/token/src/routes/staking/validator-table.tsx @@ -59,8 +59,9 @@ export const ValidatorTable = ({ {t('ETHEREUM ADDRESS')} {node.ethereumAdddress} diff --git a/apps/token/src/routes/tranches/tranche.tsx b/apps/token/src/routes/tranches/tranche.tsx index 3a429be92..307287869 100644 --- a/apps/token/src/routes/tranches/tranche.tsx +++ b/apps/token/src/routes/tranches/tranche.tsx @@ -85,8 +85,9 @@ export const Tranche = () => { return (
  • {user.address} diff --git a/apps/token/src/routes/withdraw/index.tsx b/apps/token/src/routes/withdraw/index.tsx index 89dc9b1ac..d8b6d47e8 100644 --- a/apps/token/src/routes/withdraw/index.tsx +++ b/apps/token/src/routes/withdraw/index.tsx @@ -22,7 +22,7 @@ const Withdraw = () => { return ( <> -

    {t('withdrawPageText')}

    +

    {t('withdrawPageText')}

    {(currVegaKey) => } @@ -140,9 +140,9 @@ export const WithdrawContainer = ({ currVegaKey }: WithdrawContainerProps) => { title={t('pendingWithdrawalsCalloutTitle')} intent={Intent.Prompt} > -

    {t('pendingWithdrawalsCalloutText')}

    -

    - +

    {t('pendingWithdrawalsCalloutText')}

    +

    + {t('pendingWithdrawalsCalloutButton')}

    diff --git a/apps/token/src/routes/withdrawals/index.tsx b/apps/token/src/routes/withdrawals/index.tsx index 5894bafd6..207ee5a8e 100644 --- a/apps/token/src/routes/withdrawals/index.tsx +++ b/apps/token/src/routes/withdrawals/index.tsx @@ -1,4 +1,4 @@ -import { Splash } from '@vegaprotocol/ui-toolkit'; +import { Button, Splash } from '@vegaprotocol/ui-toolkit'; import { format } from 'date-fns'; import orderBy from 'lodash/orderBy'; import React from 'react'; @@ -10,7 +10,6 @@ import { Heading } from '../../components/heading'; import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { SplashLoader } from '../../components/splash-loader'; import { VegaWalletContainer } from '../../components/vega-wallet-container'; -import type { VegaKeyExtended } from '@vegaprotocol/wallet'; import { BigNumber } from '../../lib/bignumber'; import { DATE_FORMAT_DETAILED } from '../../lib/date-formats'; import { addDecimal } from '../../lib/decimals'; @@ -27,21 +26,13 @@ const Withdrawals = () => { <> - {(currVegaKey) => ( - - )} + {(currVegaKey) => } ); }; -interface WithdrawPendingContainerProps { - currVegaKey: VegaKeyExtended; -} - -const WithdrawPendingContainer = ({ - currVegaKey, -}: WithdrawPendingContainerProps) => { +const WithdrawPendingContainer = () => { const { t } = useTranslation(); const { transaction, submit } = useCompleteWithdraw(); const { data, loading, error } = useWithdrawals(); @@ -80,11 +71,11 @@ const WithdrawPendingContainer = ({ return ( <>

    {t('withdrawalsPreparedWarningHeading')}

    -

    {t('withdrawalsText')}

    -

    {t('withdrawalsPreparedWarningText')}

    +

    {t('withdrawalsText')}

    +

    {t('withdrawalsPreparedWarningText')}

      {withdrawals.map((w) => ( -
    • +
    • ))} @@ -102,37 +93,34 @@ interface WithdrawalProps { export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => { const { ETHERSCAN_URL } = useEnvironment(); const { t } = useTranslation(); + let status = null; + let footer = null; - const renderStatus = ({ - id, - status, - txHash, - pendingOnForeignChain, - }: Withdrawals_party_withdrawals) => { - if (pendingOnForeignChain) { - return t('Pending'); + if (withdrawal.pendingOnForeignChain) { + status = t('Pending'); + footer = ( + + ); + } else if (withdrawal.status === WithdrawalStatus.Finalized) { + if (withdrawal.txHash) { + status = t('Complete'); + } else { + status = t('Incomplete'); + footer = ( + + ); } - - if (status === WithdrawalStatus.Finalized) { - if (txHash) { - return t('Complete'); - } else { - return ( - <> - {t('Incomplete')}{' '} - - - ); - } - } - - return status; - }; + } else { + status = withdrawal.status; + } return (
      @@ -151,8 +139,9 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => { {t('toEthereum')} {truncateMiddle(withdrawal.details?.receiverAddress ?? '')} @@ -174,6 +163,7 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => { {truncateMiddle(withdrawal.txHash)} @@ -184,9 +174,10 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => { {t('status')} - {renderStatus(withdrawal)} + {status} + {footer}
      ); }; diff --git a/libs/ui-toolkit/src/components/callout/callout.tsx b/libs/ui-toolkit/src/components/callout/callout.tsx index eb4122dfe..218e3082c 100644 --- a/libs/ui-toolkit/src/components/callout/callout.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.tsx @@ -45,7 +45,7 @@ const getIconElement = ({ CalloutProps, 'icon' | 'iconName' | 'iconDescription' | 'isLoading' >) => { - const wrapperClassName = 'ml-8 mr-16 mt-8'; + const wrapperClassName = 'mt-4'; if (isLoading) { return (
      @@ -64,7 +64,10 @@ const getIconElement = ({ /> ); } - return
      {icon}
      ; + if (icon) { + return
      {icon}
      ; + } + return null; }; export function Callout({ @@ -85,30 +88,30 @@ export function Callout({ }); const className = classNames( + 'flex gap-20', 'border', 'border-black', 'dark:border-white', 'text-body-large', 'dark:text-white', 'p-16', - getIntentShadow(intent), - { - flex: iconElement, - } + getIntentShadow(intent) ); const TitleTag: keyof JSX.IntrinsicElements = headingLevel ? `h${headingLevel}` : 'div'; const body = ( <> - {title && {title}} + {title && ( + {title} + )} {children} ); return (
      {iconElement} - {iconElement ?
      {body}
      : body} +
      {body}
      ); } diff --git a/libs/ui-toolkit/src/components/input/input.tsx b/libs/ui-toolkit/src/components/input/input.tsx index 8875cbc3a..9fb3e6b4d 100644 --- a/libs/ui-toolkit/src/components/input/input.tsx +++ b/libs/ui-toolkit/src/components/input/input.tsx @@ -190,7 +190,7 @@ export const Input = forwardRef( if (element) { return ( -
      +
      {hasPrepended && element} {input} {hasAppended && element} diff --git a/libs/ui-toolkit/src/components/lozenge/lozenge.stories.tsx b/libs/ui-toolkit/src/components/lozenge/lozenge.stories.tsx index cb065db53..d26288736 100644 --- a/libs/ui-toolkit/src/components/lozenge/lozenge.stories.tsx +++ b/libs/ui-toolkit/src/components/lozenge/lozenge.stories.tsx @@ -10,11 +10,6 @@ const Template: Story = (args) => lozenge; export const Default = Template.bind({}); -export const WithDetails = Template.bind({}); -WithDetails.args = { - details: 'details text', -}; - export const Highlight = Template.bind({}); Highlight.args = { variant: 'highlight', diff --git a/libs/ui-toolkit/src/components/lozenge/lozenge.tsx b/libs/ui-toolkit/src/components/lozenge/lozenge.tsx index ba5b72efd..4a697cbc7 100644 --- a/libs/ui-toolkit/src/components/lozenge/lozenge.tsx +++ b/libs/ui-toolkit/src/components/lozenge/lozenge.tsx @@ -1,38 +1,31 @@ import type { ReactNode } from 'react'; import classNames from 'classnames'; import { getVariantBackground } from '../../utils/intent'; -import type { TailwindIntents } from '../../utils/intent'; +import { TailwindIntents } from '../../utils/intent'; interface LozengeProps { children: ReactNode; variant?: TailwindIntents; className?: string; - details?: string; } -const getWrapperClasses = (className: LozengeProps['className']) => { - return classNames('inline-flex items-center gap-4', className); -}; - -const getLozengeClasses = (variant: LozengeProps['variant']) => { +const getLozengeClasses = ( + variant: LozengeProps['variant'], + className?: string +) => { return classNames( ['rounded-md', 'font-mono', 'leading-none', 'p-4'], - getVariantBackground(variant) + getVariantBackground(variant), + className ); }; export const Lozenge = ({ children, - variant, + variant = TailwindIntents.Highlight, className, - details, - ...props }: LozengeProps) => { return ( - - {children} - - {details && {details}} - + {children} ); }; diff --git a/libs/web3/src/lib/web3-connect-dialog.tsx b/libs/web3/src/lib/web3-connect-dialog.tsx index 30bc8b919..4343d8b37 100644 --- a/libs/web3/src/lib/web3-connect-dialog.tsx +++ b/libs/web3/src/lib/web3-connect-dialog.tsx @@ -27,18 +27,18 @@ export const Web3ConnectDialog = ({ >
        {connectors.map(([connector], i) => { - const connectorName = getConnectorName(connector); + const info = getConnectorInfo(connector); return (
      • ); @@ -48,8 +48,16 @@ export const Web3ConnectDialog = ({ ); }; -function getConnectorName(connector: Connector) { - if (connector instanceof MetaMask) return 'MetaMask'; - if (connector instanceof WalletConnect) return 'WalletConnect'; - return 'Unknown'; +function getConnectorInfo(connector: Connector) { + if (connector instanceof MetaMask) + return { + name: 'MetaMask', + text: t('MetaMask, Brave or other injected web wallet'), + }; + if (connector instanceof WalletConnect) + return { + name: 'WalletConnect', + text: t('WalletConnect'), + }; + return { name: 'Unknown', text: t('Unknown') }; } diff --git a/libs/withdraws/src/lib/withdrawals-table.tsx b/libs/withdraws/src/lib/withdrawals-table.tsx index ffd9b17f8..83e20a48f 100644 --- a/libs/withdraws/src/lib/withdrawals-table.tsx +++ b/libs/withdraws/src/lib/withdrawals-table.tsx @@ -138,7 +138,7 @@ const RecipientCell = ({ }: RecipientCellProps) => { return ( From e463bbe2385be126575ec96beca0062ceef2614b Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Tue, 7 Jun 2022 15:08:40 -0700 Subject: [PATCH 03/27] feat(#495): get smart contracts addresses from network params * feat: unhardcode contract addresses * fix: linting and tests * feat: switch contract usage in token app to use unhardcoded addresses * chore: remove other usage of hard coded contract addresses * feat: convert contracts to classes, update claim contract to fix circular dependency * feat: add hard coded contract addresses to contracts page * fix: misc tidy up * chore: rename ethers big num conversion func * fix: remove pending transactions modal * chore: add single toBigNum function that can accept number string or EthersBignNumber * chore: delete unused tranche helpers and decimals functions from smart contracts lib * feat: add faucetable token class * fix: reset tx state after early exit from approve tx * feat: re add transaction modal using zustand store * fix: loader colors for eth wallet * fix: pass ethereum config to gurantee existence before tx execution * chore: lint smart contracts lib * chore: fix web3container to use children and not render prop * chore: lint * fix: use background to mock ethereum wallet to avoid mocking globally for every test * chore: move web3 mock to common steps and call from withdrawals feature tests --- apps/token/src/app-loader.tsx | 10 +- .../add-locked-token/add-locked-token.tsx | 4 +- .../balance-manager/balance-manager.tsx | 36 ++-- .../src/components/eth-wallet/eth-wallet.tsx | 14 +- .../transactions-modal/transactions-modal.tsx | 8 +- apps/token/src/config/vega.ts | 2 +- .../contexts/contracts/contracts-context.ts | 22 +-- .../contexts/contracts/contracts-provider.tsx | 73 +++---- .../src/hooks/use-add-asset-to-wallet.ts | 3 +- apps/token/src/hooks/use-ethereum-config.ts | 36 ---- .../hooks/use-get-association-breakdown.ts | 75 +++++++- .../hooks/use-get-user-tranche-balances.ts | 22 ++- .../src/hooks/use-pending-transactions.ts | 5 +- .../hooks/use-refresh-associated-balances.ts | 4 +- apps/token/src/hooks/use-refresh-balances.ts | 48 +++-- apps/token/src/hooks/use-tranches.ts | 3 +- apps/token/src/hooks/use-transaction.ts | 10 +- apps/token/src/lib/ascii-to-hex.ts | 17 -- apps/token/src/lib/decimals.ts | 1 + apps/token/src/routes/claim/hooks.ts | 7 + apps/token/src/routes/contracts/index.tsx | 52 ++++- .../home/token-details/token-details.tsx | 9 +- .../associate/associate-page-container.tsx | 48 ++--- .../staking/associate/associate-page.tsx | 10 +- .../src/routes/staking/associate/hooks.ts | 10 +- .../staking/associate/wallet-associate.tsx | 23 ++- .../src/routes/staking/disassociate/hooks.ts | 8 +- ...-label.spex.tsx => tranche-label.spec.tsx} | 20 +- .../src/routes/tranches/tranche-label.tsx | 16 +- apps/token/src/routes/tranches/tranche.tsx | 17 +- apps/token/src/routes/tranches/tranches.tsx | 16 +- apps/token/src/stores/transactions.ts | 42 ++++ .../src/integration/deposits.feature | 3 +- .../src/integration/withdrawals.feature | 3 +- .../support/step_definitions/common.step.ts | 4 + .../support/step_definitions/deposits.step.ts | 4 - .../web3-container/web3-container.spec.tsx | 45 +---- .../web3-container/web3-container.tsx | 110 +++-------- .../portfolio/deposit/deposit-container.tsx | 9 +- .../pages/portfolio/deposit/index.page.tsx | 14 +- apps/trading/pages/portfolio/index.page.tsx | 112 ++++++----- .../pages/portfolio/withdraw/index.page.tsx | 14 +- .../portfolio/withdrawals/index.page.tsx | 24 ++- libs/deposits/src/lib/deposit-manager.tsx | 38 ++-- libs/deposits/src/lib/use-get-allowance.ts | 27 ++- .../src/lib/use-get-balance-of-erc20-token.ts | 16 +- .../src/lib/use-get-deposit-limits.ts | 32 ++- libs/deposits/src/lib/use-submit-approval.ts | 18 +- libs/deposits/src/lib/use-submit-deposit.ts | 16 +- libs/deposits/src/lib/use-submit-faucet.ts | 7 +- .../src/hooks/use-environment.tsx | 46 ++++- libs/react-helpers/src/index.ts | 1 + .../src/lib/environment.ts} | 0 libs/react-helpers/src/lib/format/number.ts | 16 +- libs/smart-contracts/src/config/ethereum.ts | 126 ------------ libs/smart-contracts/src/config/index.ts | 2 - .../src/contracts/base-contract.ts | 73 ------- .../src/contracts/{vega-claim.ts => claim.ts} | 109 ++++------- .../src/contracts/collateral-bridge.ts | 51 +++++ .../src/contracts/erc20-token.ts | 111 ----------- libs/smart-contracts/src/contracts/index.ts | 14 +- .../src/contracts/stake-helpers.ts | 40 ---- .../src/contracts/staking-bridge.ts | 32 +++ .../src/contracts/token-faucetable.ts | 37 ++++ .../src/contracts/token-vesting.ts | 41 ++++ libs/smart-contracts/src/contracts/token.ts | 30 +++ .../src/contracts/tranche-helpers.ts | 165 ---------------- .../src/contracts/vega-erc20-bridge.ts | 109 ----------- .../src/contracts/vega-staking.ts | 131 ------------- .../src/contracts/vega-vesting.ts | 182 ------------------ .../src/contracts/vega-web3-types.ts | 8 - libs/smart-contracts/src/index.ts | 1 - .../src/utils/decimals.test.ts | 22 --- libs/smart-contracts/src/utils/decimals.ts | 10 - libs/smart-contracts/src/utils/index.ts | 1 - .../src/components/loader/loader.tsx | 14 +- libs/web3/src/index.ts | 7 +- .../lib}/__generated__/NetworkParamsQuery.ts | 0 libs/web3/src/lib/use-bridge-contract.ts | 18 +- libs/web3/src/lib/use-ethereum-config.ts | 64 ++++++ libs/web3/src/lib/use-token-contract.ts | 19 +- libs/web3/src/lib/use-token-decimals.ts | 20 ++ .../src/lib/use-complete-withdraw.ts | 8 +- libs/withdraws/src/lib/use-withdraw.ts | 8 +- 84 files changed, 1011 insertions(+), 1672 deletions(-) delete mode 100644 apps/token/src/hooks/use-ethereum-config.ts delete mode 100644 apps/token/src/lib/ascii-to-hex.ts rename apps/token/src/routes/tranches/{tranche-label.spex.tsx => tranche-label.spec.tsx} (65%) create mode 100644 apps/token/src/stores/transactions.ts rename libs/{smart-contracts/src/config/vega.ts => react-helpers/src/lib/environment.ts} (100%) delete mode 100644 libs/smart-contracts/src/config/ethereum.ts delete mode 100644 libs/smart-contracts/src/config/index.ts delete mode 100644 libs/smart-contracts/src/contracts/base-contract.ts rename libs/smart-contracts/src/contracts/{vega-claim.ts => claim.ts} (51%) create mode 100644 libs/smart-contracts/src/contracts/collateral-bridge.ts delete mode 100644 libs/smart-contracts/src/contracts/erc20-token.ts delete mode 100644 libs/smart-contracts/src/contracts/stake-helpers.ts create mode 100644 libs/smart-contracts/src/contracts/staking-bridge.ts create mode 100644 libs/smart-contracts/src/contracts/token-faucetable.ts create mode 100644 libs/smart-contracts/src/contracts/token-vesting.ts create mode 100644 libs/smart-contracts/src/contracts/token.ts delete mode 100644 libs/smart-contracts/src/contracts/tranche-helpers.ts delete mode 100644 libs/smart-contracts/src/contracts/vega-erc20-bridge.ts delete mode 100644 libs/smart-contracts/src/contracts/vega-staking.ts delete mode 100644 libs/smart-contracts/src/contracts/vega-vesting.ts delete mode 100644 libs/smart-contracts/src/utils/decimals.test.ts delete mode 100644 libs/smart-contracts/src/utils/decimals.ts rename {apps/trading/components/web3-container => libs/web3/src/lib}/__generated__/NetworkParamsQuery.ts (100%) create mode 100644 libs/web3/src/lib/use-ethereum-config.ts create mode 100644 libs/web3/src/lib/use-token-decimals.ts diff --git a/apps/token/src/app-loader.tsx b/apps/token/src/app-loader.tsx index 2c06ea95f..3ea4ceea8 100644 --- a/apps/token/src/app-loader.tsx +++ b/apps/token/src/app-loader.tsx @@ -1,4 +1,5 @@ import * as Sentry from '@sentry/react'; +import { toBigNum } from '@vegaprotocol/react-helpers'; import { Splash } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet'; import { useWeb3React } from '@web3-react/core'; @@ -43,11 +44,16 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => { vesting.totalStaked(), token.decimals(), ]); + + const totalSupply = toBigNum(supply, decimals); + const totalWallet = toBigNum(totalAssociatedWallet, decimals); + const totalVesting = toBigNum(totalAssociatedVesting, decimals); + appDispatch({ type: AppStateActionType.SET_TOKEN, decimals, - totalSupply: supply, - totalAssociated: totalAssociatedWallet.plus(totalAssociatedVesting), + totalSupply, + totalAssociated: totalWallet.plus(totalVesting), }); setBalancesLoaded(true); } catch (err) { diff --git a/apps/token/src/components/add-locked-token/add-locked-token.tsx b/apps/token/src/components/add-locked-token/add-locked-token.tsx index 275e0aa76..429498516 100644 --- a/apps/token/src/components/add-locked-token/add-locked-token.tsx +++ b/apps/token/src/components/add-locked-token/add-locked-token.tsx @@ -1,15 +1,15 @@ import { useTranslation } from 'react-i18next'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; import { useAddAssetSupported } from '../../hooks/use-add-asset-to-wallet'; import vegaVesting from '../../images/vega_vesting.png'; import { AddTokenButtonLink } from '../add-token-button/add-token-button'; import { Callout } from '@vegaprotocol/ui-toolkit'; +import { useEnvironment } from '@vegaprotocol/react-helpers'; export const AddLockedTokenAddress = () => { - const { ADDRESSES } = useEnvironment(); const { t } = useTranslation(); const addSupported = useAddAssetSupported(); + const { ADDRESSES } = useEnvironment(); return ( { - const { ADDRESSES } = useEnvironment(); const contracts = useContracts(); const { account } = useWeb3React(); - const { appDispatch } = useAppState(); + const { + appState: { decimals }, + appDispatch, + } = useAppState(); + const { config } = useEthereumConfig(); const getUserTrancheBalances = useGetUserTrancheBalances( account || '', @@ -35,17 +37,26 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => { // update balances on connect to Ethereum React.useEffect(() => { const updateBalances = async () => { - if (!account) return; + if (!account || !config) return; try { - const [balance, walletBalance, lien, allowance] = await Promise.all([ - contracts.vesting.getUserBalanceAllTranches(account), + const [b, w, stats, a] = await Promise.all([ + contracts.vesting.userTotalAllTranches(account), contracts.token.balanceOf(account), - contracts.vesting.getLien(account), - contracts.token.allowance(account, ADDRESSES.stakingBridge), + contracts.vesting.userStats(account), + contracts.token.allowance( + account, + config.staking_bridge_contract.address + ), ]); + + const balance = toBigNum(b, decimals); + const walletBalance = toBigNum(w, decimals); + const lien = toBigNum(stats.lien, decimals); + const allowance = toBigNum(a, decimals); + appDispatch({ type: AppStateActionType.UPDATE_ACCOUNT_BALANCES, - balance: new BigNumber(balance), + balance, walletBalance, lien, allowance, @@ -57,11 +68,12 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => { updateBalances(); }, [ + decimals, appDispatch, contracts?.token, contracts?.vesting, account, - ADDRESSES.stakingBridge, + config, ]); // This use effect hook is very expensive and is kept separate to prevent expensive reloading of data. diff --git a/apps/token/src/components/eth-wallet/eth-wallet.tsx b/apps/token/src/components/eth-wallet/eth-wallet.tsx index 5e70d1589..014ec8f91 100644 --- a/apps/token/src/components/eth-wallet/eth-wallet.tsx +++ b/apps/token/src/components/eth-wallet/eth-wallet.tsx @@ -22,7 +22,7 @@ import { WalletCardHeader, WalletCardRow, } from '../wallet-card'; -import { Button, Loader } from '@vegaprotocol/ui-toolkit'; +import { Loader } from '@vegaprotocol/ui-toolkit'; import { theme } from '@vegaprotocol/tailwindcss-config'; const Colors = theme.colors; @@ -189,12 +189,12 @@ export const EthWallet = () => {

        {t('ethereumKey')}

        {account && ( -
        -
        {truncateMiddle(account)}
        +
        +
        {truncateMiddle(account)}
        {pendingTxs && (
        - +
        )}
        diff --git a/apps/token/src/components/transactions-modal/transactions-modal.tsx b/apps/token/src/components/transactions-modal/transactions-modal.tsx index c886afb5f..c1d8fc67a 100644 --- a/apps/token/src/components/transactions-modal/transactions-modal.tsx +++ b/apps/token/src/components/transactions-modal/transactions-modal.tsx @@ -1,4 +1,3 @@ -import type { TxData } from '@vegaprotocol/smart-contracts'; import { Dialog, Link } from '@vegaprotocol/ui-toolkit'; import { useEnvironment } from '@vegaprotocol/react-helpers'; import React from 'react'; @@ -8,9 +7,10 @@ import { AppStateActionType, useAppState, } from '../../contexts/app-state/app-state-context'; -import { useContracts } from '../../contexts/contracts/contracts-context'; import { truncateMiddle } from '../../lib/truncate-middle'; import { Tick } from '../icons'; +import type { TxData } from '../../stores/transactions'; +import { useTransactionStore } from '../../stores/transactions'; const TransactionModalTh = ({ children }: { children: React.ReactNode }) => ( @@ -31,7 +31,7 @@ const TransactionModalStatus = ({ export const TransactionModal = () => { const { ETHERSCAN_URL } = useEnvironment(); const { t } = useTranslation(); - const { transactions } = useContracts(); + const { transactions } = useTransactionStore(); const { appState, appDispatch } = useAppState(); const renderStatus = (txObj: TxData) => { @@ -84,8 +84,8 @@ export const TransactionModal = () => { {truncateMiddle(transaction.tx.hash)} diff --git a/apps/token/src/config/vega.ts b/apps/token/src/config/vega.ts index c5bd9f4d9..ac584cde6 100644 --- a/apps/token/src/config/vega.ts +++ b/apps/token/src/config/vega.ts @@ -1,4 +1,4 @@ -import { Networks } from '@vegaprotocol/smart-contracts'; +import { Networks } from '@vegaprotocol/react-helpers'; interface VegaNode { url: string; diff --git a/apps/token/src/contexts/contracts/contracts-context.ts b/apps/token/src/contexts/contracts/contracts-context.ts index d5ec9bc67..a415f68c8 100644 --- a/apps/token/src/contexts/contracts/contracts-context.ts +++ b/apps/token/src/contexts/contracts/contracts-context.ts @@ -1,20 +1,18 @@ import type { - TxData, - VegaClaim, - VegaErc20Bridge, - VegaStaking, - ERC20Token, - VegaVesting, + Claim, + CollateralBridge, + Token, + TokenVesting, + StakingBridge, } from '@vegaprotocol/smart-contracts'; import React from 'react'; export interface ContractsContextShape { - token: ERC20Token; - staking: VegaStaking; - vesting: VegaVesting; - claim: VegaClaim; - erc20Bridge: VegaErc20Bridge; - transactions: TxData[]; + token: Token; + staking: StakingBridge; + vesting: TokenVesting; + claim: Claim; + erc20Bridge: CollateralBridge; } export const ContractsContext = React.createContext< diff --git a/apps/token/src/contexts/contracts/contracts-provider.tsx b/apps/token/src/contexts/contracts/contracts-provider.tsx index df74c5459..8569bbd71 100644 --- a/apps/token/src/contexts/contracts/contracts-provider.tsx +++ b/apps/token/src/contexts/contracts/contracts-provider.tsx @@ -1,29 +1,28 @@ -import type { TxData } from '@vegaprotocol/smart-contracts'; import { - VegaClaim, - VegaErc20Bridge, - VegaStaking, - ERC20Token, - VegaVesting, + Token, + TokenVesting, + Claim, + CollateralBridge, + StakingBridge, } from '@vegaprotocol/smart-contracts'; import { Splash } from '@vegaprotocol/ui-toolkit'; import { useWeb3React } from '@web3-react/core'; -import uniqBy from 'lodash/uniqBy'; import React from 'react'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; import { SplashLoader } from '../../components/splash-loader'; import type { ContractsContextShape } from './contracts-context'; import { ContractsContext } from './contracts-context'; import { defaultProvider } from '../../lib/web3-connectors'; +import { useEthereumConfig } from '@vegaprotocol/web3'; +import { useEnvironment } from '@vegaprotocol/react-helpers'; /** * Provides Vega Ethereum contract instances to its children. */ export const ContractsProvider = ({ children }: { children: JSX.Element }) => { - const { ADDRESSES, VEGA_ENV } = useEnvironment(); const { provider: activeProvider, account } = useWeb3React(); - const [txs, setTxs] = React.useState([]); + const { config } = useEthereumConfig(); + const { VEGA_ENV, ADDRESSES } = useEnvironment(); const [contracts, setContracts] = React.useState { signer = provider.getSigner(); } - if (provider) { + if (provider && config) { setContracts({ - token: new ERC20Token( - ADDRESSES.vegaTokenAddress, - // @ts-ignore Cant accept JsonRpcProvider provider - provider, - signer + token: new Token(ADDRESSES.vegaTokenAddress, signer || provider), + staking: new StakingBridge( + config.staking_bridge_contract.address, + signer || provider ), - // @ts-ignore Cant accept JsonRpcProvider provider - staking: new VegaStaking(VEGA_ENV, provider, signer), - // @ts-ignore Cant accept JsonRpcProvider provider - vesting: new VegaVesting(VEGA_ENV, provider, signer), - // @ts-ignore Cant accept JsonRpcProvider provider - claim: new VegaClaim(VEGA_ENV, provider, signer), - erc20Bridge: new VegaErc20Bridge( - VEGA_ENV, - // @ts-ignore Cant accept JsonRpcProvider provider - provider, - signer + vesting: new TokenVesting( + config.token_vesting_contract.address, + signer || provider + ), + claim: new Claim(ADDRESSES.claimAddress, signer || provider), + erc20Bridge: new CollateralBridge( + config.collateral_bridge_contract.address, + signer || provider ), }); } - }, [activeProvider, account, ADDRESSES.vegaTokenAddress, VEGA_ENV]); - - React.useEffect(() => { - if (!contracts) return; - - const mergeTxs = (existing: TxData[], incoming: TxData[]) => { - return uniqBy([...incoming, ...existing], 'tx.hash'); - }; - - contracts.staking.listen((txs) => { - setTxs((curr) => mergeTxs(curr, txs)); - }); - - contracts.vesting.listen((txs) => { - setTxs((curr) => mergeTxs(curr, txs)); - }); - }, [contracts]); - - React.useEffect(() => { - setTxs([]); - }, [account]); + }, [activeProvider, account, config, ADDRESSES, VEGA_ENV]); if (!contracts) { return ( @@ -98,7 +73,7 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => { } return ( - + {children} ); diff --git a/apps/token/src/hooks/use-add-asset-to-wallet.ts b/apps/token/src/hooks/use-add-asset-to-wallet.ts index 886fc50ef..2b9b5f505 100644 --- a/apps/token/src/hooks/use-add-asset-to-wallet.ts +++ b/apps/token/src/hooks/use-add-asset-to-wallet.ts @@ -1,9 +1,8 @@ import React from 'react'; import * as Sentry from '@sentry/react'; +import { Networks, useEnvironment } from '@vegaprotocol/react-helpers'; import { useWeb3React } from '@web3-react/core'; import { MetaMask } from '@web3-react/metamask'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; -import { Networks } from '@vegaprotocol/smart-contracts'; export const useAddAssetSupported = () => { const { connector } = useWeb3React(); diff --git a/apps/token/src/hooks/use-ethereum-config.ts b/apps/token/src/hooks/use-ethereum-config.ts deleted file mode 100644 index 16ea2b05c..000000000 --- a/apps/token/src/hooks/use-ethereum-config.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as Sentry from '@sentry/react'; -import React from 'react'; - -import { NetworkParams } from '../config'; -import { useNetworkParam } from './use-network-param'; - -export const useEthereumConfig = () => { - const { data: ethereumConfigJSON, loading } = useNetworkParam([ - NetworkParams.ETHEREUM_CONFIG, - ]); - const ethereumConfig = React.useMemo(() => { - if (!ethereumConfigJSON && !loading) { - Sentry.captureMessage( - `No ETH config found for network param ${NetworkParams.ETHEREUM_CONFIG}` - ); - return null; - } else if (!ethereumConfigJSON) { - return null; - } - try { - const [configJson] = ethereumConfigJSON; - return JSON.parse(configJson); - } catch { - Sentry.captureMessage('Ethereum config JSON is invalid'); - return null; - } - }, [ethereumConfigJSON, loading]); - - if (!ethereumConfig) { - return null; - } - - return { - confirmations: ethereumConfig.confirmations, - }; -}; diff --git a/apps/token/src/hooks/use-get-association-breakdown.ts b/apps/token/src/hooks/use-get-association-breakdown.ts index ae12a6419..40af8a5b1 100644 --- a/apps/token/src/hooks/use-get-association-breakdown.ts +++ b/apps/token/src/hooks/use-get-association-breakdown.ts @@ -1,24 +1,33 @@ import React from 'react'; +import type { ethers } from 'ethers'; import * as Sentry from '@sentry/react'; -import type { VegaStaking, VegaVesting } from '@vegaprotocol/smart-contracts'; +import { addDecimal } from '@vegaprotocol/react-helpers'; +import type { + StakingBridge, + TokenVesting, +} from '@vegaprotocol/smart-contracts'; import { AppStateActionType, useAppState, } from '../contexts/app-state/app-state-context'; +import BigNumber from 'bignumber.js'; export function useGetAssociationBreakdown( ethAddress: string, - staking: VegaStaking, - vesting: VegaVesting + staking: StakingBridge, + vesting: TokenVesting ): () => Promise { - const { appDispatch } = useAppState(); + const { + appState: { decimals }, + appDispatch, + } = useAppState(); const getAssociationBreakdown = React.useCallback(async () => { try { const [stakingAssociations, vestingAssociations] = await Promise.all([ - staking.userTotalStakedByVegaKey(ethAddress), - vesting.userTotalStakedByVegaKey(ethAddress), + userTotalStakedByVegaKey(staking, ethAddress, decimals), + userTotalStakedByVegaKey(vesting, ethAddress, decimals), ]); appDispatch({ @@ -31,7 +40,59 @@ export function useGetAssociationBreakdown( } catch (err) { Sentry.captureException(err); } - }, [ethAddress, staking, vesting, appDispatch]); + }, [ethAddress, staking, vesting, decimals, appDispatch]); return getAssociationBreakdown; } + +async function userTotalStakedByVegaKey( + contract: StakingBridge | TokenVesting, + ethereumAccount: string, + decimals: number +): Promise<{ [vegaKey: string]: BigNumber }> { + const addFilter = contract.contract.filters.Stake_Deposited(ethereumAccount); + const removeFilter = contract.contract.filters.Stake_Removed(ethereumAccount); + const addEvents = await contract.contract.queryFilter(addFilter); + const removeEvents = await contract.contract.queryFilter(removeFilter); + const res = combineStakeEventsByVegaKey( + [...addEvents, ...removeEvents], + decimals + ); + return res; +} + +function combineStakeEventsByVegaKey( + events: ethers.Event[], + decimals: number +): { [vegaKey: string]: BigNumber } { + const res = events.reduce((obj, e) => { + const vegaKey = e.args?.vega_public_key; + const amount = parseEventAmount(e, decimals); + const isDeposit = e.event === 'Stake_Deposited'; + const isRemove = e.event === 'Stake_Removed'; + + if (!isDeposit && !isRemove) return obj; + + if (Object.prototype.hasOwnProperty.call(obj, vegaKey)) { + if (isDeposit) { + obj[vegaKey] = obj[vegaKey].plus(amount); + } else { + obj[vegaKey] = obj[vegaKey].minus(amount); + } + } else { + if (isDeposit) { + obj[vegaKey] = amount; + } else { + obj[vegaKey] = new BigNumber(0); + } + } + return obj; + }, {} as { [vegaKey: string]: BigNumber }); + + return res; +} + +function parseEventAmount(e: ethers.Event, decimals: number) { + const rawAmount = new BigNumber(e.args?.amount.toString() || 0); + return new BigNumber(addDecimal(rawAmount.toString(), decimals)); +} diff --git a/apps/token/src/hooks/use-get-user-tranche-balances.ts b/apps/token/src/hooks/use-get-user-tranche-balances.ts index 65d996d28..5e58bcb1d 100644 --- a/apps/token/src/hooks/use-get-user-tranche-balances.ts +++ b/apps/token/src/hooks/use-get-user-tranche-balances.ts @@ -1,6 +1,6 @@ import React from 'react'; import * as Sentry from '@sentry/react'; -import type { VegaVesting } from '@vegaprotocol/smart-contracts'; +import type { TokenVesting } from '@vegaprotocol/smart-contracts'; import { AppStateActionType, @@ -8,12 +8,16 @@ import { } from '../contexts/app-state/app-state-context'; import { BigNumber } from '../lib/bignumber'; import { useTranches } from './use-tranches'; +import { toBigNum } from '@vegaprotocol/react-helpers'; export const useGetUserTrancheBalances = ( address: string, - vesting: VegaVesting + vesting: TokenVesting ) => { - const { appDispatch } = useAppState(); + const { + appState: { decimals }, + appDispatch, + } = useAppState(); const { tranches } = useTranches(); return React.useCallback(async () => { appDispatch({ @@ -32,10 +36,14 @@ export const useGetUserTrancheBalances = ( ); const trancheIds = [0, ...userTranches.map((t) => t.tranche_id)]; const promises = trancheIds.map(async (tId) => { - const [total, vested] = await Promise.all([ - vesting.userTrancheTotalBalance(address, tId), - vesting.userTrancheVestedBalance(address, tId), + const [t, v] = await Promise.all([ + vesting.getTrancheBalance(address, tId), + vesting.getVestedForTranche(address, tId), ]); + + const total = toBigNum(t, decimals); + const vested = toBigNum(v, decimals); + return { id: tId, locked: tId === 0 ? total : total.minus(vested), @@ -56,5 +64,5 @@ export const useGetUserTrancheBalances = ( error: e as Error, }); } - }, [address, appDispatch, tranches, vesting]); + }, [address, decimals, appDispatch, tranches, vesting]); }; diff --git a/apps/token/src/hooks/use-pending-transactions.ts b/apps/token/src/hooks/use-pending-transactions.ts index 8c66b03e4..128809c55 100644 --- a/apps/token/src/hooks/use-pending-transactions.ts +++ b/apps/token/src/hooks/use-pending-transactions.ts @@ -1,9 +1,8 @@ import React from 'react'; - -import { useContracts } from '../contexts/contracts/contracts-context'; +import { useTransactionStore } from '../stores/transactions'; export const usePendingTransactions = () => { - const { transactions } = useContracts(); + const { transactions } = useTransactionStore(); return React.useMemo(() => { return transactions.some((tx) => tx.pending); diff --git a/apps/token/src/hooks/use-refresh-associated-balances.ts b/apps/token/src/hooks/use-refresh-associated-balances.ts index 5f709e249..6f0831f89 100644 --- a/apps/token/src/hooks/use-refresh-associated-balances.ts +++ b/apps/token/src/hooks/use-refresh-associated-balances.ts @@ -14,8 +14,8 @@ export function useRefreshAssociatedBalances() { async (ethAddress: string, vegaKey: string) => { const [walletAssociatedBalance, vestingAssociatedBalance] = await Promise.all([ - staking.stakeBalance(ethAddress, vegaKey), - vesting.stakeBalance(ethAddress, vegaKey), + staking.stakeBalance(ethAddress, `0x${vegaKey}`), + vesting.stakeBalance(ethAddress, `0x${vegaKey}`), ]); appDispatch({ diff --git a/apps/token/src/hooks/use-refresh-balances.ts b/apps/token/src/hooks/use-refresh-balances.ts index 79887e6ae..fe24de33a 100644 --- a/apps/token/src/hooks/use-refresh-balances.ts +++ b/apps/token/src/hooks/use-refresh-balances.ts @@ -1,8 +1,9 @@ import * as Sentry from '@sentry/react'; +import { toBigNum } from '@vegaprotocol/react-helpers'; import { useVegaWallet } from '@vegaprotocol/wallet'; +import { useEthereumConfig } from '@vegaprotocol/web3'; import React from 'react'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; import { AppStateActionType, useAppState, @@ -10,29 +11,35 @@ import { import { useContracts } from '../contexts/contracts/contracts-context'; export const useRefreshBalances = (address: string) => { - const { ADDRESSES } = useEnvironment(); - const { appDispatch } = useAppState(); + const { + appState: { decimals }, + appDispatch, + } = useAppState(); const { keypair } = useVegaWallet(); const { token, staking, vesting } = useContracts(); + const { config } = useEthereumConfig(); return React.useCallback(async () => { + if (!config) return; try { - const [ - balance, - walletBalance, - lien, - allowance, - walletAssociatedBalance, - vestingAssociatedBalance, - ] = await Promise.all([ - vesting.getUserBalanceAllTranches(address), - token.balanceOf(address), - vesting.getLien(address), - token.allowance(address, ADDRESSES.stakingBridge), - // Refresh connected vega key balances as well if we are connected to a vega key - keypair?.pub ? staking.stakeBalance(address, keypair.pub) : null, - keypair?.pub ? vesting.stakeBalance(address, keypair.pub) : null, - ]); + const [b, w, stats, a, walletStakeBalance, vestingStakeBalance] = + await Promise.all([ + vesting.userTotalAllTranches(address), + token.balanceOf(address), + vesting.userStats(address), + token.allowance(address, config.staking_bridge_contract.address), + // Refresh connected vega key balances as well if we are connected to a vega key + keypair?.pub ? staking.stakeBalance(address, keypair.pub) : null, + keypair?.pub ? vesting.stakeBalance(address, keypair.pub) : null, + ]); + + const balance = toBigNum(b, decimals); + const walletBalance = toBigNum(w, decimals); + const lien = toBigNum(stats.lien, decimals); + const allowance = toBigNum(a, decimals); + const walletAssociatedBalance = toBigNum(walletStakeBalance, decimals); + const vestingAssociatedBalance = toBigNum(vestingStakeBalance, decimals); + appDispatch({ type: AppStateActionType.REFRESH_BALANCES, balance, @@ -47,11 +54,12 @@ export const useRefreshBalances = (address: string) => { } }, [ address, + decimals, appDispatch, keypair?.pub, staking, token, vesting, - ADDRESSES.stakingBridge, + config, ]); }; diff --git a/apps/token/src/hooks/use-tranches.ts b/apps/token/src/hooks/use-tranches.ts index ba379dbf4..76ad242f1 100644 --- a/apps/token/src/hooks/use-tranches.ts +++ b/apps/token/src/hooks/use-tranches.ts @@ -1,5 +1,6 @@ +import type { Networks } from '@vegaprotocol/react-helpers'; import { useFetch } from '@vegaprotocol/react-helpers'; -import type { Networks, Tranche } from '@vegaprotocol/smart-contracts'; +import type { Tranche } from '@vegaprotocol/smart-contracts'; import React, { useEffect } from 'react'; import { useEnvironment } from '@vegaprotocol/react-helpers'; diff --git a/apps/token/src/hooks/use-transaction.ts b/apps/token/src/hooks/use-transaction.ts index 8d1bbd28c..0aa63fcb8 100644 --- a/apps/token/src/hooks/use-transaction.ts +++ b/apps/token/src/hooks/use-transaction.ts @@ -2,19 +2,20 @@ import React from 'react'; import * as Sentry from '@sentry/react'; import { useTranslation } from 'react-i18next'; import type { ethers } from 'ethers'; - import { isUnexpectedError, isUserRejection } from '../lib/web3-utils'; import { initialState, TransactionActionType, transactionReducer, } from './transaction-reducer'; +import { useTransactionStore } from '../stores/transactions'; export const useTransaction = ( performTransaction: () => Promise, requiredConfirmations = 1 ) => { const { t } = useTranslation(); + const store = useTransactionStore(); const [state, dispatch] = React.useReducer(transactionReducer, { ...initialState, requiredConfirmations, @@ -62,10 +63,12 @@ export const useTransaction = ( try { const tx = await performTransaction(); + store.add({ tx, receipt: null, pending: true, requiredConfirmations }); dispatch({ type: TransactionActionType.TX_SUBMITTED, txHash: tx.hash, }); + Sentry.addBreadcrumb({ type: 'Transaction', level: Sentry.Severity.Log, @@ -83,6 +86,7 @@ export const useTransaction = ( for (let i = 1; i <= requiredConfirmations; i++) { receipt = await tx.wait(i); + store.update({ tx, receipt, pending: true, requiredConfirmations }); dispatch({ type: TransactionActionType.TX_CONFIRMATION, confirmations: receipt.confirmations, @@ -93,11 +97,13 @@ export const useTransaction = ( throw new Error('No receipt after confirmations are met'); } + store.update({ tx, receipt, pending: false, requiredConfirmations }); dispatch({ type: TransactionActionType.TX_COMPLETE, receipt, confirmations: receipt.confirmations, }); + Sentry.addBreadcrumb({ type: 'Transaction', level: Sentry.Severity.Log, @@ -114,7 +120,7 @@ export const useTransaction = ( } catch (err) { handleError(err as Error); } - }, [performTransaction, requiredConfirmations, handleError]); + }, [performTransaction, requiredConfirmations, handleError, store]); const reset = () => { dispatch({ type: TransactionActionType.TX_RESET }); diff --git a/apps/token/src/lib/ascii-to-hex.ts b/apps/token/src/lib/ascii-to-hex.ts deleted file mode 100644 index 606a6b2d4..000000000 --- a/apps/token/src/lib/ascii-to-hex.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * From: - * https://github.com/ChainSafe/web3.js/blob/436e77a8eaa061fbaa183a9f73ca590c2e1d7697/packages/web3-utils/src/index.js - */ -export const asciiToHex = (str: string) => { - if (!str) return '0x00'; - - let hex = ''; - - for (let i = 0; i < str.length; i++) { - const code = str.charCodeAt(i); - const n = code.toString(16); - hex += n.length < 2 ? '0' + n : n; - } - - return '0x' + hex; -}; diff --git a/apps/token/src/lib/decimals.ts b/apps/token/src/lib/decimals.ts index b68fc8ad4..7f46611ce 100644 --- a/apps/token/src/lib/decimals.ts +++ b/apps/token/src/lib/decimals.ts @@ -6,6 +6,7 @@ export function addDecimal(value: BigNumber, decimals: number): string { .decimalPlaces(decimals) .toString(); } + export function removeDecimal(value: BigNumber, decimals: number): string { return value.times(Math.pow(10, decimals)).toFixed(0); } diff --git a/apps/token/src/routes/claim/hooks.ts b/apps/token/src/routes/claim/hooks.ts index 6ea6eee60..b446175af 100644 --- a/apps/token/src/routes/claim/hooks.ts +++ b/apps/token/src/routes/claim/hooks.ts @@ -1,8 +1,14 @@ import { useContracts } from '../../contexts/contracts/contracts-context'; import { useTransaction } from '../../hooks/use-transaction'; import type { IClaimTokenParams } from '@vegaprotocol/smart-contracts'; +import { removeDecimal } from '@vegaprotocol/react-helpers'; +import { useAppState } from '../../contexts/app-state/app-state-context'; export const useClaim = (claimData: IClaimTokenParams, address: string) => { + const { + appState: { decimals }, + } = useAppState(); + const claimArgs = { ...claimData, ...claimData.signature, @@ -10,6 +16,7 @@ export const useClaim = (claimData: IClaimTokenParams, address: string) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion country: claimData.country!, account: address, + amount: removeDecimal(claimData.claim.amount.toString(), decimals), }; const { claim } = useContracts(); return useTransaction(() => claim.claim(claimArgs)); diff --git a/apps/token/src/routes/contracts/index.tsx b/apps/token/src/routes/contracts/index.tsx index 8f4782509..ef36f137c 100644 --- a/apps/token/src/routes/contracts/index.tsx +++ b/apps/token/src/routes/contracts/index.tsx @@ -1,22 +1,60 @@ -import { useTranslation } from 'react-i18next'; -import { Link } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { t, useEnvironment } from '@vegaprotocol/react-helpers'; +import { Link, Splash } from '@vegaprotocol/ui-toolkit'; +import type { EthereumConfig } from '@vegaprotocol/web3'; +import { useEthereumConfig } from '@vegaprotocol/web3'; import { Heading } from '../../components/heading'; +import { SplashLoader } from '../../components/splash-loader'; const Contracts = () => { + const { config } = useEthereumConfig(); const { ADDRESSES, ETHERSCAN_URL } = useEnvironment(); - const { t } = useTranslation(); + + if (!config) { + return ( + + + + ); + } + return (

        + {[ + 'collateral_bridge_contract', + 'multisig_control_contract', + 'staking_bridge_contract', + 'token_vesting_contract', + ].map((key) => { + const contract = config[key as keyof EthereumConfig] as { + address: string; + }; + + return ( +
        +
        {key}:
        + + {config.collateral_bridge_contract.address} + +
        + ); + })} {Object.entries(ADDRESSES).map(([key, value]) => ( -
        +
        {key}:
        {value} diff --git a/apps/token/src/routes/home/token-details/token-details.tsx b/apps/token/src/routes/home/token-details/token-details.tsx index fa9d6a1ed..44a9b7b79 100644 --- a/apps/token/src/routes/home/token-details/token-details.tsx +++ b/apps/token/src/routes/home/token-details/token-details.tsx @@ -8,6 +8,7 @@ import type { BigNumber } from '../../../lib/bignumber'; import { formatNumber } from '../../../lib/format-number'; import { TokenDetailsCirculating } from './token-details-circulating'; import { SplashLoader } from '../../../components/splash-loader'; +import { useEthereumConfig } from '@vegaprotocol/web3'; export const TokenDetails = ({ totalSupply, @@ -20,6 +21,7 @@ export const TokenDetails = ({ const { t } = useTranslation(); const { tranches, loading, error } = useTranches(); + const { config } = useEthereumConfig(); if (error) { return ( @@ -29,7 +31,7 @@ export const TokenDetails = ({ ); } - if (!tranches || loading) { + if (!tranches || loading || !config) { return ( @@ -57,10 +59,9 @@ export const TokenDetails = ({ data-testid="token-contract" title={t('View on Etherscan (opens in a new tab)')} className="font-mono" - href={`${ETHERSCAN_URL}/address/${ADDRESSES.vestingAddress}`} - target="_blank" + href={`${ETHERSCAN_URL}/address/${config.token_vesting_contract.address}`} > - {ADDRESSES.vestingAddress} + {config.token_vesting_contract.address} diff --git a/apps/token/src/routes/staking/associate/associate-page-container.tsx b/apps/token/src/routes/staking/associate/associate-page-container.tsx index de9bb0d07..d1e0d6eb0 100644 --- a/apps/token/src/routes/staking/associate/associate-page-container.tsx +++ b/apps/token/src/routes/staking/associate/associate-page-container.tsx @@ -1,44 +1,28 @@ -import React from 'react'; - -import { useEthereumConfig } from '../../../hooks/use-ethereum-config'; +import { useEthereumConfig } from '@vegaprotocol/web3'; import { StakingWalletsContainer } from '../staking-wallets-container'; import { AssociatePage } from './associate-page'; import { AssociatePageNoVega } from './associate-page-no-vega'; -export const NetworkParamsContainer = ({ - children, -}: { - children: (data: { confirmations: number }) => React.ReactElement; -}) => { - const config = useEthereumConfig(); +export const AssociateContainer = () => { + const { config } = useEthereumConfig(); if (!config) { return null; } - return children({ - confirmations: config.confirmations, - }); -}; - -export const AssociateContainer = () => { return ( - - {({ confirmations }) => ( - - {({ address, currVegaKey }) => - currVegaKey ? ( - - ) : ( - - ) - } - - )} - + + {({ address, currVegaKey }) => + currVegaKey ? ( + + ) : ( + + ) + } + ); }; diff --git a/apps/token/src/routes/staking/associate/associate-page.tsx b/apps/token/src/routes/staking/associate/associate-page.tsx index 1104d0ee8..933647208 100644 --- a/apps/token/src/routes/staking/associate/associate-page.tsx +++ b/apps/token/src/routes/staking/associate/associate-page.tsx @@ -1,5 +1,6 @@ import { Callout, Intent } from '@vegaprotocol/ui-toolkit'; import type { VegaKeyExtended } from '@vegaprotocol/wallet'; +import type { EthereumConfig } from '@vegaprotocol/web3'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -18,11 +19,11 @@ import { WalletAssociate } from './wallet-associate'; export const AssociatePage = ({ address, vegaKey, - requiredConfirmations, + ethereumConfig, }: { address: string; vegaKey: VegaKeyExtended; - requiredConfirmations: number; + ethereumConfig: EthereumConfig; }) => { const { t } = useTranslation(); const params = useSearchParams(); @@ -45,7 +46,7 @@ export const AssociatePage = ({ amount, vegaKey.pub, selectedStakingMethod, - requiredConfirmations + ethereumConfig.confirmations ); const linking = usePollForStakeLinking(vegaKey.pub, txState.txData.hash); @@ -81,7 +82,7 @@ export const AssociatePage = ({ vegaKey={vegaKey.pub} state={txState} dispatch={txDispatch} - requiredConfirmations={requiredConfirmations} + requiredConfirmations={ethereumConfig.confirmations} linking={linking} /> ); @@ -128,6 +129,7 @@ export const AssociatePage = ({ perform={txPerform} amount={amount} setAmount={setAmount} + ethereumConfig={ethereumConfig} /> ))}
        diff --git a/apps/token/src/routes/staking/associate/hooks.ts b/apps/token/src/routes/staking/associate/hooks.ts index c85c8e644..e819167ef 100644 --- a/apps/token/src/routes/staking/associate/hooks.ts +++ b/apps/token/src/routes/staking/associate/hooks.ts @@ -1,6 +1,5 @@ import { gql, useApolloClient } from '@apollo/client'; import * as Sentry from '@sentry/react'; -import BigNumber from 'bignumber.js'; import React from 'react'; import { StakeLinkingStatus } from '../../../__generated__/globalTypes'; @@ -15,6 +14,8 @@ import type { PartyStakeLinkings_party_stake_linkings, PartyStakeLinkingsVariables, } from './__generated__/PartyStakeLinkings'; +import { useAppState } from '../../../contexts/app-state/app-state-context'; +import { removeDecimal } from '@vegaprotocol/react-helpers'; export const useAddStake = ( address: string, @@ -24,12 +25,15 @@ export const useAddStake = ( confirmations: number ) => { const { staking, vesting } = useContracts(); + const { + appState: { decimals }, + } = useAppState(); const contractAdd = useTransaction( - () => vesting.addStake(new BigNumber(amount), vegaKey, confirmations), + () => vesting.stakeTokens(removeDecimal(amount, decimals), vegaKey), confirmations ); const walletAdd = useTransaction( - () => staking.addStake(new BigNumber(amount), vegaKey, confirmations), + () => staking.stake(removeDecimal(amount, decimals), vegaKey), confirmations ); const refreshBalances = useRefreshBalances(address); diff --git a/apps/token/src/routes/staking/associate/wallet-associate.tsx b/apps/token/src/routes/staking/associate/wallet-associate.tsx index 099cd2c27..bc6a4f974 100644 --- a/apps/token/src/routes/staking/associate/wallet-associate.tsx +++ b/apps/token/src/routes/staking/associate/wallet-associate.tsx @@ -12,7 +12,8 @@ import { useTransaction } from '../../../hooks/use-transaction'; import { BigNumber } from '../../../lib/bignumber'; import { AssociateInfo } from './associate-info'; import type { VegaKeyExtended } from '@vegaprotocol/wallet'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { toBigNum } from '@vegaprotocol/react-helpers'; +import type { EthereumConfig } from '@vegaprotocol/web3'; export const WalletAssociate = ({ perform, @@ -20,18 +21,19 @@ export const WalletAssociate = ({ amount, setAmount, address, + ethereumConfig, }: { perform: () => void; amount: string; setAmount: React.Dispatch>; vegaKey: VegaKeyExtended; address: string; + ethereumConfig: EthereumConfig; }) => { - const { ADDRESSES } = useEnvironment(); const { t } = useTranslation(); const { appDispatch, - appState: { walletBalance, allowance, walletAssociatedBalance }, + appState: { walletBalance, allowance, walletAssociatedBalance, decimals }, } = useAppState(); const { token } = useContracts(); @@ -40,16 +42,22 @@ export const WalletAssociate = ({ state: approveState, perform: approve, dispatch: approveDispatch, - } = useTransaction(() => token.approve(ADDRESSES.stakingBridge)); + } = useTransaction(() => + token.approve( + ethereumConfig.staking_bridge_contract.address, + Number.MAX_SAFE_INTEGER.toString() + ) + ); // Once they have approved deposits then we need to refresh their allowance React.useEffect(() => { const run = async () => { if (approveState.txState === TxState.Complete) { - const allowance = await token.allowance( + const a = await token.allowance( address, - ADDRESSES.stakingBridge + ethereumConfig.staking_bridge_contract.address ); + const allowance = toBigNum(a, decimals); appDispatch({ type: AppStateActionType.SET_ALLOWANCE, allowance, @@ -62,7 +70,8 @@ export const WalletAssociate = ({ appDispatch, approveState.txState, token, - ADDRESSES.stakingBridge, + decimals, + ethereumConfig, ]); let pageContent = null; diff --git a/apps/token/src/routes/staking/disassociate/hooks.ts b/apps/token/src/routes/staking/disassociate/hooks.ts index ab15a072b..574651000 100644 --- a/apps/token/src/routes/staking/disassociate/hooks.ts +++ b/apps/token/src/routes/staking/disassociate/hooks.ts @@ -1,4 +1,5 @@ -import BigNumber from 'bignumber.js'; +import { removeDecimal } from '@vegaprotocol/react-helpers'; +import { useAppState } from '../../../contexts/app-state/app-state-context'; import React from 'react'; import { StakingMethod } from '../../../components/staking-method-radio'; @@ -14,15 +15,16 @@ export const useRemoveStake = ( vegaKey: string, stakingMethod: StakingMethod | null ) => { + const { appState } = useAppState(); const { staking, vesting } = useContracts(); // Cannot use call on these as they check wallet balance // which if staked > wallet balance means you cannot unstaked // even worse if you stake everything then you can't unstake anything! const contractRemove = useTransaction(() => - vesting.removeStake(new BigNumber(amount), vegaKey) + vesting.removeStake(removeDecimal(amount, appState.decimals), vegaKey) ); const walletRemove = useTransaction(() => - staking.removeStake(new BigNumber(amount), vegaKey) + staking.removeStake(removeDecimal(amount, appState.decimals), vegaKey) ); const refreshBalances = useRefreshBalances(address); const getAssociationBreakdown = useGetAssociationBreakdown( diff --git a/apps/token/src/routes/tranches/tranche-label.spex.tsx b/apps/token/src/routes/tranches/tranche-label.spec.tsx similarity index 65% rename from apps/token/src/routes/tranches/tranche-label.spex.tsx rename to apps/token/src/routes/tranches/tranche-label.spec.tsx index e391cca1c..cc429f8af 100644 --- a/apps/token/src/routes/tranches/tranche-label.spex.tsx +++ b/apps/token/src/routes/tranches/tranche-label.spec.tsx @@ -1,10 +1,5 @@ import { render } from '@testing-library/react'; -import { - EthereumChainIds, - EnvironmentConfig, - Networks, -} from '@vegaprotocol/smart-contracts'; import type { TrancheLabelProps } from './tranche-label'; import { TrancheLabel } from './tranche-label'; @@ -12,14 +7,13 @@ let props: TrancheLabelProps; beforeEach(() => { props = { - chainId: EthereumChainIds.Mainnet, - contract: EnvironmentConfig[Networks.MAINNET].vestingAddress, + chainId: 1, id: 5, }; }); it('Renders null for right contract address, wrong network', () => { - const WRONG_CHAIN = EthereumChainIds.Goerli; + const WRONG_CHAIN = 3; const { container } = render( ); @@ -27,16 +21,6 @@ it('Renders null for right contract address, wrong network', () => { expect(container).toBeEmptyDOMElement(); }); -it('Renders null for right network, wrong contract address', () => { - const WRONG_ADDRESS = '0x0'; - - const { container } = render( - - ); - - expect(container).toBeEmptyDOMElement(); -}); - it('Renders null for right network, right contract address, tranche without a name', () => { const UNNAMED_TRANCHE = 0; diff --git a/apps/token/src/routes/tranches/tranche-label.tsx b/apps/token/src/routes/tranches/tranche-label.tsx index 54154e7f5..7a12ed1e2 100644 --- a/apps/token/src/routes/tranches/tranche-label.tsx +++ b/apps/token/src/routes/tranches/tranche-label.tsx @@ -1,7 +1,3 @@ -import type { EthereumChainId } from '@vegaprotocol/smart-contracts'; -import { EthereumChainIds } from '@vegaprotocol/smart-contracts'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; - const TRANCHE_LABELS: Record = { '5': ['Coinlist Option 1', 'Community Whitelist'], '6': ['Coinlist Option 2'], @@ -11,8 +7,7 @@ const TRANCHE_LABELS: Record = { }; export interface TrancheLabelProps { - chainId: EthereumChainId | null; - contract: string; + chainId: number | undefined; id: number; } @@ -26,14 +21,9 @@ export interface TrancheLabelProps { * @param chainId The ID of the chain this contract is on * @param id The tranche ID on this contract */ -export const TrancheLabel = ({ contract, chainId, id }: TrancheLabelProps) => { - const { ADDRESSES } = useEnvironment(); +export const TrancheLabel = ({ chainId, id }: TrancheLabelProps) => { // Only mainnet tranches on the known vesting contract have useful name - if ( - chainId && - chainId === EthereumChainIds.Mainnet && - contract === ADDRESSES.vestingAddress - ) { + if (chainId === 1) { // Only some tranches have titles worth showing if (TRANCHE_LABELS[id]) { return ( diff --git a/apps/token/src/routes/tranches/tranche.tsx b/apps/token/src/routes/tranches/tranche.tsx index 307287869..39b844c8a 100644 --- a/apps/token/src/routes/tranches/tranche.tsx +++ b/apps/token/src/routes/tranches/tranche.tsx @@ -1,7 +1,6 @@ -import type { - Tranche as ITranche, - EthereumChainId, -} from '@vegaprotocol/smart-contracts'; +import { useEnvironment } from '@vegaprotocol/react-helpers'; +import type { Tranche as ITranche } from '@vegaprotocol/smart-contracts'; +import { Link } from '@vegaprotocol/ui-toolkit'; import { useWeb3React } from '@web3-react/core'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -9,8 +8,6 @@ import { useParams } from 'react-router'; import { Navigate } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom'; -import { Link } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; import { BigNumber } from '../../lib/bignumber'; import { formatNumber } from '../../lib/format-number'; import { TrancheItem } from '../redemption/tranche-item'; @@ -29,7 +26,7 @@ const TrancheProgressContents = ({ export const Tranche = () => { const tranches = useOutletContext(); - const { ADDRESSES, ETHERSCAN_URL } = useEnvironment(); + const { ETHERSCAN_URL } = useEnvironment(); const { t } = useTranslation(); const { trancheId } = useParams<{ trancheId: string }>(); const { chainId } = useWeb3React(); @@ -58,11 +55,7 @@ export const Tranche = () => { unlocked={tranche.total_added.minus(tranche.locked_amount)} total={tranche.total_added} secondaryHeader={ - + } />
        !t.total_added.isLessThanOrEqualTo(trancheMinimum); export const Tranches = () => { - const { ADDRESSES } = useEnvironment(); const tranches = useOutletContext(); const [showAll, setShowAll] = React.useState(false); const { t } = useTranslation(); const { chainId } = useWeb3React(); + const { config } = useEthereumConfig(); const filteredTranches = tranches?.filter(shouldShowTranche) || []; + if (!config) { + return null; + } + return (

        {t('chartTitle')}

        @@ -41,11 +45,7 @@ export const Tranches = () => { unlocked={tranche.total_added.minus(tranche.locked_amount)} total={tranche.total_added} secondaryHeader={ - + } /> diff --git a/apps/token/src/stores/transactions.ts b/apps/token/src/stores/transactions.ts new file mode 100644 index 000000000..e6eefe4fa --- /dev/null +++ b/apps/token/src/stores/transactions.ts @@ -0,0 +1,42 @@ +import type ethers from 'ethers'; +import type { GetState, SetState } from 'zustand'; +import create from 'zustand'; + +export interface TxData { + tx: ethers.ContractTransaction; + receipt: ethers.ContractReceipt | null; + pending: boolean; + requiredConfirmations: number; +} + +interface TransactionStore { + transactions: Array; + add: (tx: TxData) => void; + update: (tx: TxData) => void; + remove: (tx: TxData) => void; +} + +export const useTransactionStore = create( + (set: SetState, get: GetState) => ({ + transactions: [], + add: (tx) => { + const { transactions } = get(); + set({ transactions: [...transactions, tx] }); + }, + update: (tx) => { + const { transactions } = get(); + set({ + transactions: [ + ...transactions.filter((t) => t.tx.hash !== tx.tx.hash), + tx, + ], + }); + }, + remove: (tx) => { + const { transactions } = get(); + set({ + transactions: transactions.filter((t) => t.tx.hash !== tx.tx.hash), + }); + }, + }) +); diff --git a/apps/trading-e2e/src/integration/deposits.feature b/apps/trading-e2e/src/integration/deposits.feature index b1025da0d..ae8c62401 100644 --- a/apps/trading-e2e/src/integration/deposits.feature +++ b/apps/trading-e2e/src/integration/deposits.feature @@ -1,7 +1,8 @@ Feature: Deposits to vega wallet Background: - Given I navigate to deposits page + Given I can connect to Ethereum + And I navigate to deposits page # wallet is already connected before tests start and doesn't prompt the disconnected state @ignore diff --git a/apps/trading-e2e/src/integration/withdrawals.feature b/apps/trading-e2e/src/integration/withdrawals.feature index 8a16cb629..1ec7ff5e7 100644 --- a/apps/trading-e2e/src/integration/withdrawals.feature +++ b/apps/trading-e2e/src/integration/withdrawals.feature @@ -1,7 +1,8 @@ Feature: Withdrawals to eth wallet Background: - Given I navigate to withdrawal page + Given I can connect to Ethereum + And I navigate to withdrawal page And I connect to Vega Wallet Scenario: Succesfull withdrawal diff --git a/apps/trading-e2e/src/support/step_definitions/common.step.ts b/apps/trading-e2e/src/support/step_definitions/common.step.ts index 5cf95f90f..8e163ad43 100644 --- a/apps/trading-e2e/src/support/step_definitions/common.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/common.step.ts @@ -19,3 +19,7 @@ Given('I am on the homepage', () => { basePage.closeDialog(); marketPage.validateMarketsAreDisplayed(); }); + +Given('I can connect to Ethereum', () => { + cy.mockWeb3Provider(); +}); diff --git a/apps/trading-e2e/src/support/step_definitions/deposits.step.ts b/apps/trading-e2e/src/support/step_definitions/deposits.step.ts index 025264a13..4f28bd189 100644 --- a/apps/trading-e2e/src/support/step_definitions/deposits.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/deposits.step.ts @@ -5,10 +5,6 @@ import DepositsPage from '../pages/deposits-page'; const depositsPage = new DepositsPage(); const ethWallet = new EthereumWallet(); -beforeEach(() => { - cy.mockWeb3Provider(); -}); - Then('I navigate to deposits page', () => { depositsPage.navigateToDeposits(); }); diff --git a/apps/trading/components/web3-container/web3-container.spec.tsx b/apps/trading/components/web3-container/web3-container.spec.tsx index 04028f99e..1d2a20f63 100644 --- a/apps/trading/components/web3-container/web3-container.spec.tsx +++ b/apps/trading/components/web3-container/web3-container.spec.tsx @@ -1,9 +1,10 @@ import { fireEvent, render, screen } from '@testing-library/react'; import type { MockedResponse } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing'; -import { NETWORK_PARAMS_QUERY, Web3Container } from './web3-container'; -import type { NetworkParamsQuery } from './__generated__/NetworkParamsQuery'; +import { Web3Container } from './web3-container'; import type { useWeb3React } from '@web3-react/core'; +import type { NetworkParamsQuery } from '@vegaprotocol/web3'; +import { NETWORK_PARAMS_QUERY } from '@vegaprotocol/web3'; const defaultHookValue = { isActive: false, @@ -50,14 +51,12 @@ jest.mock('@web3-react/core', () => { function setup(mock = networkParamsQueryMock) { return render( - ( -
        -
        Child
        -
        {ethereumConfig.collateral_bridge_contract.address}
        -
        - )} - /> + +
        +
        Child
        +
        {mockEthereumConfig.collateral_bridge_contract.address}
        +
        +
        ); } @@ -140,29 +139,5 @@ it('Shows no config found message if the network parameter doesnt exist', async }; setup(mock); expect(screen.getByText('Loading...')).toBeInTheDocument(); - expect( - await screen.findByText('No ethereum config found') - ).toBeInTheDocument(); -}); - -it('Shows message if ethereum config could not be parsed', async () => { - const mock: MockedResponse = { - request: { - query: NETWORK_PARAMS_QUERY, - }, - result: { - data: { - networkParameters: [ - { - __typename: 'NetworkParameter', - key: 'blockchains.ethereumConfig', - value: '"something invalid }', - }, - ], - }, - }, - }; - setup(mock); - expect(screen.getByText('Loading...')).toBeInTheDocument(); - expect(await screen.findByText('Could not parse config')).toBeInTheDocument(); + expect(await screen.findByText('No data')).toBeInTheDocument(); }); diff --git a/apps/trading/components/web3-container/web3-container.tsx b/apps/trading/components/web3-container/web3-container.tsx index 2b777c8b2..9ce54ca95 100644 --- a/apps/trading/components/web3-container/web3-container.tsx +++ b/apps/trading/components/web3-container/web3-container.tsx @@ -1,96 +1,42 @@ -import { gql } from '@apollo/client'; -import type { NetworkParamsQuery } from './__generated__/NetworkParamsQuery'; -import { Button, Splash } from '@vegaprotocol/ui-toolkit'; -import { Web3Provider, Web3ConnectDialog } from '@vegaprotocol/web3'; +import { AsyncRenderer, Button, Splash } from '@vegaprotocol/ui-toolkit'; +import { + Web3Provider, + Web3ConnectDialog, + useEthereumConfig, +} from '@vegaprotocol/web3'; import { useWeb3React } from '@web3-react/core'; import type { ReactNode } from 'react'; import { useEffect, useState } from 'react'; import { Connectors } from '../../lib/web3-connectors'; -import { PageQueryContainer } from '../page-query-container'; import { t } from '@vegaprotocol/react-helpers'; -export interface EthereumConfig { - network_id: string; - chain_id: string; - confirmations: number; - collateral_bridge_contract: { - address: string; - }; - multisig_control_contract: { - address: string; - deployment_block_height: number; - }; - staking_bridge_contract: { - address: string; - deployment_block_height: number; - }; - token_vesting_contract: { - address: string; - deployment_block_height: number; - }; -} - -export const NETWORK_PARAMS_QUERY = gql` - query NetworkParamsQuery { - networkParameters { - key - value - } - } -`; - interface Web3ContainerProps { - render: (params: { ethereumConfig: EthereumConfig }) => ReactNode; + children: ReactNode; } -export const Web3Container = ({ render }: Web3ContainerProps) => { +export const Web3Container = ({ children }: Web3ContainerProps) => { const [dialogOpen, setDialogOpen] = useState(false); + const { config, loading, error } = useEthereumConfig(); + return ( - - query={NETWORK_PARAMS_QUERY} - render={(data) => { - const ethereumConfigParam = data.networkParameters?.find( - (np) => np.key === 'blockchains.ethereumConfig' - ); - - if (!ethereumConfigParam) { - return ( - -

        {t('No ethereum config found')}

        -
        - ); - } - - let ethereumConfig: EthereumConfig; - - try { - ethereumConfig = JSON.parse(ethereumConfigParam.value); - } catch { - return ( - -

        {t('Could not parse config')}

        -
        - ); - } - - return ( - - - {render({ ethereumConfig })} - - - - ); - }} - /> + + {config ? ( + + + {children} + + + + ) : null} + ); }; diff --git a/apps/trading/pages/portfolio/deposit/deposit-container.tsx b/apps/trading/pages/portfolio/deposit/deposit-container.tsx index 81672dc62..27c11f2ff 100644 --- a/apps/trading/pages/portfolio/deposit/deposit-container.tsx +++ b/apps/trading/pages/portfolio/deposit/deposit-container.tsx @@ -1,4 +1,3 @@ -import type { EthereumConfig } from '../../../components/web3-container/web3-container'; import { gql } from '@apollo/client'; import { PageQueryContainer } from '../../../components/page-query-container'; import type { DepositPage } from './__generated__/DepositPage'; @@ -17,17 +16,13 @@ const DEPOSIT_PAGE_QUERY = gql` `; interface DepositContainerProps { - ethereumConfig: EthereumConfig; assetId?: string; } /** * Fetches data required for the Deposit page */ -export const DepositContainer = ({ - ethereumConfig, - assetId, -}: DepositContainerProps) => { +export const DepositContainer = ({ assetId }: DepositContainerProps) => { const { VEGA_ENV } = useEnvironment(); return ( @@ -44,8 +39,6 @@ export const DepositContainer = ({ return ( { }, [query]); return ( - ( -
        -

        Deposit

        - -
        - )} - /> + +
        +

        Deposit

        + +
        +
        ); }; diff --git a/apps/trading/pages/portfolio/index.page.tsx b/apps/trading/pages/portfolio/index.page.tsx index fd1618e6e..a362bebea 100644 --- a/apps/trading/pages/portfolio/index.page.tsx +++ b/apps/trading/pages/portfolio/index.page.tsx @@ -12,68 +12,66 @@ const Portfolio = () => { const tabClassName = 'p-[16px] pl-[316px]'; return ( - ( -
        -
        - -
        - - -
        -

        - {t('Positions')} -

        - -
        -
        - -
        -

        - {t('Orders')} -

        - -
        -
        - -
        -

        - {t('Fills')} -

        -
        -
        - -
        -

        - {t('History')} -

        -
        -
        -
        -
        -
        -
        - - - + +
        +
        + +
        + + +
        +

        + {t('Positions')} +

        + +
        - - - {t('Deposit')} - + +
        +

        + {t('Orders')} +

        + +
        - - + +
        +

        + {t('Fills')} +

        +
        +
        + +
        +

        + {t('History')} +

        +
        -
        - )} - /> + +
        + + + + + + + {t('Deposit')} + + + + + + +
        +
        +
        ); }; diff --git a/apps/trading/pages/portfolio/withdraw/index.page.tsx b/apps/trading/pages/portfolio/withdraw/index.page.tsx index 99ed4b17b..ef7c41dbf 100644 --- a/apps/trading/pages/portfolio/withdraw/index.page.tsx +++ b/apps/trading/pages/portfolio/withdraw/index.page.tsx @@ -23,14 +23,12 @@ const Withdraw = () => { return ( - ( -
        -

        {t('Withdraw')}

        - -
        - )} - /> + +
        +

        {t('Withdraw')}

        + +
        +
        ); }; diff --git a/apps/trading/pages/portfolio/withdrawals/index.page.tsx b/apps/trading/pages/portfolio/withdrawals/index.page.tsx index 2c6af9058..69e205818 100644 --- a/apps/trading/pages/portfolio/withdrawals/index.page.tsx +++ b/apps/trading/pages/portfolio/withdrawals/index.page.tsx @@ -7,19 +7,17 @@ import { WithdrawalsContainer } from './withdrawals-container'; const Withdrawals = () => { return ( - ( -
        -
        -

        {t('Withdrawals')}

        - - {t('Start withdrawal')} - -
        - -
        - )} - /> + +
        +
        +

        {t('Withdrawals')}

        + + {t('Start withdrawal')} + +
        + +
        +
        ); }; diff --git a/libs/deposits/src/lib/deposit-manager.tsx b/libs/deposits/src/lib/deposit-manager.tsx index 9d879eec8..21efb1cb8 100644 --- a/libs/deposits/src/lib/deposit-manager.tsx +++ b/libs/deposits/src/lib/deposit-manager.tsx @@ -7,8 +7,13 @@ import { useSubmitApproval } from './use-submit-approval'; import { useGetDepositLimits } from './use-get-deposit-limits'; import { useGetAllowance } from './use-get-allowance'; import { useSubmitFaucet } from './use-submit-faucet'; -import { EthTxStatus, TransactionDialog } from '@vegaprotocol/web3'; -import { useTokenContract, useBridgeContract } from '@vegaprotocol/web3'; +import { + EthTxStatus, + TransactionDialog, + useEthereumConfig, + useTokenDecimals, +} from '@vegaprotocol/web3'; +import { useTokenContract } from '@vegaprotocol/web3'; interface ERC20AssetSource { __typename: 'ERC20'; @@ -30,16 +35,12 @@ export interface Asset { } interface DepositManagerProps { - requiredConfirmations: number; - bridgeAddress: string; assets: Asset[]; initialAssetId?: string; isFaucetable?: boolean; } export const DepositManager = ({ - requiredConfirmations, - bridgeAddress, assets, initialAssetId, isFaucetable, @@ -52,31 +53,34 @@ export const DepositManager = ({ return asset; }, [assets, assetId]); + const { config } = useEthereumConfig(); + const tokenContract = useTokenContract( asset?.source.__typename === 'ERC20' ? asset.source.contractAddress : undefined, isFaucetable ); - const bridgeContract = useBridgeContract(); + + const decimals = useTokenDecimals(tokenContract); // Get users balance of the erc20 token selected - const { balanceOf, refetch } = useGetBalanceOfERC20Token(tokenContract); + const { balance, refetch } = useGetBalanceOfERC20Token( + tokenContract, + decimals + ); // Get temporary deposit limits - const limits = useGetDepositLimits(bridgeContract, asset); + const limits = useGetDepositLimits(asset, decimals); // Get allowance (approved spending limit of brdige contract) for the selected asset - const allowance = useGetAllowance(tokenContract, bridgeAddress); + const allowance = useGetAllowance(tokenContract, decimals); // Set up approve transaction - const approve = useSubmitApproval(tokenContract, bridgeAddress); + const approve = useSubmitApproval(tokenContract); // Set up deposit transaction - const { confirmationEvent, ...deposit } = useSubmitDeposit( - bridgeContract, - requiredConfirmations - ); + const { confirmationEvent, ...deposit } = useSubmitDeposit(); // Set up faucet transaction const faucet = useSubmitFaucet(tokenContract); @@ -94,7 +98,7 @@ export const DepositManager = ({ return ( <> setAssetId(id)} assets={sortBy(assets, 'name')} @@ -112,7 +116,7 @@ export const DepositManager = ({ name="deposit" confirmed={Boolean(confirmationEvent)} // Must wait for additional confirmations for Vega to pick up the Ethereum transaction - requiredConfirmations={requiredConfirmations} + requiredConfirmations={config?.confirmations} /> ); diff --git a/libs/deposits/src/lib/use-get-allowance.ts b/libs/deposits/src/lib/use-get-allowance.ts index 842bcdac4..73a2203bc 100644 --- a/libs/deposits/src/lib/use-get-allowance.ts +++ b/libs/deposits/src/lib/use-get-allowance.ts @@ -1,24 +1,31 @@ -import type { ERC20Token } from '@vegaprotocol/smart-contracts'; +import type { Token } from '@vegaprotocol/smart-contracts'; import { useWeb3React } from '@web3-react/core'; import { useCallback } from 'react'; -import { useEthereumReadContract } from '@vegaprotocol/web3'; +import { useEthereumConfig, useEthereumReadContract } from '@vegaprotocol/web3'; +import BigNumber from 'bignumber.js'; +import { addDecimal } from '@vegaprotocol/react-helpers'; -export const useGetAllowance = ( - contract: ERC20Token | null, - bridgeAddress: string -) => { +export const useGetAllowance = (contract: Token | null, decimals?: number) => { const { account } = useWeb3React(); + const { config } = useEthereumConfig(); const getAllowance = useCallback(() => { - if (!contract || !account) { + if (!contract || !account || !config) { return; } - return contract.allowance(account, bridgeAddress); - }, [contract, account, bridgeAddress]); + return contract.allowance( + account, + config.collateral_bridge_contract.address + ); + }, [contract, account, config]); const { state: { data }, } = useEthereumReadContract(getAllowance); - return data; + if (!data || !decimals) return; + + const allowance = new BigNumber(addDecimal(data.toString(), decimals)); + + return allowance; }; diff --git a/libs/deposits/src/lib/use-get-balance-of-erc20-token.ts b/libs/deposits/src/lib/use-get-balance-of-erc20-token.ts index cf6ba27d1..0d507c33d 100644 --- a/libs/deposits/src/lib/use-get-balance-of-erc20-token.ts +++ b/libs/deposits/src/lib/use-get-balance-of-erc20-token.ts @@ -1,9 +1,14 @@ import { useEthereumReadContract } from '@vegaprotocol/web3'; -import type { ERC20Token } from '@vegaprotocol/smart-contracts'; +import type { Token } from '@vegaprotocol/smart-contracts'; import { useWeb3React } from '@web3-react/core'; import { useCallback } from 'react'; +import BigNumber from 'bignumber.js'; +import { addDecimal } from '@vegaprotocol/react-helpers'; -export const useGetBalanceOfERC20Token = (contract: ERC20Token | null) => { +export const useGetBalanceOfERC20Token = ( + contract: Token | null, + decimals: number | undefined +) => { const { account } = useWeb3React(); const getBalance = useCallback(() => { @@ -16,5 +21,10 @@ export const useGetBalanceOfERC20Token = (contract: ERC20Token | null) => { const { state, refetch } = useEthereumReadContract(getBalance); - return { balanceOf: state.data, refetch }; + const balance = + state.data && decimals + ? new BigNumber(addDecimal(state.data?.toString(), decimals)) + : undefined; + + return { balance, refetch }; }; diff --git a/libs/deposits/src/lib/use-get-deposit-limits.ts b/libs/deposits/src/lib/use-get-deposit-limits.ts index dcc8b4332..c6c7c130c 100644 --- a/libs/deposits/src/lib/use-get-deposit-limits.ts +++ b/libs/deposits/src/lib/use-get-deposit-limits.ts @@ -1,37 +1,33 @@ -import type BigNumber from 'bignumber.js'; import { useCallback } from 'react'; -import type { VegaErc20Bridge } from '@vegaprotocol/smart-contracts'; import type { Asset } from './deposit-manager'; -import { useEthereumReadContract } from '@vegaprotocol/web3'; +import { useBridgeContract, useEthereumReadContract } from '@vegaprotocol/web3'; +import BigNumber from 'bignumber.js'; +import { addDecimal } from '@vegaprotocol/react-helpers'; -interface Limits { - min: BigNumber; - max: BigNumber; -} - -export const useGetDepositLimits = ( - contract: VegaErc20Bridge | null, - asset?: Asset -): Limits | null => { +export const useGetDepositLimits = (asset?: Asset, decimals?: number) => { + const contract = useBridgeContract(); const getLimits = useCallback(async () => { if (!contract || !asset || asset.source.__typename !== 'ERC20') { return; } return Promise.all([ - contract.getDepositMinimum(asset.source.contractAddress, asset.decimals), - contract.getDepositMaximum(asset.source.contractAddress, asset.decimals), + contract.getDepositMinimum(asset.source.contractAddress), + contract.getDepositMaximum(asset.source.contractAddress), ]); }, [asset, contract]); const { state: { data }, - } = useEthereumReadContract<[BigNumber, BigNumber] | undefined>(getLimits); + } = useEthereumReadContract(getLimits); - if (!data) return null; + if (!data || !decimals) return null; + + const min = new BigNumber(addDecimal(data[0].toString(), decimals)); + const max = new BigNumber(addDecimal(data[1].toString(), decimals)); return { - min: data[0], - max: data[1], + min, + max: max.isEqualTo(0) ? new BigNumber(Infinity) : max, }; }; diff --git a/libs/deposits/src/lib/use-submit-approval.ts b/libs/deposits/src/lib/use-submit-approval.ts index 9dc1f6054..2ad905071 100644 --- a/libs/deposits/src/lib/use-submit-approval.ts +++ b/libs/deposits/src/lib/use-submit-approval.ts @@ -1,15 +1,17 @@ -import type { ERC20Token } from '@vegaprotocol/smart-contracts'; -import { useEthereumTransaction } from '@vegaprotocol/web3'; +import type { Token } from '@vegaprotocol/smart-contracts'; +import { useEthereumConfig, useEthereumTransaction } from '@vegaprotocol/web3'; + +export const useSubmitApproval = (contract: Token | null) => { + const { config } = useEthereumConfig(); -export const useSubmitApproval = ( - contract: ERC20Token | null, - bridgeAddress: string -) => { const transaction = useEthereumTransaction(() => { - if (!contract) { + if (!contract || !config) { return null; } - return contract.approve(bridgeAddress); + return contract.approve( + config.collateral_bridge_contract.address, + Number.MAX_SAFE_INTEGER.toString() + ); }); return transaction; diff --git a/libs/deposits/src/lib/use-submit-deposit.ts b/libs/deposits/src/lib/use-submit-deposit.ts index 5ae56d7bf..9a65a6fda 100644 --- a/libs/deposits/src/lib/use-submit-deposit.ts +++ b/libs/deposits/src/lib/use-submit-deposit.ts @@ -7,8 +7,11 @@ import type { import { DepositStatus } from '@vegaprotocol/types'; import { useState } from 'react'; import { remove0x } from '@vegaprotocol/react-helpers'; -import { useEthereumTransaction } from '@vegaprotocol/web3'; -import type { VegaErc20Bridge } from '@vegaprotocol/smart-contracts'; +import { + useBridgeContract, + useEthereumConfig, + useEthereumTransaction, +} from '@vegaprotocol/web3'; const DEPOSIT_EVENT_SUB = gql` subscription DepositEvent($partyId: ID!) { @@ -24,10 +27,9 @@ const DEPOSIT_EVENT_SUB = gql` } `; -export const useSubmitDeposit = ( - contract: VegaErc20Bridge | null, - confirmations: number -) => { +export const useSubmitDeposit = () => { + const { config } = useEthereumConfig(); + const contract = useBridgeContract(); const [confirmationEvent, setConfirmationEvent] = useState(null); // Store public key from contract arguments for use in the subscription, @@ -52,7 +54,7 @@ export const useSubmitDeposit = ( args.amount, args.vegaPublicKey ); - }, confirmations); + }, config?.confirmations); useSubscription(DEPOSIT_EVENT_SUB, { variables: { partyId: partyId ? remove0x(partyId) : '' }, diff --git a/libs/deposits/src/lib/use-submit-faucet.ts b/libs/deposits/src/lib/use-submit-faucet.ts index 73b53e76c..d0bf94ced 100644 --- a/libs/deposits/src/lib/use-submit-faucet.ts +++ b/libs/deposits/src/lib/use-submit-faucet.ts @@ -1,9 +1,10 @@ -import type { ERC20Token } from '@vegaprotocol/smart-contracts'; +import { Token } from '@vegaprotocol/smart-contracts'; +import type { TokenFaucetable } from '@vegaprotocol/smart-contracts'; import { useEthereumTransaction } from '@vegaprotocol/web3'; -export const useSubmitFaucet = (contract: ERC20Token | null) => { +export const useSubmitFaucet = (contract: Token | TokenFaucetable | null) => { const transaction = useEthereumTransaction(() => { - if (!contract) { + if (!contract || contract instanceof Token) { return null; } return contract.faucet(); diff --git a/libs/react-helpers/src/hooks/use-environment.tsx b/libs/react-helpers/src/hooks/use-environment.tsx index dc00c57fe..fdf77f7f5 100644 --- a/libs/react-helpers/src/hooks/use-environment.tsx +++ b/libs/react-helpers/src/hooks/use-environment.tsx @@ -1,15 +1,53 @@ import type { ReactNode } from 'react'; -import type { Networks } from '@vegaprotocol/smart-contracts'; -import { EnvironmentConfig } from '@vegaprotocol/smart-contracts'; import { createContext, useContext } from 'react'; +import type { Networks } from '../lib/environment'; declare global { interface Window { _ENV?: RawEnvironment; } } +const customVegaTokenAddress = process.env['CUSTOM_TOKEN_ADDRESS'] as string; +const customClaimAddress = process.env['CUSTOM_CLAIM_ADDRESS'] as string; +const customLockedAddress = process.env['CUSTOM_LOCKED_ADDRESS'] as string; +interface VegaContracts { + vegaTokenAddress: string; + claimAddress: string; + lockedAddress: string; +} -type VegaContracts = typeof EnvironmentConfig[Networks]; +export const ContractAddresses: { [key in Networks]: VegaContracts } = { + CUSTOM: { + vegaTokenAddress: customVegaTokenAddress, + claimAddress: customClaimAddress, + lockedAddress: customLockedAddress, + }, + DEVNET: { + vegaTokenAddress: '0xc93137f9F4B820Ca85FfA3C7e84cCa6Ebc7bB517', + claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', + lockedAddress: '0x0', + }, + STAGNET: { + vegaTokenAddress: '0x547cbA83a7eb82b546ee5C7ff0527F258Ba4546D', + claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error + lockedAddress: '0x0', // TODO not deployed to this env + }, + STAGNET2: { + vegaTokenAddress: '0xd8fa193B93a179DdCf51FFFDe5320E0872cdcf44', + claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error + lockedAddress: '0x0', // TODO not deployed to this env + }, + TESTNET: { + vegaTokenAddress: '0xDc335304979D378255015c33AbFf09B60c31EBAb', + claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error + lockedAddress: '0x0', // TODO not deployed to this env + }, + MAINNET: { + vegaTokenAddress: '0xcB84d72e61e383767C4DFEb2d8ff7f4FB89abc6e', + claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3', + lockedAddress: '0x78344c7305d73a7a0ac3c94cd9960f4449a1814e', + }, +}; type EnvironmentProviderProps = { definintions?: Partial; @@ -106,7 +144,7 @@ export const EnvironmentProvider = ({ {children} diff --git a/libs/react-helpers/src/index.ts b/libs/react-helpers/src/index.ts index 87c27d7b9..1ed3710ae 100644 --- a/libs/react-helpers/src/index.ts +++ b/libs/react-helpers/src/index.ts @@ -1,5 +1,6 @@ export * from './hooks'; export * from './lib/context'; +export * from './lib/environment'; export * from './lib/format'; export * from './lib/generic-data-provider'; export * from './lib/grid'; diff --git a/libs/smart-contracts/src/config/vega.ts b/libs/react-helpers/src/lib/environment.ts similarity index 100% rename from libs/smart-contracts/src/config/vega.ts rename to libs/react-helpers/src/lib/environment.ts diff --git a/libs/react-helpers/src/lib/format/number.ts b/libs/react-helpers/src/lib/format/number.ts index 7504ee2ac..1a5b5559b 100644 --- a/libs/react-helpers/src/lib/format/number.ts +++ b/libs/react-helpers/src/lib/format/number.ts @@ -1,4 +1,5 @@ import { BigNumber } from 'bignumber.js'; +import { BigNumber as EthersBigNumber } from 'ethers'; import memoize from 'lodash/memoize'; import { getUserLocale } from './utils'; @@ -6,15 +7,22 @@ export function toDecimal(numberOfDecimals: number) { return Math.pow(10, -numberOfDecimals); } +export function toBigNum( + rawValue: string | number | EthersBigNumber, + decimals: number +): BigNumber { + return new BigNumber( + rawValue instanceof EthersBigNumber ? rawValue.toString() : rawValue || 0 + ).dividedBy(Math.pow(10, decimals)); +} + export function addDecimal( - value: string | number, + value: string | number | EthersBigNumber, decimals: number, decimalPrecision = decimals ): string { if (!decimals) return value.toString(); - return new BigNumber(value || 0) - .dividedBy(Math.pow(10, decimals)) - .toFixed(decimalPrecision); + return toBigNum(value, decimals).toFixed(decimalPrecision); } export function removeDecimal(value: string, decimals: number): string { diff --git a/libs/smart-contracts/src/config/ethereum.ts b/libs/smart-contracts/src/config/ethereum.ts deleted file mode 100644 index 49675ba05..000000000 --- a/libs/smart-contracts/src/config/ethereum.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Networks } from './vega'; - -const customVegaTokenAddress = process.env.CUSTOM_TOKEN_ADDRESS as string; -const customClaimAddress = process.env.CUSTOM_CLAIM_ADDRESS as string; -const customLockedAddress = process.env.CUSTOM_LOCKED_ADDRESS as string; -const customVestingAddress = process.env.CUSTOM_VESTING_ADDRESS as string; -const customStakingBridge = process.env.CUSTOM_STAKING_BRIDGE as string; -const customErc20Bridge = process.env.CUSTOM_ERC20_BRIDGE as string; - -export type EthereumChainId = '0x1' | '0x3' | '0x4' | '0x5' | '0x2a'; - -export type EthereumChainName = - | 'Mainnet' - | 'Ropsten' - | 'Rinkeby' - | 'Goerli' - | 'Kovan'; - -export const EthereumChainNames: Record = { - '0x1': 'Mainnet', - '0x3': 'Ropsten', - '0x4': 'Rinkeby', - '0x5': 'Goerli', - '0x2a': 'Kovan', -}; - -export const EthereumChainIds: Record = { - Mainnet: '0x1', - Ropsten: '0x3', - Rinkeby: '0x4', - Goerli: '0x5', - Kovan: '0x2a', -}; - -export const ChainIdMap: Record = { - '0x1': 1, - '0x3': 3, - '0x4': 4, - '0x5': 5, - '0x2a': 42, -}; -interface VegaContracts { - vestingAddress: string; - vegaTokenAddress: string; - claimAddress: string; - lockedAddress: string; - stakingBridge: string; - erc20Bridge: string; -} - -export const EnvironmentConfig: { [key in Networks]: VegaContracts } = { - [Networks.CUSTOM]: { - vegaTokenAddress: customVegaTokenAddress, - claimAddress: customClaimAddress, - lockedAddress: customLockedAddress, - vestingAddress: customVestingAddress, - stakingBridge: customStakingBridge, - erc20Bridge: customErc20Bridge, - }, - [Networks.DEVNET]: { - vegaTokenAddress: '0xc93137f9F4B820Ca85FfA3C7e84cCa6Ebc7bB517', - claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', - lockedAddress: '0x0', - vestingAddress: '0xd1216AAb948f5FC706Df73df6d71c64CcaA8550a', - stakingBridge: '0xf2cD5C8b8c52f96293338A0AF463a0Bfc602D5bc', - erc20Bridge: '0xE43013C3c2A134AB3782ADEb258669A8566DAD57', - }, - [Networks.STAGNET]: { - vegaTokenAddress: '0x547cbA83a7eb82b546ee5C7ff0527F258Ba4546D', - claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error - lockedAddress: '0x0', // TODO not deployed to this env - vestingAddress: '0xfCe6eB272D3d4146A96bC28de71212b327F575fa', - stakingBridge: '0x7D88CD817227D599815d407D929af18Bb8D57176', - erc20Bridge: '0xc0835e6dEf177F8ba2561C4e4216827A3798c6B9', - }, - [Networks.STAGNET2]: { - vegaTokenAddress: '0xd8fa193B93a179DdCf51FFFDe5320E0872cdcf44', - claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error - lockedAddress: '0x0', // TODO not deployed to this env - vestingAddress: '0x9F10cBeEf03A564Fb914c2010c0Cd55E9BB11406', - stakingBridge: '0x7896C9491962D5839783CB6e0492ECebd34Bb35F', - erc20Bridge: '0xffC1eb64e22fd5E29816c633eE84088EEEe879E5', - }, - [Networks.TESTNET]: { - vegaTokenAddress: '0xDc335304979D378255015c33AbFf09B60c31EBAb', - claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error - lockedAddress: '0x0', // TODO not deployed to this env - vestingAddress: '0xe2deBB240b43EDfEBc9c38B67c0894B9A92Bf07c', - stakingBridge: '0xF5A3830F002BE78dd801214F5316b677E0355c60', - erc20Bridge: '0xF009C66c6afC9661143fD7cE1eDb02c1961a6510', - }, - [Networks.MAINNET]: { - vegaTokenAddress: '0xcB84d72e61e383767C4DFEb2d8ff7f4FB89abc6e', - claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3', - lockedAddress: '0x78344c7305d73a7a0ac3c94cd9960f4449a1814e', - vestingAddress: '0x23d1bFE8fA50a167816fBD79D7932577c06011f4', - stakingBridge: '0x195064D33f09e0c42cF98E665D9506e0dC17de68', - erc20Bridge: '0xCd403f722b76366f7d609842C589906ca051310f', - }, -}; - -// No concept of dev/staging/test for these right now. -export const RewardsAddresses = { - [EthereumChainIds.Mainnet]: { - 'SushiSwap VEGA/ETH': '0x285de24077440c53b1661287D170e3ae22de0a44', - 'SushiSwap VEGA/USDC': '0x49407c243c26f109b3c77c41dd83742164c20b5f', - } as { [key: string]: string }, - [EthereumChainIds.Ropsten]: { - 'SushiSwap VEGA/ETH': '0xa93dd6912897c5fe8503a82234d829bc7905714b', - 'SushiSwap VEGA/USDC': '0xa93dd6912897c5fe8503a82234d829bc7905714b', - } as { [key: string]: string }, -}; - -export const RewardsPoolAddresses = { - [EthereumChainIds.Mainnet]: { - '0x285de24077440c53b1661287D170e3ae22de0a44': - '0x29c827ce49accf68a1a278c67c9d30c52fbbc348', - '0x49407c243c26f109b3c77c41dd83742164c20b5f': - '0x42b7B8f8F83fA5cbf0176f8c24Ad51EbcD4B5F17', - } as { [key: string]: string }, - [EthereumChainIds.Ropsten]: { - // Only one deployed to this environment - '0xa93dd6912897c5fe8503a82234d829bc7905714b': - '0x29c827ce49accf68a1a278c67c9d30c52fbbc348', - } as { [key: string]: string }, -}; diff --git a/libs/smart-contracts/src/config/index.ts b/libs/smart-contracts/src/config/index.ts deleted file mode 100644 index 51cb66024..000000000 --- a/libs/smart-contracts/src/config/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ethereum'; -export * from './vega'; diff --git a/libs/smart-contracts/src/contracts/base-contract.ts b/libs/smart-contracts/src/contracts/base-contract.ts deleted file mode 100644 index 954aaa452..000000000 --- a/libs/smart-contracts/src/contracts/base-contract.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { ethers } from 'ethers'; -import type { TxData } from '.'; - -export class BaseContract { - public signer: ethers.Signer | null = null; - public provider: ethers.providers.Provider; - public transactions: TxData[] = []; - public listeners: ((transactions: TxData[]) => void)[] = []; - - constructor(provider: ethers.providers.Provider, signer?: ethers.Signer) { - this.provider = provider; - this.signer = signer || null; - } - - async handleEvent(event: ethers.Event, requiredConfirmations = 1) { - const tx = await event.getTransaction(); - // start tracking transaction if its not already in the transactions array - const existing = this.transactions.find((t) => t.tx.hash === tx.hash); - if (!existing) { - this.trackTransaction(tx, requiredConfirmations); - } - } - - async trackTransaction( - tx: ethers.providers.TransactionResponse, - requiredConfirmations = 1 - ) { - this.mergeTransaction({ - tx, - receipt: null, - pending: true, - requiredConfirmations, - }); - - let receipt = null; - - for (let i = 1; i <= requiredConfirmations; i++) { - receipt = await tx.wait(i); - this.mergeTransaction({ - tx, - receipt, - pending: true, - requiredConfirmations, - }); - } - - this.mergeTransaction({ - tx, - receipt, - pending: false, - requiredConfirmations, - }); - } - - private mergeTransaction(tx: TxData) { - this.transactions = [ - // Replace any existing transaction in the array with this one - ...this.transactions.filter((t) => t.tx.hash !== tx.tx.hash), - tx, - ]; - this.emit(); - } - - emit() { - this.listeners.forEach((ln) => { - ln(this.transactions); - }); - } - - listen(cb: (txs: TxData[]) => void) { - this.listeners.push(cb); - } -} diff --git a/libs/smart-contracts/src/contracts/vega-claim.ts b/libs/smart-contracts/src/contracts/claim.ts similarity index 51% rename from libs/smart-contracts/src/contracts/vega-claim.ts rename to libs/smart-contracts/src/contracts/claim.ts index 6ca34ec7c..998e0e530 100644 --- a/libs/smart-contracts/src/contracts/vega-claim.ts +++ b/libs/smart-contracts/src/contracts/claim.ts @@ -1,11 +1,6 @@ -import type BigNumber from 'bignumber.js'; import { ethers } from 'ethers'; -import { EnvironmentConfig } from '../config/ethereum'; -import type { Networks } from '../config/vega'; -import claimAbi from '../abis/claim_abi.json'; -import tokenAbi from '../abis/vega_token_abi.json'; -import { asciiToHex, removeDecimal } from '../utils'; -import { BaseContract } from './base-contract'; +import abi from '../abis/claim_abi.json'; +import { asciiToHex } from '../utils'; export const UNSPENT_CODE = '0x0000000000000000000000000000000000000000'; export const SPENT_CODE = '0x0000000000000000000000000000000000000001'; @@ -16,55 +11,27 @@ export const SPENT_CODE = '0x0000000000000000000000000000000000000001'; * const provider = new Web3.providers.HttpProvider( * "https://ropsten.infura.io/v3/5aff9e61ad844bcf982d0d0c3f1d29f1" * ); - * const web3 = new Web3(provider); - * + * const web3 = new Web3(provider) + * // Ropsten address * const contract = new VegaClaim(web3, "0xAf5dC1772714b2F4fae3b65eb83100f1Ea677b21") * contract.isCountryBlocked("US").then(console.log) * contract.isClaimValid({ claimCode: "0x...", expiry: 0, nonce: "0x00", account: "0x00" }) * ``` */ -export class VegaClaim extends BaseContract { +export class Claim { public contract: ethers.Contract; - public tokenContract: ethers.Contract; - public dp: Promise; constructor( - network: Networks, - provider: ethers.providers.Web3Provider, - signer?: ethers.Signer + address: string, + signerOrProvider: ethers.Signer | ethers.providers.Provider ) { - super(provider, signer); - - this.contract = new ethers.Contract( - EnvironmentConfig[network].claimAddress, - claimAbi, - this.signer || this.provider - ); - - const tokenContract = new ethers.Contract( - EnvironmentConfig[network].vegaTokenAddress, - tokenAbi, - this.signer || this.provider - ); - this.tokenContract = tokenContract; - - this.dp = (async () => { - const val = await tokenContract.decimals(); - return Number(val); - })(); + this.contract = new ethers.Contract(address, abi, signerOrProvider); } /** Execute contracts commit_untargeted function */ - async commit( - s: string, - confirmations = 1 - ): Promise { - const tx = await this.contract.commit_untargeted(s); - - this.trackTransaction(tx, confirmations); - - return tx; + public commit(s: string): Promise { + return this.contract.commit_untargeted(s); } /** @@ -73,36 +40,32 @@ export class VegaClaim extends BaseContract { * was performed and mined beforehand * @return {Promise} */ - public async claim( - { - amount, - tranche, - expiry, - target, - country, - v, - r, - s, - }: { - amount: BigNumber; - tranche: number; - expiry: number; - target?: string; - country: string; - v: number; - r: string; - s: string; - }, - confirmations = 1 - ): Promise { - const convertedAmount = removeDecimal(amount, await this.dp).toString(); - const tx = await this.contract[ + public claim({ + amount, + tranche, + expiry, + target, + country, + v, + r, + s, + }: { + amount: string; + tranche: number; + expiry: number; + target?: string; + country: string; + v: number; + r: string; + s: string; + }): Promise { + return this.contract[ target != null ? 'claim_targeted' : 'claim_untargeted' ]( ...[ { r, s, v }, { - amount: convertedAmount, + amount, tranche, expiry, }, @@ -110,18 +73,14 @@ export class VegaClaim extends BaseContract { target, ].filter(Boolean) ); - - this.trackTransaction(tx, confirmations); - - return tx; } /** * Check if this code was already committed to by this account * @return {Promise} */ - async isCommitted({ s }: { s: string }): Promise { - return await this.contract.commitments(s); + isCommitted({ s }: { s: string }): Promise { + return this.contract.commitments(s); } /** @@ -130,7 +89,7 @@ export class VegaClaim extends BaseContract { * @returns Promise */ async isExpired(expiry: number): Promise { - return expiry < (await this.provider.getBlock('latest')).timestamp; + return expiry < (await this.contract.provider.getBlock('latest')).timestamp; } /** diff --git a/libs/smart-contracts/src/contracts/collateral-bridge.ts b/libs/smart-contracts/src/contracts/collateral-bridge.ts new file mode 100644 index 000000000..3824491a8 --- /dev/null +++ b/libs/smart-contracts/src/contracts/collateral-bridge.ts @@ -0,0 +1,51 @@ +import type { BigNumber } from 'ethers'; +import { ethers } from 'ethers'; +import abi from '../abis/erc20_bridge_abi.json'; + +export class CollateralBridge { + public contract: ethers.Contract; + + constructor( + address: string, + signerOrProvider: ethers.Signer | ethers.providers.Provider + ) { + this.contract = new ethers.Contract(address, abi, signerOrProvider); + } + + depositAsset(assetSource: string, amount: string, vegaPublicKey: string) { + return this.contract.deposit_asset(assetSource, amount, vegaPublicKey); + } + getAssetSource(vegaAssetId: string) { + return this.contract.get_asset_source(vegaAssetId); + } + getDepositMaximum(assetSource: string): Promise { + return this.contract.get_deposit_maximum(assetSource); + } + getDepositMinimum(assetSource: string): Promise { + return this.contract.get_deposit_minimum(assetSource); + } + getMultisigControlAddres() { + return this.contract.get_multisig_control_address(); + } + getVegaAssetId(address: string) { + return this.contract.get_vega_asset_id(address); + } + isAssetListed(address: string) { + return this.contract.is_asset_listed(address); + } + withdrawAsset( + assetSource: string, + amount: string, + target: string, + nonce: string, + signatures: string + ) { + return this.contract.withdraw_asset( + assetSource, + amount, + target, + nonce, + signatures + ); + } +} diff --git a/libs/smart-contracts/src/contracts/erc20-token.ts b/libs/smart-contracts/src/contracts/erc20-token.ts deleted file mode 100644 index 64f63b6c4..000000000 --- a/libs/smart-contracts/src/contracts/erc20-token.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { BigNumber as EthersBigNumber } from 'ethers'; -import { ethers } from 'ethers'; -import erc20Abi from '../abis/erc20_abi.json'; -import erc20AbiFaucet from '../abis/erc20_abi_faucet.json'; -import BigNumber from 'bignumber.js'; -import { addDecimal, removeDecimal } from '../utils'; -import { BaseContract } from './base-contract'; - -export class ERC20Token extends BaseContract { - public contract: ethers.Contract; - public dp: Promise; - private faucetable: boolean; - - constructor( - address: string, - provider: ethers.providers.Web3Provider, - signer?: ethers.Signer, - faucetable = false - ) { - super(provider, signer); - - this.faucetable = faucetable; - const contract = new ethers.Contract( - address, - faucetable ? erc20AbiFaucet : erc20Abi, - signer || provider - ); - this.contract = contract; - this.dp = (async () => { - const val = await contract.decimals(); - return Number(val); - })(); - } - - /** Gets Vega token total supply */ - async totalSupply(): Promise { - const res: EthersBigNumber = await this.contract.totalSupply(); - const value = addDecimal(new BigNumber(res.toString()), await this.dp); - return value; - } - - /** Gets number tokens an Ethereum account owns */ - async balanceOf(address: string): Promise { - const res: EthersBigNumber = await this.contract.balanceOf(address); - const value = addDecimal(new BigNumber(res.toString()), await this.dp); - return value; - } - - async transfer( - from: string, - to: string, - amount: BigNumber, - confirmations = 1 - ) { - const value = removeDecimal(amount, await this.dp).toString(); - const tx = await this.contract.transfer(from, to, value); - this.trackTransaction(tx, confirmations); - return tx; - } - - async transferFrom( - sender: string, - recipient: string, - amount: BigNumber, - confirmations = 1 - ) { - const value = removeDecimal(amount, await this.dp).toString(); - const tx = await this.contract.transferFrom(sender, recipient, value); - this.trackTransaction(tx, confirmations); - return tx; - } - - /** Gets Ethereum account's permitted allowance */ - async allowance(address: string, spender: string): Promise { - const res: EthersBigNumber = await this.contract.allowance( - address, - spender - ); - const value = addDecimal(new BigNumber(res.toString()), await this.dp); - return value; - } - - /** Executs contracts approve function */ - async approve( - spender: string, - confirmations = 1 - ): Promise { - const amount = removeDecimal( - new BigNumber(Number.MAX_SAFE_INTEGER), - await this.dp - ).toString(); - const tx = await this.contract.approve(spender, amount); - this.trackTransaction(tx, confirmations); - return tx; - } - - async faucet(confirmations = 1): Promise { - if (!this.faucetable) { - throw new Error('Faucet function can not be called on this contract'); - } - const tx = await this.contract.faucet(); - this.trackTransaction(tx, confirmations); - return tx; - } - - /** Gets number of decimals used by token */ - async decimals(): Promise { - const res: number = await this.contract.decimals(); - return Number(res); - } -} diff --git a/libs/smart-contracts/src/contracts/index.ts b/libs/smart-contracts/src/contracts/index.ts index 86140f0e5..15ec967b1 100644 --- a/libs/smart-contracts/src/contracts/index.ts +++ b/libs/smart-contracts/src/contracts/index.ts @@ -1,8 +1,8 @@ -export * from './stake-helpers'; -export * from './tranche-helpers'; -export * from './vega-claim'; -export * from './vega-erc20-bridge'; -export * from './vega-staking'; -export * from './vega-vesting'; export * from './vega-web3-types'; -export * from './erc20-token'; + +export * from './claim'; +export * from './collateral-bridge'; +export * from './staking-bridge'; +export * from './token-vesting'; +export * from './token'; +export * from './token-faucetable'; diff --git a/libs/smart-contracts/src/contracts/stake-helpers.ts b/libs/smart-contracts/src/contracts/stake-helpers.ts deleted file mode 100644 index 8b924da6f..000000000 --- a/libs/smart-contracts/src/contracts/stake-helpers.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { ethers } from 'ethers'; - -import BigNumber from 'bignumber.js'; -import { addDecimal } from '../utils/decimals'; - -export function combineStakeEventsByVegaKey( - events: ethers.Event[], - decimals: number -): { [vegaKey: string]: BigNumber } { - const res = events.reduce((obj, e) => { - const vegaKey = e.args?.vega_public_key; - const amount = parseEventAmount(e, decimals); - const isDeposit = e.event === 'Stake_Deposited'; - const isRemove = e.event === 'Stake_Removed'; - - if (!isDeposit && !isRemove) return obj; - - if (Object.prototype.hasOwnProperty.call(obj, vegaKey)) { - if (isDeposit) { - obj[vegaKey] = obj[vegaKey].plus(amount); - } else { - obj[vegaKey] = obj[vegaKey].minus(amount); - } - } else { - if (isDeposit) { - obj[vegaKey] = amount; - } else { - obj[vegaKey] = new BigNumber(0); - } - } - return obj; - }, {} as { [vegaKey: string]: BigNumber }); - - return res; -} - -function parseEventAmount(e: ethers.Event, decimals: number) { - const rawAmount = new BigNumber(e.args?.amount.toString() || 0); - return addDecimal(rawAmount, decimals); -} diff --git a/libs/smart-contracts/src/contracts/staking-bridge.ts b/libs/smart-contracts/src/contracts/staking-bridge.ts new file mode 100644 index 000000000..036175596 --- /dev/null +++ b/libs/smart-contracts/src/contracts/staking-bridge.ts @@ -0,0 +1,32 @@ +import { ethers } from 'ethers'; +import abi from '../abis/staking_abi.json'; + +export class StakingBridge { + public contract: ethers.Contract; + + constructor( + address: string, + signerOrProvider: ethers.Signer | ethers.providers.Provider + ) { + this.contract = new ethers.Contract(address, abi, signerOrProvider); + } + + stake(amount: string, vegaPublicKey: string) { + return this.contract.stake(amount, `0x${vegaPublicKey}`); + } + removeStake(amount: string, vegaPublicKey: string) { + return this.contract.remove_stake(amount, `0x${vegaPublicKey}`); + } + transferStake(amount: string, newAddress: string, vegaPublicKey: string) { + return this.contract.transfer_stake(amount, newAddress, vegaPublicKey); + } + stakingToken() { + return this.contract.staking_token(); + } + stakeBalance(target: string, vegaPublicKey: string) { + return this.contract.stake_balance(target, vegaPublicKey); + } + totalStaked() { + return this.contract.total_staked(); + } +} diff --git a/libs/smart-contracts/src/contracts/token-faucetable.ts b/libs/smart-contracts/src/contracts/token-faucetable.ts new file mode 100644 index 000000000..86afcb8f8 --- /dev/null +++ b/libs/smart-contracts/src/contracts/token-faucetable.ts @@ -0,0 +1,37 @@ +import type { BigNumber } from 'ethers'; +import { ethers } from 'ethers'; +import erc20AbiFaucetable from '../abis/erc20_abi_faucet.json'; + +export class TokenFaucetable { + public contract: ethers.Contract; + + constructor( + address: string, + signerOrProvider: ethers.Signer | ethers.providers.Provider + ) { + this.contract = new ethers.Contract( + address, + erc20AbiFaucetable, + signerOrProvider + ); + } + + totalSupply() { + return this.contract.totalSupply(); + } + balanceOf(account: string): Promise { + return this.contract.balanceOf(account); + } + allowance(owner: string, spender: string): Promise { + return this.contract.allowance(owner, spender); + } + approve(spender: string, amount: string) { + return this.contract.approve(spender, amount); + } + decimals(): Promise { + return this.contract.decimals(); + } + faucet() { + return this.contract.faucet(); + } +} diff --git a/libs/smart-contracts/src/contracts/token-vesting.ts b/libs/smart-contracts/src/contracts/token-vesting.ts new file mode 100644 index 000000000..956098d94 --- /dev/null +++ b/libs/smart-contracts/src/contracts/token-vesting.ts @@ -0,0 +1,41 @@ +import { ethers } from 'ethers'; +import abi from '../abis/vesting_abi.json'; + +export class TokenVesting { + public contract: ethers.Contract; + + constructor( + address: string, + signerOrProvider: ethers.Signer | ethers.providers.Provider + ) { + this.contract = new ethers.Contract(address, abi, signerOrProvider); + } + + stakeTokens(amount: string, vegaPublicKey: string) { + return this.contract.stake_tokens(amount, vegaPublicKey); + } + removeStake(amount: string, vegaPublicKey: string) { + return this.contract.remove_stake(amount, vegaPublicKey); + } + stakeBalance(address: string, vegaPublicKey: string) { + return this.contract.stake_balance(address, vegaPublicKey); + } + totalStaked() { + return this.contract.total_staked(); + } + userStats(address: string) { + return this.contract.user_stats(address); + } + getTrancheBalance(address: string, trancheId: number) { + return this.contract.get_tranche_balance(address, trancheId); + } + getVestedForTranche(address: string, trancheId: number) { + return this.contract.get_vested_for_tranche(address, trancheId); + } + userTotalAllTranches(address: string) { + return this.contract.user_total_all_tranches(address); + } + withdrawFromTranche(trancheId: number) { + return this.contract.withdraw_from_tranche(trancheId); + } +} diff --git a/libs/smart-contracts/src/contracts/token.ts b/libs/smart-contracts/src/contracts/token.ts new file mode 100644 index 000000000..ac1ff0a41 --- /dev/null +++ b/libs/smart-contracts/src/contracts/token.ts @@ -0,0 +1,30 @@ +import type { BigNumber } from 'ethers'; +import { ethers } from 'ethers'; +import erc20Abi from '../abis/erc20_abi.json'; + +export class Token { + public contract: ethers.Contract; + + constructor( + address: string, + signerOrProvider: ethers.Signer | ethers.providers.Provider + ) { + this.contract = new ethers.Contract(address, erc20Abi, signerOrProvider); + } + + totalSupply() { + return this.contract.totalSupply(); + } + balanceOf(account: string): Promise { + return this.contract.balanceOf(account); + } + allowance(owner: string, spender: string): Promise { + return this.contract.allowance(owner, spender); + } + approve(spender: string, amount: string) { + return this.contract.approve(spender, amount); + } + decimals(): Promise { + return this.contract.decimals(); + } +} diff --git a/libs/smart-contracts/src/contracts/tranche-helpers.ts b/libs/smart-contracts/src/contracts/tranche-helpers.ts deleted file mode 100644 index 2d0a26d9b..000000000 --- a/libs/smart-contracts/src/contracts/tranche-helpers.ts +++ /dev/null @@ -1,165 +0,0 @@ -import BigNumber from 'bignumber.js'; -import type { ethers } from 'ethers'; -import uniq from 'lodash/uniq'; -import { addDecimal } from '../utils/decimals'; - -import type { Tranche, TrancheUser } from './vega-web3-types'; -import { TrancheEvents } from './vega-web3-types'; - -export function createUserTransactions( - events: ethers.Event[], - decimals: number -) { - return events.map((event) => { - return { - amount: addDecimal( - new BigNumber(event.args?.amount.toString()), - decimals - ), - user: event.args?.user, - tranche_id: event.args?.tranche_id, - tx: event.transactionHash, - }; - }); -} - -export function getUsersInTranche( - balanceAddedEvents: ethers.Event[], - balanceRemovedEvents: ethers.Event[], - addresses: string[], - decimals: number -): TrancheUser[] { - return addresses.map((address) => { - const userDeposits = balanceAddedEvents.filter( - (event) => event.args?.user === address - ); - const userWithdraws = balanceRemovedEvents.filter( - (event) => event.args?.user === address - ); - const deposits = createUserTransactions(userDeposits, decimals); - const withdrawals = createUserTransactions(userWithdraws, decimals); - const total_tokens = deposits.reduce( - (pre, cur) => pre.plus(cur.amount), - new BigNumber(0) - ); - const withdrawn_tokens = withdrawals.reduce( - (pre, cur) => pre.plus(cur.amount), - new BigNumber(0) - ); - const remaining_tokens = total_tokens.minus(withdrawn_tokens); - - return { - address, - deposits, - withdrawals, - total_tokens, - withdrawn_tokens, - remaining_tokens, - }; - }); -} - -export function sumFromEvents(events: ethers.Event[], decimals: number) { - const amounts = events.map((e) => - addDecimal(new BigNumber(e.args?.amount.toString()), decimals) - ); - // Start with a 0 so if there are none there is no NaN - return BigNumber.sum.apply(null, [new BigNumber(0), ...amounts]); -} - -export function getLockedAmount( - totalAdded: BigNumber, - cliffStart: number, - trancheDuration: number -) { - let amount = new BigNumber(0); - const ts = Math.round(new Date().getTime() / 1000); - const tranche_progress = (ts - cliffStart) / trancheDuration; - - if (tranche_progress < 0) { - amount = totalAdded; - } else if (tranche_progress < 1) { - amount = totalAdded.times(1 - tranche_progress); - } - - return amount; -} - -export function createTransactions(events: ethers.Event[], decimals: number) { - return events.map((event) => { - return { - amount: addDecimal( - new BigNumber(event.args?.amount.toString()), - decimals - ), - user: event.args?.user, - tx: event.transactionHash, - }; - }); -} - -export function getTranchesFromHistory( - createEvents: ethers.Event[], - addEvents: ethers.Event[], - removeEvents: ethers.Event[], - decimals: number -): Tranche[] { - return createEvents.map((event) => { - const tranche_id = event.args?.tranche_id; - const balanceAddedEvents = addEvents.filter( - (e) => - e.event === TrancheEvents.BalanceAdded && - e.args?.tranche_id === tranche_id - ); - const balanceRemovedEvents = removeEvents.filter( - (e) => - e.event === TrancheEvents.BalanceRemoved && - e.args?.tranche_id === tranche_id - ); - - //get tranche start and end dates - const tranche_duration = event.args?.duration; - const cliff_start = event.args?.cliff_start; - const tranche_start = new Date(cliff_start.mul(1000).toNumber()); - const tranche_end = new Date( - cliff_start.add(tranche_duration).mul(1000).toNumber() - ); - - // get added and removed values - const total_added = sumFromEvents(balanceAddedEvents, decimals); - const total_removed = sumFromEvents(balanceRemovedEvents, decimals); - // get locked amount - const locked_amount = getLockedAmount( - total_added, - cliff_start, - tranche_duration - ); - - // get all deposits and withdrawals - const deposits = createTransactions(balanceAddedEvents, decimals); - const withdrawals = createTransactions(balanceRemovedEvents, decimals); - - // get all users - const uniqueAddresses = uniq( - balanceAddedEvents.map((event) => event.args?.user) - ); - const users = getUsersInTranche( - balanceAddedEvents, - balanceRemovedEvents, - uniqueAddresses, - decimals - ); - - return { - tranche_id: parseInt(tranche_id), - tranche_start, - tranche_end, - total_added, - total_removed, - locked_amount, - deposits, - withdrawals, - users, - }; - }); -} diff --git a/libs/smart-contracts/src/contracts/vega-erc20-bridge.ts b/libs/smart-contracts/src/contracts/vega-erc20-bridge.ts deleted file mode 100644 index 3926658ae..000000000 --- a/libs/smart-contracts/src/contracts/vega-erc20-bridge.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { BigNumber as EthersBigNumber } from 'ethers'; -import { ethers } from 'ethers'; -import { EnvironmentConfig } from '../config/ethereum'; -import type { Networks } from '../config/vega'; - -import erc20BridgeAbi from '../abis/erc20_bridge_abi.json'; -import { BaseContract } from './base-contract'; -import BigNumber from 'bignumber.js'; -import { addDecimal } from '../utils'; - -export class VegaErc20Bridge extends BaseContract { - private contract: ethers.Contract; - - constructor( - network: Networks, - provider: ethers.providers.Web3Provider, - signer?: ethers.Signer - ) { - super(provider, signer); - this.contract = new ethers.Contract( - EnvironmentConfig[network].erc20Bridge, - erc20BridgeAbi, - this.signer || this.provider - ); - } - - /** Executes contracts withdraw_asset function */ - async withdraw( - approval: { - assetSource: string; - amount: string; - nonce: string; - signatures: string; - targetAddress: string; - }, - confirmations = 1 - ): Promise { - const tx = await this.contract.withdraw_asset( - approval.assetSource, - approval.amount, // No need to remove decimals as this value is already set and not manipulated by the user - approval.targetAddress, - approval.nonce, - approval.signatures - ); - - this.trackTransaction(tx, confirmations); - - return tx; - } - - async depositAsset( - assetSource: string, - amount: string, - vegaPublicKey: string, - confirmations = 1 - ) { - const tx = await this.contract.deposit_asset( - assetSource, - amount, - vegaPublicKey - ); - - this.trackTransaction(tx, confirmations); - - return tx; - } - - async getAssetSource(vegaAssetId: string): Promise { - const res = await this.contract.get_asset_source(vegaAssetId); - return res; - } - - async getDepositMaximum( - assetSource: string, - decimals: number - ): Promise { - const res: EthersBigNumber = await this.contract.get_deposit_maximum( - assetSource - ); - const value = addDecimal(new BigNumber(res.toString()), decimals); - return value; - } - - async getDepositMinimum( - assetSource: string, - decimals: number - ): Promise { - const res: EthersBigNumber = await this.contract.get_deposit_minimum( - assetSource - ); - const value = addDecimal(new BigNumber(res.toString()), decimals); - return value; - } - - async getMultisigControlAddress(): Promise { - const res = await this.contract.get_multisig_control_address(); - return res; - } - - async getVegaAssetId(): Promise { - const res = await this.contract.get_vega_asset_id(); - return res; - } - - async isAssetListed(assetSource: string): Promise { - const res = await this.contract.is_asset_listed(assetSource); - return res; - } -} diff --git a/libs/smart-contracts/src/contracts/vega-staking.ts b/libs/smart-contracts/src/contracts/vega-staking.ts deleted file mode 100644 index b9c86ef45..000000000 --- a/libs/smart-contracts/src/contracts/vega-staking.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type { BigNumber as EthersBigNumber } from 'ethers'; -import { ethers } from 'ethers'; -import stakingAbi from '../abis/staking_abi.json'; -import tokenAbi from '../abis/vega_token_abi.json'; -import { combineStakeEventsByVegaKey } from './stake-helpers'; -import BigNumber from 'bignumber.js'; -import { BaseContract } from './base-contract'; -import { EnvironmentConfig } from '../config/ethereum'; -import type { Networks } from '../config/vega'; -import { addDecimal, hexadecimalify, removeDecimal } from '../utils'; - -export class VegaStaking extends BaseContract { - public contract: ethers.Contract; - public tokenContract: ethers.Contract; - public dp: Promise; - - constructor( - network: Networks, - provider: ethers.providers.Web3Provider, - signer?: ethers.Signer - ) { - super(provider, signer); - - const tokenContract = new ethers.Contract( - EnvironmentConfig[network].vegaTokenAddress, - tokenAbi, - this.signer || this.provider - ); - this.tokenContract = tokenContract; - - this.contract = new ethers.Contract( - EnvironmentConfig[network].stakingBridge, - stakingAbi, - this.signer || this.provider - ); - - this.dp = (async () => { - const val = await tokenContract.decimals(); - return Number(val); - })(); - } - - /** Executes staking contracts stake function */ - async addStake( - amount: BigNumber, - vegaKey: string, - confirmations = 1 - ): Promise { - const convertedAmount = removeDecimal(amount, await this.dp).toString(); - - const tx = await this.contract.stake( - convertedAmount, - hexadecimalify(vegaKey) - ); - - // store and track the transaction in BaseContract - this.trackTransaction(tx, confirmations); - - return tx; - } - - /** Executes staking contracts remove_stake function */ - async removeStake( - amount: BigNumber, - vegaKey: string, - confirmations = 1 - ): Promise { - const convertedAmount = removeDecimal(amount, await this.dp).toString(); - - const tx = await this.contract.remove_stake( - convertedAmount, - hexadecimalify(vegaKey) - ); - - this.trackTransaction(tx, confirmations); - - return tx; - } - - /** Executes staking contracts transfer_stake function */ - async transferStake( - amount: BigNumber, - newAddress: string, - vegaKey: string, - confirmations = 1 - ): Promise { - const convertedAmount = removeDecimal(amount, await this.dp).toString(); - - const tx = await this.contract.transfer_stake( - convertedAmount, - newAddress, - hexadecimalify(vegaKey) - ); - - this.trackTransaction(tx, confirmations); - - return tx; - } - - /** Returns the amount staked for given Vega public key */ - async stakeBalance(address: string, vegaKey: string): Promise { - const res: EthersBigNumber = await this.contract.stake_balance( - address, - hexadecimalify(vegaKey) - ); - const value = addDecimal(new BigNumber(res.toString()), await this.dp); - return value; - } - - /** Returns the total amount currently staked */ - async totalStaked(): Promise { - const res: EthersBigNumber = await this.contract.total_staked(); - const value = addDecimal(new BigNumber(res.toString()), await this.dp); - return value; - } - - /** Returns amounts staked across all Vega keys for single Ethereum account */ - async userTotalStakedByVegaKey( - ethereumAccount: string - ): Promise<{ [vegaKey: string]: BigNumber }> { - const addFilter = this.contract.filters.Stake_Deposited(ethereumAccount); - const removeFilter = this.contract.filters.Stake_Removed(ethereumAccount); - const addEvents = await this.contract.queryFilter(addFilter); - const removeEvents = await this.contract.queryFilter(removeFilter); - const res = combineStakeEventsByVegaKey( - [...addEvents, ...removeEvents], - await this.dp - ); - return res; - } -} diff --git a/libs/smart-contracts/src/contracts/vega-vesting.ts b/libs/smart-contracts/src/contracts/vega-vesting.ts deleted file mode 100644 index 425672466..000000000 --- a/libs/smart-contracts/src/contracts/vega-vesting.ts +++ /dev/null @@ -1,182 +0,0 @@ -import BigNumber from 'bignumber.js'; -import type { BigNumber as EthersBigNumber } from 'ethers'; -import { ethers } from 'ethers'; -import { EnvironmentConfig } from '../config/ethereum'; -import type { Networks } from '../config/vega'; -import vestingAbi from '../abis/vesting_abi.json'; -import tokenAbi from '../abis/vega_token_abi.json'; -import { BaseContract } from './base-contract'; -import { combineStakeEventsByVegaKey } from './stake-helpers'; -import { getTranchesFromHistory } from './tranche-helpers'; -import type { Tranche } from './vega-web3-types'; -import { addDecimal, hexadecimalify, removeDecimal } from '../utils'; - -export class VegaVesting extends BaseContract { - public contract: ethers.Contract; - public tokenContract: ethers.Contract; - public dp: Promise; - - constructor( - network: Networks, - provider: ethers.providers.Web3Provider, - signer?: ethers.Signer - ) { - super(provider, signer); - - const tokenContract = new ethers.Contract( - EnvironmentConfig[network].vegaTokenAddress, - tokenAbi, - this.signer || this.provider - ); - this.tokenContract = tokenContract; - - this.contract = new ethers.Contract( - EnvironmentConfig[network].vestingAddress, - vestingAbi, - this.signer || this.provider - ); - - this.dp = (async () => { - const val = await tokenContract.decimals(); - return Number(val); - })(); - } - - /** Executes vesting contracts stake_tokens function */ - async addStake( - amount: BigNumber, - vegaKey: string, - confirmations = 1 - ): Promise { - const convertedAmount = removeDecimal(amount, await this.dp).toString(); - - const tx = await this.contract.stake_tokens( - convertedAmount, - hexadecimalify(vegaKey) - ); - - this.trackTransaction(tx, confirmations); - - return tx; - } - - /** Executes vesting contracts remove_stake function */ - async removeStake( - amount: BigNumber, - vegaKey: string, - confirmations = 1 - ): Promise { - const convertedAmount = removeDecimal(amount, await this.dp).toString(); - - const tx = await this.contract.remove_stake( - convertedAmount, - hexadecimalify(vegaKey) - ); - - this.trackTransaction(tx, confirmations); - - return tx; - } - - /** Returns the amount staked for a given Vega public key */ - async stakeBalance(address: string, vegaKey: string): Promise { - const res: EthersBigNumber = await this.contract.stake_balance( - address, - hexadecimalify(vegaKey) - ); - const value = addDecimal(new BigNumber(res.toString()), await this.dp); - return value; - } - - /** Returns the total amount currently staked */ - async totalStaked(): Promise { - const res: EthersBigNumber = await this.contract.total_staked(); - const value = addDecimal(new BigNumber(res.toString()), await this.dp); - return value; - } - - /** Returns the amount of locked tokens in the vesting contract */ - async getLien(address: string): Promise { - const { - lien, - }: { - lien: EthersBigNumber; - total_in_all_tranches: EthersBigNumber; - } = await this.contract.user_stats(address); - const value = addDecimal(new BigNumber(lien.toString()), await this.dp); - return value; - } - - /** Returns the amount a user has in a specific tranche */ - async userTrancheTotalBalance( - address: string, - tranche: number - ): Promise { - const amount: EthersBigNumber = await this.contract.get_tranche_balance( - address, - tranche - ); - const value = addDecimal(new BigNumber(amount.toString()), await this.dp); - return value; - } - - /** Returns vested amount for a given tranche */ - async userTrancheVestedBalance( - address: string, - tranche: number - ): Promise { - const amount: EthersBigNumber = await this.contract.get_vested_for_tranche( - address, - tranche - ); - const value = addDecimal(new BigNumber(amount.toString()), await this.dp); - return value; - } - - /** Returns the users total tokens across all tranches */ - async getUserBalanceAllTranches(account: string): Promise { - const amount: EthersBigNumber = await this.contract.user_total_all_tranches( - account - ); - const value = addDecimal(new BigNumber(amount.toString()), await this.dp); - return value; - } - - /** Gets all tranche data */ - async getAllTranches(): Promise { - const [created, added, removed] = await Promise.all([ - this.contract.queryFilter(this.contract.filters.Tranche_Created()), - this.contract.queryFilter(this.contract.filters.Tranche_Balance_Added()), - this.contract.queryFilter( - this.contract.filters.Tranche_Balance_Removed() - ), - ]); - const dp = await this.dp; - return getTranchesFromHistory(created, added, removed, dp); - } - - /** Executes contracts withdraw_from_tranche function */ - async withdrawFromTranche( - trancheId: number, - confirmations = 1 - ): Promise { - const tx = await this.contract.withdraw_from_tranche(trancheId); - - this.trackTransaction(tx, confirmations); - - return tx; - } - - /** Returns amounts staked across all Vega keys for single Ethereum account */ - async userTotalStakedByVegaKey(address: string) { - const addFilter = this.contract.filters.Stake_Deposited(address); - const removeFilter = this.contract.filters.Stake_Removed(address); - const addEvents = await this.contract.queryFilter(addFilter); - const removeEvents = await this.contract.queryFilter(removeFilter); - const res = combineStakeEventsByVegaKey( - [...addEvents, ...removeEvents], - await this.dp - ); - return res; - } -} diff --git a/libs/smart-contracts/src/contracts/vega-web3-types.ts b/libs/smart-contracts/src/contracts/vega-web3-types.ts index 8b5f5452b..210dc6384 100644 --- a/libs/smart-contracts/src/contracts/vega-web3-types.ts +++ b/libs/smart-contracts/src/contracts/vega-web3-types.ts @@ -1,4 +1,3 @@ -import type { ethers } from 'ethers'; import type BigNumber from 'bignumber.js'; export interface Tranche { @@ -74,10 +73,3 @@ export interface EpochDetails { startSeconds: BigNumber; endSeconds: BigNumber; } - -export interface TxData { - tx: ethers.ContractTransaction; - receipt: ethers.ContractReceipt | null; - pending: boolean; - requiredConfirmations: number; -} diff --git a/libs/smart-contracts/src/index.ts b/libs/smart-contracts/src/index.ts index 4d801929f..d49668b31 100644 --- a/libs/smart-contracts/src/index.ts +++ b/libs/smart-contracts/src/index.ts @@ -1,3 +1,2 @@ -export * from './config'; export * from './contracts'; export * from './utils'; diff --git a/libs/smart-contracts/src/utils/decimals.test.ts b/libs/smart-contracts/src/utils/decimals.test.ts deleted file mode 100644 index 3e4e42a44..000000000 --- a/libs/smart-contracts/src/utils/decimals.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import BigNumber from 'bignumber.js'; -import { addDecimal, removeDecimal } from './decimals'; - -test('Do not pad numbers with 0s when the number length is less than the specified DPs', () => { - expect(addDecimal(new BigNumber(10000), 10).toString()).toEqual('0.000001'); -}); - -test('Handles large numbers correctly', () => { - const claimCode = new BigNumber('20000000000000000000000000'); - const decimals = 18; - - const decimalised = addDecimal(claimCode, decimals); - expect(decimalised.toString()).toEqual('20000000'); - - const undecimalised = removeDecimal(claimCode, decimals); - expect(undecimalised.toString()).toEqual( - '20000000000000000000000000000000000000000000' - ); - - const mangled = removeDecimal(addDecimal(claimCode, decimals), decimals); - expect(mangled.toString()).toEqual('20000000000000000000000000'); -}); diff --git a/libs/smart-contracts/src/utils/decimals.ts b/libs/smart-contracts/src/utils/decimals.ts deleted file mode 100644 index 0654e6bb6..000000000 --- a/libs/smart-contracts/src/utils/decimals.ts +++ /dev/null @@ -1,10 +0,0 @@ -import BigNumber from 'bignumber.js'; - -BigNumber.config({ EXPONENTIAL_AT: 20000 }); - -export function addDecimal(value: BigNumber, decimals: number): BigNumber { - return value.dividedBy(Math.pow(10, decimals)).decimalPlaces(decimals); -} -export function removeDecimal(value: BigNumber, decimals: number): BigNumber { - return value.times(Math.pow(10, decimals)); -} diff --git a/libs/smart-contracts/src/utils/index.ts b/libs/smart-contracts/src/utils/index.ts index 967db1f40..96a2027d6 100644 --- a/libs/smart-contracts/src/utils/index.ts +++ b/libs/smart-contracts/src/utils/index.ts @@ -1,3 +1,2 @@ -export * from './decimals'; export * from './ascii-to-hex'; export * from './hexadecimalify'; diff --git a/libs/ui-toolkit/src/components/loader/loader.tsx b/libs/ui-toolkit/src/components/loader/loader.tsx index 18b0955c7..602519dfb 100644 --- a/libs/ui-toolkit/src/components/loader/loader.tsx +++ b/libs/ui-toolkit/src/components/loader/loader.tsx @@ -1,10 +1,12 @@ +import classNames from 'classnames'; import { useEffect, useState } from 'react'; interface LoaderProps { size?: 'small' | 'large'; + forceTheme?: 'dark' | 'light'; } -export const Loader = ({ size = 'large' }: LoaderProps) => { +export const Loader = ({ size = 'large', forceTheme }: LoaderProps) => { const [, forceRender] = useState(false); useEffect(() => { @@ -15,8 +17,14 @@ export const Loader = ({ size = 'large' }: LoaderProps) => { return () => clearInterval(interval); }, []); + const itemClasses = classNames({ + 'dark:bg-white bg-black': !forceTheme, + 'bg-white': forceTheme === 'dark', + 'bg-black': forceTheme === 'light', + 'w-16 h-16': size === 'large', + 'w-[5px] h-[5px]': size === 'small', + }); const wrapperClasses = size === 'small' ? 'w-[15px] h-[15px]' : 'w-64 h-64'; - const gridItemClasses = size === 'small' ? 'w-[5px] h-[5px]' : 'w-16 h-16'; const items = size === 'small' ? 9 : 16; return ( @@ -25,7 +33,7 @@ export const Loader = ({ size = 'large' }: LoaderProps) => { {new Array(items).fill(null).map((_, i) => { return (
        0.75 ? 1 : 0, diff --git a/libs/web3/src/index.ts b/libs/web3/src/index.ts index 8106718bc..cc2f7a4bc 100644 --- a/libs/web3/src/index.ts +++ b/libs/web3/src/index.ts @@ -1,8 +1,11 @@ -export * from './lib/web3-provider'; -export * from './lib/web3-connect-dialog'; export * from './lib/ethereum-error'; +export * from './lib/__generated__/NetworkParamsQuery'; export * from './lib/use-bridge-contract'; export * from './lib/use-token-contract'; +export * from './lib/use-token-decimals'; +export * from './lib/use-ethereum-config'; export * from './lib/use-ethereum-read-contract'; export * from './lib/use-ethereum-transaction'; export * from './lib/transaction-dialog'; +export * from './lib/web3-provider'; +export * from './lib/web3-connect-dialog'; diff --git a/apps/trading/components/web3-container/__generated__/NetworkParamsQuery.ts b/libs/web3/src/lib/__generated__/NetworkParamsQuery.ts similarity index 100% rename from apps/trading/components/web3-container/__generated__/NetworkParamsQuery.ts rename to libs/web3/src/lib/__generated__/NetworkParamsQuery.ts diff --git a/libs/web3/src/lib/use-bridge-contract.ts b/libs/web3/src/lib/use-bridge-contract.ts index 0bc311af1..db7637bf2 100644 --- a/libs/web3/src/lib/use-bridge-contract.ts +++ b/libs/web3/src/lib/use-bridge-contract.ts @@ -1,18 +1,24 @@ -import { VegaErc20Bridge } from '@vegaprotocol/smart-contracts'; +import { CollateralBridge } from '@vegaprotocol/smart-contracts'; import { useWeb3React } from '@web3-react/core'; import { useMemo } from 'react'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEthereumConfig } from './use-ethereum-config'; export const useBridgeContract = () => { - const { VEGA_ENV } = useEnvironment(); const { provider } = useWeb3React(); + const { config } = useEthereumConfig(); const contract = useMemo(() => { - if (!provider) { + if (!provider || !config) { return null; } - return new VegaErc20Bridge(VEGA_ENV, provider, provider?.getSigner()); - }, [provider, VEGA_ENV]); + + const signer = provider.getSigner(); + + return new CollateralBridge( + config.collateral_bridge_contract.address, + signer || provider + ); + }, [provider, config]); return contract; }; diff --git a/libs/web3/src/lib/use-ethereum-config.ts b/libs/web3/src/lib/use-ethereum-config.ts new file mode 100644 index 000000000..af3603d0d --- /dev/null +++ b/libs/web3/src/lib/use-ethereum-config.ts @@ -0,0 +1,64 @@ +import { gql, useQuery } from '@apollo/client'; +import { useMemo } from 'react'; +import type { NetworkParamsQuery } from './__generated__/NetworkParamsQuery'; + +export interface EthereumConfig { + network_id: string; + chain_id: string; + confirmations: number; + collateral_bridge_contract: { + address: string; + }; + multisig_control_contract: { + address: string; + deployment_block_height: number; + }; + staking_bridge_contract: { + address: string; + deployment_block_height: number; + }; + token_vesting_contract: { + address: string; + deployment_block_height: number; + }; +} + +export const NETWORK_PARAMS_QUERY = gql` + query NetworkParamsQuery { + networkParameters { + key + value + } + } +`; + +export const useEthereumConfig = () => { + const { data, loading, error } = + useQuery(NETWORK_PARAMS_QUERY); + + const config = useMemo(() => { + if (!data) { + return null; + } + + const param = data.networkParameters?.find( + (np) => np.key === 'blockchains.ethereumConfig' + ); + + if (!param) { + return null; + } + + let parsedConfig: EthereumConfig; + + try { + parsedConfig = JSON.parse(param.value); + } catch { + return null; + } + + return parsedConfig; + }, [data]); + + return { config, loading, error }; +}; diff --git a/libs/web3/src/lib/use-token-contract.ts b/libs/web3/src/lib/use-token-contract.ts index 4a171ccb4..49cbae48f 100644 --- a/libs/web3/src/lib/use-token-contract.ts +++ b/libs/web3/src/lib/use-token-contract.ts @@ -1,22 +1,25 @@ -import { ERC20Token } from '@vegaprotocol/smart-contracts'; +import { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts'; import { useWeb3React } from '@web3-react/core'; import { useMemo } from 'react'; export const useTokenContract = ( contractAddress?: string, faucetable = false -): ERC20Token | null => { +) => { const { provider } = useWeb3React(); + const contract = useMemo(() => { if (!provider || !contractAddress) { return null; } - return new ERC20Token( - contractAddress, - provider, - provider.getSigner(), - faucetable - ); + + const signer = provider.getSigner(); + + if (faucetable) { + return new TokenFaucetable(contractAddress, signer || provider); + } else { + return new Token(contractAddress, signer || provider); + } }, [provider, contractAddress, faucetable]); return contract; diff --git a/libs/web3/src/lib/use-token-decimals.ts b/libs/web3/src/lib/use-token-decimals.ts new file mode 100644 index 000000000..1ac7f232b --- /dev/null +++ b/libs/web3/src/lib/use-token-decimals.ts @@ -0,0 +1,20 @@ +import type { Token } from '@vegaprotocol/smart-contracts'; +import { useCallback } from 'react'; +import { useEthereumReadContract } from './use-ethereum-read-contract'; + +export const useTokenDecimals = (contract: Token | null) => { + const getDecimals = useCallback(async () => { + if (!contract) { + return; + } + + const value = await contract.decimals(); + const decimals = Number(value); + + return decimals; + }, [contract]); + + const { state } = useEthereumReadContract(getDecimals); + + return state.data; +}; diff --git a/libs/withdraws/src/lib/use-complete-withdraw.ts b/libs/withdraws/src/lib/use-complete-withdraw.ts index 70a471db3..5171ee2d8 100644 --- a/libs/withdraws/src/lib/use-complete-withdraw.ts +++ b/libs/withdraws/src/lib/use-complete-withdraw.ts @@ -33,7 +33,13 @@ export const useCompleteWithdraw = () => { if (!contract) { return null; } - return contract.withdraw(args); + return contract.withdrawAsset( + args.assetSource, + args.amount, + args.targetAddress, + args.nonce, + args.signatures + ); }); const submit = useCallback( diff --git a/libs/withdraws/src/lib/use-withdraw.ts b/libs/withdraws/src/lib/use-withdraw.ts index 2fd5da72f..1f242c524 100644 --- a/libs/withdraws/src/lib/use-withdraw.ts +++ b/libs/withdraws/src/lib/use-withdraw.ts @@ -38,7 +38,13 @@ export const useWithdraw = (cancelled: boolean) => { if (!contract) { return null; } - return contract.withdraw(args); + return contract.withdrawAsset( + args.assetSource, + args.amount, + args.targetAddress, + args.nonce, + args.signatures + ); }); const { data, stopPolling } = useQuery( From 6b14221de3a5f206cf6e17ff9bb6790ad22650c1 Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Tue, 7 Jun 2022 16:06:40 -0700 Subject: [PATCH 04/27] fix: make scroll bars auto in trade header --- apps/trading/pages/markets/trade-grid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/trading/pages/markets/trade-grid.tsx b/apps/trading/pages/markets/trade-grid.tsx index 18f865e3f..ec5b4c53f 100644 --- a/apps/trading/pages/markets/trade-grid.tsx +++ b/apps/trading/pages/markets/trade-grid.tsx @@ -56,7 +56,7 @@ export const TradeMarketHeader = ({ market }: TradeGridProps) => { -
        +
        Change (24h) Date: Wed, 8 Jun 2022 08:04:32 +0000 Subject: [PATCH 05/27] chore: update tranches Signed-off-by: github-actions[bot] --- apps/static/src/assets/mainnet-tranches.json | 516 +++++++++++------- apps/static/src/assets/stagnet1-tranches.json | 4 +- 2 files changed, 322 insertions(+), 198 deletions(-) diff --git a/apps/static/src/assets/mainnet-tranches.json b/apps/static/src/assets/mainnet-tranches.json index 8e883eb4c..3091b7143 100644 --- a/apps/static/src/assets/mainnet-tranches.json +++ b/apps/static/src/assets/mainnet-tranches.json @@ -38,7 +38,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "129999.45", "total_removed": "0", - "locked_amount": "129444.52681890269686566", + "locked_amount": "129207.952267285068194955", "deposits": [ { "amount": "129999.45", @@ -488,7 +488,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "97499.58", "total_removed": "0", - "locked_amount": "70195.22412914844148698", + "locked_amount": "69963.16653192787035435", "deposits": [ { "amount": "97499.58", @@ -521,7 +521,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "135173.4239508", "total_removed": "0", - "locked_amount": "95944.7526846639458443835832", + "locked_amount": "95627.5700123362659064383246", "deposits": [ { "amount": "135173.4239508", @@ -554,7 +554,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "32499.86", "total_removed": "0", - "locked_amount": "29529.91858445099991019", + "locked_amount": "29432.295960720658724126", "deposits": [ { "amount": "32499.86", @@ -587,7 +587,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "10833.29", "total_removed": "0", - "locked_amount": "9611.701947691653720404", + "locked_amount": "9579.926730974945326072", "deposits": [ { "amount": "10833.29", @@ -675,7 +675,7 @@ "tranche_end": "2022-11-01T00:00:00.000Z", "total_added": "22500", "total_removed": "0", - "locked_amount": "17934.06221693840475", + "locked_amount": "17812.11503623188375", "deposits": [ { "amount": "15000", @@ -761,7 +761,7 @@ "tranche_end": "2023-06-02T00:00:00.000Z", "total_added": "1939928.38", "total_removed": "0", - "locked_amount": "1911551.08354358756368137", + "locked_amount": "1906250.79025953335932051", "deposits": [ { "amount": "1852091.69", @@ -1777,7 +1777,7 @@ "tranche_end": "2022-09-30T00:00:00.000Z", "total_added": "60916.66666633337", "total_removed": "17896.108295511846757997", - "locked_amount": "17909.6211245656664631173828909605", + "locked_amount": "17753.8529992697474932209560159272", "deposits": [ { "amount": "2833.333333", @@ -3217,10 +3217,15 @@ "tranche_id": 10, "tranche_start": "2021-07-15T23:37:11.000Z", "tranche_end": "2021-07-15T23:37:11.000Z", - "total_added": "3475768.150000000000000001", - "total_removed": "3467578.640000000000000001", + "total_added": "3515768.150000000000000001", + "total_removed": "3507578.640000000000000001", "locked_amount": "0", "deposits": [ + { + "amount": "40000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tx": "0xf61821785ec3404b4c0120875ca2adac3577325f5ba3381be65af2087fa3d430" + }, { "amount": "50000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -3618,6 +3623,11 @@ } ], "withdrawals": [ + { + "amount": "40000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tx": "0x91ca1d5a05f389c43a5bd4d65a84d6400c094513841543080e6b8b489a16e7a4" + }, { "amount": "90000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -3958,6 +3968,12 @@ { "address": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "deposits": [ + { + "amount": "40000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tranche_id": 10, + "tx": "0xf61821785ec3404b4c0120875ca2adac3577325f5ba3381be65af2087fa3d430" + }, { "amount": "50000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -4008,6 +4024,12 @@ } ], "withdrawals": [ + { + "amount": "40000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tranche_id": 10, + "tx": "0x91ca1d5a05f389c43a5bd4d65a84d6400c094513841543080e6b8b489a16e7a4" + }, { "amount": "90000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -4051,8 +4073,8 @@ "tx": "0xac16a4ce688d40a482a59914d68c3a676592f8804ee8f0781b66a4ba5ccfbdfc" } ], - "total_tokens": "329451", - "withdrawn_tokens": "329451", + "total_tokens": "369451", + "withdrawn_tokens": "369451", "remaining_tokens": "0" }, { @@ -5050,10 +5072,25 @@ "tranche_id": 11, "tranche_start": "2021-09-03T00:00:00.000Z", "tranche_end": "2022-09-03T00:00:00.000Z", - "total_added": "15073.000000000000000003", + "total_added": "15188.000000000000000003", "total_removed": "2905.98379934253", - "locked_amount": "3620.0297768581945964007204995243531204", + "locked_amount": "3606.15210755961410856071230289193302886", "deposits": [ + { + "amount": "30", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tx": "0xf1be966b6f8b899485abd86d558bfe1a79b7af80077bb6157f12cf1d813ec443" + }, + { + "amount": "25", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tx": "0xab9fcd474774c5094af51f03443082fd560aa679da77ba78778c7c02dee1102d" + }, + { + "amount": "60", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tx": "0xebf6e95ac8b64942a6ab21fa3835f5bdc143e7c6b15c186b95b7836de779a950" + }, { "amount": "10", "user": "0x10ea6ae9343A79c48eB5D7e58260a6523D7cb443", @@ -7948,6 +7985,183 @@ } ], "users": [ + { + "address": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "deposits": [ + { + "amount": "30", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xf1be966b6f8b899485abd86d558bfe1a79b7af80077bb6157f12cf1d813ec443" + }, + { + "amount": "25", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xab9fcd474774c5094af51f03443082fd560aa679da77ba78778c7c02dee1102d" + }, + { + "amount": "60", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xebf6e95ac8b64942a6ab21fa3835f5bdc143e7c6b15c186b95b7836de779a950" + }, + { + "amount": "35", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xb2fb3960b88da57f1e7347fc19dd8fa59820bce7721341e6e9108d093a568975" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x7d429cebf256a98d748cdd9dc1d61b0057086e0c9b5775dcbe94626a4e6c721a" + }, + { + "amount": "20", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x9bf0c211330b0a6039d5026ff1201ae605ef17cc97874162ba757f7256165f05" + }, + { + "amount": "20", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xf5b6413a16a381772153499b430975cbb472111f4e91182d2d251b323a0ea455" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xe9292e81c451724fe5ed54b4a6c233343d479309f899c6618b305668e5a6e2b0" + }, + { + "amount": "20", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x3c8303c57fb6c55d9529a8b6123897bc374f6196cf5cf7d82375e7f3ab5cb0c7" + }, + { + "amount": "20", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xe61f863255b61bd130f4b376e6ae384268dfb4965b7450753d7d47da986f5a05" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x50c094b3accb1ecc7d6e237cfa2c94948845cd814d3b2abd65b88a879f73af5e" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x62d19a29c673db27c02c5f4266638278212e134f25a30d0b38f28b9254cfeeee" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x9c96a5eb7945b8e666fbab617c072cba6f23d843ac221003396141805878d8e8" + }, + { + "amount": "15", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x9d67e8fb6762dd435cc749ffdc1b2a5a8640b45aecbfd084fc18103ee08b9ba2" + }, + { + "amount": "100", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xbddda2e0eb2449fb616b03d69f6c237bc7276c66feaefd9de59ca6ffdd81507a" + }, + { + "amount": "20", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xa3f6ec1b231c89dd1e34e0247266532aa7164ed1687e17d34c582c748b228d0d" + }, + { + "amount": "35", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xf55115db5cfc11464a8d994647a027d4e1013143f7d54fe34f9cc94b39ba293c" + }, + { + "amount": "35", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xcba0565d95ca237cca151d88bd19dc7cebd5c1820445fe1966be1f4b6f175e24" + }, + { + "amount": "15", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x32ac32e5d7f8ab38ecf4467c3c116ad676d9bfdc8b11ac8aa618b079837a7fa3" + }, + { + "amount": "15", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x481b1bac5120af196dfac4f480e85e26dd9e6c0b4bab3f8be506e5484a21292b" + }, + { + "amount": "15", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x50a1a2928181e56380fe21091786fcd82f3a8fafd9b13fee65a32cff117292ec" + }, + { + "amount": "20", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x758533d671732b224d241e109691655d01ea58db862b8b366a6c8ede67109ce7" + }, + { + "amount": "20", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xaabdfe069e1e4d75132cbf18cca3991d58473fcb82f47c9adb516da4eba4b1cf" + }, + { + "amount": "5", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x982e8902d21680a0e57e97b33efcdd7ad7b08bea0873c82f933a4c73abdc972a" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0xd1641e0814331fdfd0553c3785de9c5697d575dac44ca78767569d68560017d4" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x4490efa6050f006d31faaebb4411335be52c29edf449bed4380664c8377529a3" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x2e64b89e05ee0a9646251b7acec1614b945417ec750b6808657c59d7ab2db2a0" + }, + { + "amount": "10", + "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", + "tranche_id": 11, + "tx": "0x248d34bdf3224997d3f0e2cd55f75e1b13937591a64cff99abf513bada6da087" + } + ], + "withdrawals": [], + "total_tokens": "615", + "withdrawn_tokens": "0", + "remaining_tokens": "615" + }, { "address": "0x10ea6ae9343A79c48eB5D7e58260a6523D7cb443", "deposits": [ @@ -8845,165 +9059,6 @@ "withdrawn_tokens": "0", "remaining_tokens": "3755" }, - { - "address": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "deposits": [ - { - "amount": "35", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xb2fb3960b88da57f1e7347fc19dd8fa59820bce7721341e6e9108d093a568975" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x7d429cebf256a98d748cdd9dc1d61b0057086e0c9b5775dcbe94626a4e6c721a" - }, - { - "amount": "20", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x9bf0c211330b0a6039d5026ff1201ae605ef17cc97874162ba757f7256165f05" - }, - { - "amount": "20", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xf5b6413a16a381772153499b430975cbb472111f4e91182d2d251b323a0ea455" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xe9292e81c451724fe5ed54b4a6c233343d479309f899c6618b305668e5a6e2b0" - }, - { - "amount": "20", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x3c8303c57fb6c55d9529a8b6123897bc374f6196cf5cf7d82375e7f3ab5cb0c7" - }, - { - "amount": "20", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xe61f863255b61bd130f4b376e6ae384268dfb4965b7450753d7d47da986f5a05" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x50c094b3accb1ecc7d6e237cfa2c94948845cd814d3b2abd65b88a879f73af5e" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x62d19a29c673db27c02c5f4266638278212e134f25a30d0b38f28b9254cfeeee" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x9c96a5eb7945b8e666fbab617c072cba6f23d843ac221003396141805878d8e8" - }, - { - "amount": "15", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x9d67e8fb6762dd435cc749ffdc1b2a5a8640b45aecbfd084fc18103ee08b9ba2" - }, - { - "amount": "100", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xbddda2e0eb2449fb616b03d69f6c237bc7276c66feaefd9de59ca6ffdd81507a" - }, - { - "amount": "20", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xa3f6ec1b231c89dd1e34e0247266532aa7164ed1687e17d34c582c748b228d0d" - }, - { - "amount": "35", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xf55115db5cfc11464a8d994647a027d4e1013143f7d54fe34f9cc94b39ba293c" - }, - { - "amount": "35", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xcba0565d95ca237cca151d88bd19dc7cebd5c1820445fe1966be1f4b6f175e24" - }, - { - "amount": "15", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x32ac32e5d7f8ab38ecf4467c3c116ad676d9bfdc8b11ac8aa618b079837a7fa3" - }, - { - "amount": "15", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x481b1bac5120af196dfac4f480e85e26dd9e6c0b4bab3f8be506e5484a21292b" - }, - { - "amount": "15", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x50a1a2928181e56380fe21091786fcd82f3a8fafd9b13fee65a32cff117292ec" - }, - { - "amount": "20", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x758533d671732b224d241e109691655d01ea58db862b8b366a6c8ede67109ce7" - }, - { - "amount": "20", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xaabdfe069e1e4d75132cbf18cca3991d58473fcb82f47c9adb516da4eba4b1cf" - }, - { - "amount": "5", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x982e8902d21680a0e57e97b33efcdd7ad7b08bea0873c82f933a4c73abdc972a" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0xd1641e0814331fdfd0553c3785de9c5697d575dac44ca78767569d68560017d4" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x4490efa6050f006d31faaebb4411335be52c29edf449bed4380664c8377529a3" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x2e64b89e05ee0a9646251b7acec1614b945417ec750b6808657c59d7ab2db2a0" - }, - { - "amount": "10", - "user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f", - "tranche_id": 11, - "tx": "0x248d34bdf3224997d3f0e2cd55f75e1b13937591a64cff99abf513bada6da087" - } - ], - "withdrawals": [], - "total_tokens": "500", - "withdrawn_tokens": "0", - "remaining_tokens": "500" - }, { "address": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c", "deposits": [ @@ -12929,7 +12984,7 @@ "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "3732368.4671", "total_removed": "74162.9780761646031", - "locked_amount": "2961889.80873522203511981118", + "locked_amount": "2953745.10332375333468142534", "deposits": [ { "amount": "1998.95815", @@ -13642,7 +13697,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "15788853.065470999700000001", "total_removed": "0", - "locked_amount": "15721455.8528753381544497845048217020856988", + "locked_amount": "15692723.1095097283484980512258269601432819", "deposits": [ { "amount": "16249.93", @@ -15593,8 +15648,8 @@ "tranche_start": "2021-11-05T00:00:00.000Z", "tranche_end": "2023-05-05T00:00:00.000Z", "total_added": "14597706.0446472999", - "total_removed": "2040741.482328946820515322", - "locked_amount": "8867191.40430055827121812084261657", + "total_removed": "2059027.100116428494952272", + "locked_amount": "8840529.01652403262655557751496936", "deposits": [ { "amount": "129284.449", @@ -15813,6 +15868,16 @@ "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", "tx": "0x0014532478275404cdb672384ad9bf4dc4202b7ccd60682b7d8bfd5b997d3c00" }, + { + "amount": "499.42967288092014825", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0xf2fb89f9f7804f79d49dd03026a6f554befce5e9f368893cdd1b911467e61341" + }, + { + "amount": "17786.1881146007542887", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tx": "0x3a572e6cf3a23c7fd307c4feb9b9c3d770a53d3ba083cd346c3650f8c647d574" + }, { "amount": "1384.357697656285885", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", @@ -17301,6 +17366,12 @@ "tranche_id": 3, "tx": "0x0014532478275404cdb672384ad9bf4dc4202b7ccd60682b7d8bfd5b997d3c00" }, + { + "amount": "499.42967288092014825", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0xf2fb89f9f7804f79d49dd03026a6f554befce5e9f368893cdd1b911467e61341" + }, { "amount": "1384.357697656285885", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", @@ -18425,8 +18496,8 @@ } ], "total_tokens": "359123.469575", - "withdrawn_tokens": "140494.75538433103984475", - "remaining_tokens": "218628.71419066896015525" + "withdrawn_tokens": "140994.185057211959993", + "remaining_tokens": "218129.284517788040007" }, { "address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB", @@ -18613,6 +18684,12 @@ } ], "withdrawals": [ + { + "amount": "17786.1881146007542887", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tranche_id": 3, + "tx": "0x3a572e6cf3a23c7fd307c4feb9b9c3d770a53d3ba083cd346c3650f8c647d574" + }, { "amount": "14131.31542114178485692", "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", @@ -18813,8 +18890,8 @@ } ], "total_tokens": "1266324.603486", - "withdrawn_tokens": "479570.19259359168266556", - "remaining_tokens": "786754.41089240831733444" + "withdrawn_tokens": "497356.38070819243695426", + "remaining_tokens": "768968.22277780756304574" }, { "address": "0xC5d9221EB9c28A69859264c0A2Fe0d3272228296", @@ -19410,8 +19487,8 @@ "tranche_start": "2021-10-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "5778205.3912159303", - "total_removed": "1341050.833974497576969742", - "locked_amount": "3186577.54862608898743900152867561", + "total_removed": "1354392.149662275357179742", + "locked_amount": "3176043.07796280512852898867591957", "deposits": [ { "amount": "552496.6455", @@ -19555,6 +19632,11 @@ } ], "withdrawals": [ + { + "amount": "13341.31568777778021", + "user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90", + "tx": "0xcaaf2eae83061031d9ffb54f095e12ea6bf1438a5d83c8ef48bd7b73aa67c0c1" + }, { "amount": "32683.576612762644585", "user": "0x91715128a71c9C734CDC20E5EdEEeA02E72e428E", @@ -20346,6 +20428,12 @@ } ], "withdrawals": [ + { + "amount": "13341.31568777778021", + "user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90", + "tranche_id": 4, + "tx": "0xcaaf2eae83061031d9ffb54f095e12ea6bf1438a5d83c8ef48bd7b73aa67c0c1" + }, { "amount": "19314.782639120135472", "user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90", @@ -20396,8 +20484,8 @@ } ], "total_tokens": "331498.5873", - "withdrawn_tokens": "135489.51216912215997", - "remaining_tokens": "196009.07513087784003" + "withdrawn_tokens": "148830.82785689994018", + "remaining_tokens": "182667.75944310005982" }, { "address": "0x16da609341ed67750A8BCC5AAa2005471006Cd77", @@ -20467,7 +20555,7 @@ "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "472355.6199999996", "total_removed": "0", - "locked_amount": "469328.37107805075503164173921864", + "locked_amount": "468037.79594848260006626206189752", "deposits": [ { "amount": "3000", @@ -46120,7 +46208,7 @@ "tranche_start": "2021-12-05T00:00:00.000Z", "tranche_end": "2022-06-05T00:00:00.000Z", "total_added": "171288.42", - "total_removed": "22821.6538075488377", + "total_removed": "23771.6538075488377", "locked_amount": "0", "deposits": [ { @@ -50490,6 +50578,21 @@ "user": "0x22771eDF88B1cec6E8849Af5c3C140FAc3EAcA5C", "tx": "0x4b565d215b1149dad23489678d7b51688e35e919039b6f8b221992c733888e19" }, + { + "amount": "500", + "user": "0x3Be204e2979113FA52fA6b0942AF8E4AD63bEe13", + "tx": "0xcc52eaea8fa6218e622c1bb487666b9eff1c12255c78d67c68a7377fbff1cf6f" + }, + { + "amount": "200", + "user": "0x7F1e7f6253DD2c64B6818FB2376F0e38e46F9cCD", + "tx": "0xe9bc87b901a15d3c5d75c07e65425e154b66aba4782f112a663e8a1690ab17f9" + }, + { + "amount": "250", + "user": "0x5DE853CB6813f9Aefd25635776139d8C0AAaF77A", + "tx": "0xdac530dc97a09dce8335182feaaab51f82b81ec29c04d3e4d38c226cdee629a3" + }, { "amount": "65.56208982", "user": "0x36d345DCEB35816AD557355fA51E7C99FEE7d18E", @@ -56917,10 +57020,17 @@ "tx": "0x057f65938b1360b6d2c4ebf5e789c67897cf60cb6a13f947004def03891afbd8" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "500", + "user": "0x3Be204e2979113FA52fA6b0942AF8E4AD63bEe13", + "tranche_id": 6, + "tx": "0xcc52eaea8fa6218e622c1bb487666b9eff1c12255c78d67c68a7377fbff1cf6f" + } + ], "total_tokens": "500", - "withdrawn_tokens": "0", - "remaining_tokens": "500" + "withdrawn_tokens": "500", + "remaining_tokens": "0" }, { "address": "0xdc324433c9936c38c9334B373Db2468719CC17B9", @@ -60439,10 +60549,17 @@ "tx": "0x9f916cf09e8a3c4ade0ffce5190db464d0a2b1dadba78e1ee7ba5d6e751d6148" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "250", + "user": "0x5DE853CB6813f9Aefd25635776139d8C0AAaF77A", + "tranche_id": 6, + "tx": "0xdac530dc97a09dce8335182feaaab51f82b81ec29c04d3e4d38c226cdee629a3" + } + ], "total_tokens": "250", - "withdrawn_tokens": "0", - "remaining_tokens": "250" + "withdrawn_tokens": "250", + "remaining_tokens": "0" }, { "address": "0x72e6631ADf75E813c09eCB4ec69B45F461e4fC2a", @@ -64423,10 +64540,17 @@ "tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "200", + "user": "0x7F1e7f6253DD2c64B6818FB2376F0e38e46F9cCD", + "tranche_id": 6, + "tx": "0xe9bc87b901a15d3c5d75c07e65425e154b66aba4782f112a663e8a1690ab17f9" + } + ], "total_tokens": "200", - "withdrawn_tokens": "0", - "remaining_tokens": "200" + "withdrawn_tokens": "200", + "remaining_tokens": "0" }, { "address": "0x9195bCD64d01C55a495f67364fF1d24C9df59540", diff --git a/apps/static/src/assets/stagnet1-tranches.json b/apps/static/src/assets/stagnet1-tranches.json index ac492c4ca..17ea4243e 100644 --- a/apps/static/src/assets/stagnet1-tranches.json +++ b/apps/static/src/assets/stagnet1-tranches.json @@ -38,7 +38,7 @@ "tranche_end": "2022-11-26T13:48:10.000Z", "total_added": "100", "total_removed": "0", - "locked_amount": "47.187921740233385", + "locked_amount": "46.91470700152207", "deposits": [ { "amount": "100", @@ -242,7 +242,7 @@ "tranche_end": "2022-10-12T00:53:20.000Z", "total_added": "100", "total_removed": "0", - "locked_amount": "34.71173579401319", + "locked_amount": "34.438521055301874", "deposits": [ { "amount": "100", From be3b416176cf815adc947598df4dd98007f5b38a Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Wed, 8 Jun 2022 01:47:31 -0700 Subject: [PATCH 06/27] Fix/Default market and tabs navigation (#518) * fix: dont use localstorage for navigation, remove query params for tabs * chore: lint * fix: revert to using url rather than data test id * chore: lint * chore: remove arrow down ref from markets page object --- .../src/support/pages/base-page.ts | 3 +- .../src/support/pages/markets-page.ts | 9 ---- .../step_definitions/markets-page.step.ts | 1 - .../components/grid-tabs/grid-tabs.tsx | 25 +-------- apps/trading/components/navbar/navbar.tsx | 52 +++---------------- apps/trading/pages/index.page.tsx | 8 +-- .../trading/pages/markets/[marketId].page.tsx | 3 +- apps/trading/pages/markets/trade-grid.tsx | 6 +-- apps/trading/pages/portfolio/index.page.tsx | 4 +- libs/candles-chart/src/lib/candles-chart.tsx | 2 +- .../components/landing/select-market-list.tsx | 5 +- .../markets-container/markets-container.tsx | 4 +- 12 files changed, 21 insertions(+), 101 deletions(-) diff --git a/apps/trading-e2e/src/support/pages/base-page.ts b/apps/trading-e2e/src/support/pages/base-page.ts index 084ad09ef..6f62a0d70 100644 --- a/apps/trading-e2e/src/support/pages/base-page.ts +++ b/apps/trading-e2e/src/support/pages/base-page.ts @@ -20,11 +20,10 @@ export default class BasePage { .should('be.visible') .click({ force: true }); cy.url().should('include', '/portfolio'); - cy.getByTestId('portfolio'); } navigateToMarkets() { - cy.getByTestId('markets-link').should('be.visible').click({ force: true }); + cy.get(`a[href='${this.marketsUrl}']`).should('be.visible').click(); cy.url().should('include', '/markets'); } diff --git a/apps/trading-e2e/src/support/pages/markets-page.ts b/apps/trading-e2e/src/support/pages/markets-page.ts index b787da89b..7335b1546 100644 --- a/apps/trading-e2e/src/support/pages/markets-page.ts +++ b/apps/trading-e2e/src/support/pages/markets-page.ts @@ -8,7 +8,6 @@ export default class MarketPage extends BasePage { marketRowPrices = 'flash-cell'; marketRowDescription = 'name'; marketStateColId = 'data'; - openMarketMenu = 'arrow-down'; validateMarketsAreDisplayed() { // We need this to ensure that ag-grid is fully rendered before asserting @@ -57,13 +56,5 @@ export default class MarketPage extends BasePage { clickOnMarket(text: string) { cy.get(`[col-id=${this.marketStateColId}]`).should('be.visible'); cy.get(`[col-id=${this.marketStateColId}]`).contains(text).click(); - cy.url({ timeout: 8000 }).should( - 'contain', - 'portfolio=orders&trade=orderbook' - ); - } - - clickOpenMarketMenu() { - cy.getByTestId(this.openMarketMenu).click(); } } diff --git a/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts b/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts index a9722a928..73d3952d2 100644 --- a/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts @@ -19,7 +19,6 @@ const mockMarkets = () => { Then('I navigate to markets page', () => { mockMarkets(); marketsPage.navigateToMarkets(); - marketsPage.clickOpenMarketMenu(); cy.wait('@Markets'); }); diff --git a/apps/trading/components/grid-tabs/grid-tabs.tsx b/apps/trading/components/grid-tabs/grid-tabs.tsx index e8b938561..5556d281f 100644 --- a/apps/trading/components/grid-tabs/grid-tabs.tsx +++ b/apps/trading/components/grid-tabs/grid-tabs.tsx @@ -1,38 +1,17 @@ import * as Tabs from '@radix-ui/react-tabs'; import classNames from 'classnames'; -import { useRouter } from 'next/router'; import type { ReactElement, ReactNode } from 'react'; -import { Children, isValidElement, useEffect, useState } from 'react'; +import { Children, isValidElement, useState } from 'react'; interface GridTabsProps { children: ReactElement[]; - group: string; } -export const GridTabs = ({ children, group }: GridTabsProps) => { - const { query, asPath, replace } = useRouter(); +export const GridTabs = ({ children }: GridTabsProps) => { const [activeTab, setActiveTab] = useState(() => { - const tab = query[group]; - - if (typeof tab === 'string') { - return tab; - } - - // Default to first tab return children[0].props.id; }); - // Update the query string in the url when the active tab changes - // uses group property as the query string key - useEffect(() => { - const [url, queryString] = asPath.split('?'); - const searchParams = new URLSearchParams(queryString); - searchParams.set(group, activeTab as string); - replace(`${url}?${searchParams.toString()}`); - // replace and using asPath causes a render loop - // eslint-disable-next-line - }, [activeTab, group]); - return ( { - const initNavItemsState = [ - { - name: t('Portfolio'), - path: '/portfolio', - testId: 'portfolio-link', - slug: '', - }, - ]; - const [navItems, setNavItems] = useState(initNavItemsState); - const marketId = LocalStorage.getItem('marketId') ?? ''; - - useEffect(() => { - setNavItems([ - { - name: t('Trading'), - path: '/markets', - testId: 'markets-link', - slug: marketId, - }, - { - name: t('Portfolio'), - path: '/portfolio', - testId: 'portfolio-link', - slug: '', - }, - ]); - }, [marketId]); - return ( @@ -53,30 +27,18 @@ interface NavLinkProps { path: string; exact?: boolean; testId?: string; - slug?: string; } -const NavLink = ({ - name, - path, - exact, - testId = name, - slug = '', -}: NavLinkProps) => { +const NavLink = ({ name, path, exact, testId = name }: NavLinkProps) => { const router = useRouter(); const isActive = router.asPath === path || (!exact && router.asPath.startsWith(path)); - const href = slug !== '' ? `${path}/${slug}` : path; return ( { - e.preventDefault(); - router.push(href); - }} + href={path} > {name} diff --git a/apps/trading/pages/index.page.tsx b/apps/trading/pages/index.page.tsx index 0f36de7b2..ee52b352b 100644 --- a/apps/trading/pages/index.page.tsx +++ b/apps/trading/pages/index.page.tsx @@ -1,5 +1,4 @@ import { gql, useQuery } from '@apollo/client'; -import { LocalStorage } from '@vegaprotocol/react-helpers'; import { MarketTradingMode } from '@vegaprotocol/types'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import sortBy from 'lodash/sortBy'; @@ -36,13 +35,10 @@ export function Index() { // should be the oldest market that is currently trading in continuous mode(i.e. not in auction). const { data, error, loading } = useQuery(MARKETS_QUERY); const setLandingDialog = useGlobalStore((state) => state.setLandingDialog); - const lastSelectedMarketId = LocalStorage.getItem('marketId'); useEffect(() => { if (data) { - const marketId = lastSelectedMarketId - ? lastSelectedMarketId - : marketList(data)[0]?.id; + const marketId = marketList(data)[0]?.id; // If a default market is found, go to it with the landing dialog open if (marketId) { @@ -54,7 +50,7 @@ export function Index() { replace('/markets'); } } - }, [data, lastSelectedMarketId, replace, setLandingDialog]); + }, [data, replace, setLandingDialog]); return ( diff --git a/apps/trading/pages/markets/[marketId].page.tsx b/apps/trading/pages/markets/[marketId].page.tsx index 9fbdc0315..c93841bf4 100644 --- a/apps/trading/pages/markets/[marketId].page.tsx +++ b/apps/trading/pages/markets/[marketId].page.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react'; import debounce from 'lodash/debounce'; import { PageQueryContainer } from '../../components/page-query-container'; import { TradeGrid, TradePanels } from './trade-grid'; -import { LocalStorage, t } from '@vegaprotocol/react-helpers'; +import { t } from '@vegaprotocol/react-helpers'; import { useGlobalStore } from '../../stores'; import { LandingDialog } from '@vegaprotocol/market-list'; import type { Market, MarketVariables } from './__generated__/Market'; @@ -74,7 +74,6 @@ const MarketPage = ({ id }: { id?: string }) => { ); } - LocalStorage.setItem('marketId', marketId); return ( query={MARKET_QUERY} diff --git a/apps/trading/pages/markets/trade-grid.tsx b/apps/trading/pages/markets/trade-grid.tsx index ec5b4c53f..56b20c5ae 100644 --- a/apps/trading/pages/markets/trade-grid.tsx +++ b/apps/trading/pages/markets/trade-grid.tsx @@ -99,7 +99,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
        - + @@ -112,7 +112,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => { - + @@ -122,7 +122,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => { - + diff --git a/apps/trading/pages/portfolio/index.page.tsx b/apps/trading/pages/portfolio/index.page.tsx index a362bebea..52deac12f 100644 --- a/apps/trading/pages/portfolio/index.page.tsx +++ b/apps/trading/pages/portfolio/index.page.tsx @@ -21,7 +21,7 @@ const Portfolio = () => {
  • - +

    @@ -56,7 +56,7 @@ const Portfolio = () => {

    - + diff --git a/libs/candles-chart/src/lib/candles-chart.tsx b/libs/candles-chart/src/lib/candles-chart.tsx index 518bc3d18..dac8075dd 100644 --- a/libs/candles-chart/src/lib/candles-chart.tsx +++ b/libs/candles-chart/src/lib/candles-chart.tsx @@ -60,7 +60,7 @@ export const CandlesChartContainer = ({ return (
    -
    +
    diff --git a/apps/token/src/routes/home/token-details/token-details.tsx b/apps/token/src/routes/home/token-details/token-details.tsx index 44a9b7b79..b87b04426 100644 --- a/apps/token/src/routes/home/token-details/token-details.tsx +++ b/apps/token/src/routes/home/token-details/token-details.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next'; import { Callout, Link, Intent, Splash } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { useTranches } from '../../../hooks/use-tranches'; import type { BigNumber } from '../../../lib/bignumber'; diff --git a/apps/token/src/routes/redemption/tranche/index.tsx b/apps/token/src/routes/redemption/tranche/index.tsx index ba99db188..cd13fd949 100644 --- a/apps/token/src/routes/redemption/tranche/index.tsx +++ b/apps/token/src/routes/redemption/tranche/index.tsx @@ -3,7 +3,7 @@ import { Trans, useTranslation } from 'react-i18next'; import { Link, useParams, useOutletContext } from 'react-router-dom'; import { TransactionCallout } from '../../../components/transaction-callout'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { useAppState } from '../../../contexts/app-state/app-state-context'; import { useContracts } from '../../../contexts/contracts/contracts-context'; import { diff --git a/apps/token/src/routes/staking/associate/associate-transaction.tsx b/apps/token/src/routes/staking/associate/associate-transaction.tsx index 14d3e27a7..a5e4021bd 100644 --- a/apps/token/src/routes/staking/associate/associate-transaction.tsx +++ b/apps/token/src/routes/staking/associate/associate-transaction.tsx @@ -5,7 +5,7 @@ import { Intent, Loader, } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { Link as RouteLink } from 'react-router-dom'; diff --git a/apps/token/src/routes/staking/staking.tsx b/apps/token/src/routes/staking/staking.tsx index 9abe8c52e..be36cbfa7 100644 --- a/apps/token/src/routes/staking/staking.tsx +++ b/apps/token/src/routes/staking/staking.tsx @@ -5,7 +5,7 @@ import { Link as RouteLink } from 'react-router-dom'; import { BulletHeader } from '../../components/bullet-header'; import { Link } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { Links } from '../../config'; import { AppStateActionType, diff --git a/apps/token/src/routes/staking/validator-table.tsx b/apps/token/src/routes/staking/validator-table.tsx index 300940d56..041d0ab66 100644 --- a/apps/token/src/routes/staking/validator-table.tsx +++ b/apps/token/src/routes/staking/validator-table.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { BigNumber } from '../../lib/bignumber'; import { formatNumber } from '../../lib/format-number'; diff --git a/apps/token/src/routes/tranches/tranche.tsx b/apps/token/src/routes/tranches/tranche.tsx index 39b844c8a..bff32105a 100644 --- a/apps/token/src/routes/tranches/tranche.tsx +++ b/apps/token/src/routes/tranches/tranche.tsx @@ -1,4 +1,3 @@ -import { useEnvironment } from '@vegaprotocol/react-helpers'; import type { Tranche as ITranche } from '@vegaprotocol/smart-contracts'; import { Link } from '@vegaprotocol/ui-toolkit'; import { useWeb3React } from '@web3-react/core'; @@ -8,6 +7,7 @@ import { useParams } from 'react-router'; import { Navigate } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { BigNumber } from '../../lib/bignumber'; import { formatNumber } from '../../lib/format-number'; import { TrancheItem } from '../redemption/tranche-item'; diff --git a/apps/token/src/routes/withdrawals/index.tsx b/apps/token/src/routes/withdrawals/index.tsx index 207ee5a8e..fea7ecd1d 100644 --- a/apps/token/src/routes/withdrawals/index.tsx +++ b/apps/token/src/routes/withdrawals/index.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { Heading } from '../../components/heading'; import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { SplashLoader } from '../../components/splash-loader'; diff --git a/apps/trading-e2e/src/support/vega-wallet/index.ts b/apps/trading-e2e/src/support/vega-wallet/index.ts index ce921871d..3c7d6a4ad 100644 --- a/apps/trading-e2e/src/support/vega-wallet/index.ts +++ b/apps/trading-e2e/src/support/vega-wallet/index.ts @@ -7,6 +7,7 @@ export default class VegaWallet { walletInputError = 'input-wallet-error'; walletFormError = 'form-error'; inputError = 'input-error-text'; + connectNetworkBtn = 'connect-network'; openVegaWalletConnectDialog() { this.clickOnWalletConnectDialog(); diff --git a/apps/trading/.env b/apps/trading/.env index 09d342bd1..d31c82976 100644 --- a/apps/trading/.env +++ b/apps/trading/.env @@ -24,3 +24,4 @@ NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" +NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}' diff --git a/apps/trading/.env.devnet b/apps/trading/.env.devnet index 43dfa5712..37006e725 100644 --- a/apps/trading/.env.devnet +++ b/apps/trading/.env.devnet @@ -1,6 +1,7 @@ # App configuration variables NX_VEGA_ENV = "DEVNET" NX_VEGA_URL = "https://n04.d.vega.xyz/query" +NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}' NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" -NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" \ No newline at end of file +NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" diff --git a/apps/trading/.env.mainnet b/apps/trading/.env.mainnet index 037533823..d408380b0 100644 --- a/apps/trading/.env.mainnet +++ b/apps/trading/.env.mainnet @@ -1,6 +1,7 @@ # App configuration variables NX_VEGA_ENV = "MAINNET" NX_VEGA_URL = "https://api.token.vega.xyz/query" +NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}' NX_ETHEREUM_CHAIN_ID = 1 NX_ETHEREUM_PROVIDER_URL = "https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" -NX_ETHERSCAN_URL = "https://etherscan.io" \ No newline at end of file +NX_ETHERSCAN_URL = "https://etherscan.io" diff --git a/apps/trading/.env.stagnet1 b/apps/trading/.env.stagnet1 index 17d96f986..e16cdde77 100644 --- a/apps/trading/.env.stagnet1 +++ b/apps/trading/.env.stagnet1 @@ -1,6 +1,7 @@ # App configuration variables NX_VEGA_ENV = "STAGNET" NX_VEGA_URL = "https://n03.s.vega.xyz/query" +NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}' NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" -NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" \ No newline at end of file +NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" diff --git a/apps/trading/.env.stagnet2 b/apps/trading/.env.stagnet2 index b7bcbb54a..b71d4f1ab 100644 --- a/apps/trading/.env.stagnet2 +++ b/apps/trading/.env.stagnet2 @@ -1,6 +1,7 @@ # App configuration variables NX_VEGA_ENV = "STAGNET2" NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query" +NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}' NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" -NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" \ No newline at end of file +NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" diff --git a/apps/trading/.env.testnet b/apps/trading/.env.testnet index 668cc42a7..bb2731e4b 100644 --- a/apps/trading/.env.testnet +++ b/apps/trading/.env.testnet @@ -1,6 +1,7 @@ # App configuration variables NX_VEGA_ENV = "TESTNET" NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" +NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}' NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" -NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" \ No newline at end of file +NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index 3396dc2af..ec79754c8 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -7,7 +7,11 @@ import { VegaManageDialog, VegaWalletProvider, } from '@vegaprotocol/wallet'; -import { EnvironmentProvider } from '@vegaprotocol/react-helpers'; +import { + useEnvironment, + EnvironmentProvider, + NetworkSwitcherDialog, +} from '@vegaprotocol/network-switcher'; import { Connectors } from '../lib/vega-connectors'; import { useMemo } from 'react'; import { createClient } from '../lib/apollo-client'; @@ -18,10 +22,57 @@ import { VegaWalletConnectButton } from '../components/vega-wallet-connect-butto import './styles.css'; import { useGlobalStore } from '../stores'; -function VegaTradingApp({ Component, pageProps }: AppProps) { - const client = useMemo(() => createClient(process.env['NX_VEGA_URL']), []); +function AppBody({ Component, pageProps }: AppProps) { const store = useGlobalStore(); - const [theme, toggleTheme] = useThemeSwitcher(); + const { VEGA_NETWORKS } = useEnvironment(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, toggleTheme] = useThemeSwitcher(); + + return ( +
    +
    + +
    + { + store.setVegaWalletConnectDialog(open); + }} + setManageDialog={(open) => { + store.setVegaWalletManageDialog(open); + }} + /> + +
    +
    +
    + {/* @ts-ignore conflict between @types/react and nextjs internal types */} + +
    + store.setVegaWalletConnectDialog(open)} + /> + store.setVegaWalletManageDialog(open)} + /> + store.setVegaNetworkSwitcherDialog(open)} + onConnect={({ network }) => { + if (VEGA_NETWORKS[network]) { + window.location.href = VEGA_NETWORKS[network]; + } + }} + /> +
    + ); +} + +function VegaTradingApp(props: AppProps) { + const [theme] = useThemeSwitcher(); + const client = useMemo(() => createClient(process.env['NX_VEGA_URL']), []); return ( @@ -48,39 +99,7 @@ function VegaTradingApp({ Component, pageProps }: AppProps) { href="https://static.vega.xyz/fonts.css" /> -
    -
    - -
    - { - store.setVegaWalletConnectDialog(open); - }} - setManageDialog={(open) => { - store.setVegaWalletManageDialog(open); - }} - /> - -
    -
    -
    - {/* @ts-ignore conflict between @types/react and nextjs internal types */} - -
    - - store.setVegaWalletConnectDialog(open) - } - /> - - store.setVegaWalletManageDialog(open) - } - /> -
    + diff --git a/apps/trading/pages/portfolio/deposit/deposit-container.tsx b/apps/trading/pages/portfolio/deposit/deposit-container.tsx index 27c11f2ff..a4aaa68db 100644 --- a/apps/trading/pages/portfolio/deposit/deposit-container.tsx +++ b/apps/trading/pages/portfolio/deposit/deposit-container.tsx @@ -2,7 +2,8 @@ import { gql } from '@apollo/client'; import { PageQueryContainer } from '../../../components/page-query-container'; import type { DepositPage } from './__generated__/DepositPage'; import { DepositManager } from '@vegaprotocol/deposits'; -import { t, useEnvironment } from '@vegaprotocol/react-helpers'; +import { t } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { Splash } from '@vegaprotocol/ui-toolkit'; import { ASSET_FRAGMENT } from '../../../lib/query-fragments'; diff --git a/apps/trading/stores/global.ts b/apps/trading/stores/global.ts index 7ade770d5..4e5591d2c 100644 --- a/apps/trading/stores/global.ts +++ b/apps/trading/stores/global.ts @@ -6,6 +6,8 @@ interface GlobalStore { setVegaWalletConnectDialog: (isOpen: boolean) => void; vegaWalletManageDialog: boolean; setVegaWalletManageDialog: (isOpen: boolean) => void; + vegaNetworkSwitcherDialog: boolean; + setVegaNetworkSwitcherDialog: (isOpen: boolean) => void; landingDialog: boolean; setLandingDialog: (isOpen: boolean) => void; } @@ -19,6 +21,10 @@ export const useGlobalStore = create((set: SetState) => ({ setVegaWalletManageDialog: (isOpen: boolean) => { set({ vegaWalletManageDialog: isOpen }); }, + vegaNetworkSwitcherDialog: false, + setVegaNetworkSwitcherDialog: (isOpen: boolean) => { + set({ vegaNetworkSwitcherDialog: isOpen }); + }, landingDialog: false, setLandingDialog: (isOpen: boolean) => { set({ landingDialog: isOpen }); diff --git a/libs/network-switcher/.babelrc b/libs/network-switcher/.babelrc new file mode 100644 index 000000000..ccae900be --- /dev/null +++ b/libs/network-switcher/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/network-switcher/.eslintrc.json b/libs/network-switcher/.eslintrc.json new file mode 100644 index 000000000..734ddacee --- /dev/null +++ b/libs/network-switcher/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/network-switcher/README.md b/libs/network-switcher/README.md new file mode 100644 index 000000000..7053a1c52 --- /dev/null +++ b/libs/network-switcher/README.md @@ -0,0 +1,17 @@ +# network-switcher + +This library was generated with [Nx](https://nx.dev). + +## Prerequisites + +Two environment variables need to be present for any app consuming this library. + +`NX_VEGA_ENV` is the name of the environment. + +`NX_VEGA_REST` is the REST endpoint for the environment. + +For examples, see Block Explorer's .env files [here](../../apps/explorer) + +## Running unit tests + +Run `nx test network-switcher` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/network-switcher/jest.config.js b/libs/network-switcher/jest.config.js new file mode 100644 index 000000000..b3eaa4301 --- /dev/null +++ b/libs/network-switcher/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + displayName: 'network-switcher', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/network-switcher', +}; diff --git a/libs/network-switcher/package.json b/libs/network-switcher/package.json new file mode 100644 index 000000000..fd7f0c0ea --- /dev/null +++ b/libs/network-switcher/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vegaprotocol/network-switcher", + "version": "0.0.1" +} diff --git a/libs/network-switcher/project.json b/libs/network-switcher/project.json new file mode 100644 index 000000000..6047531b7 --- /dev/null +++ b/libs/network-switcher/project.json @@ -0,0 +1,43 @@ +{ + "root": "libs/network-switcher", + "sourceRoot": "libs/network-switcher/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/network-switcher", + "tsConfig": "libs/network-switcher/tsconfig.lib.json", + "project": "libs/network-switcher/package.json", + "entryFile": "libs/network-switcher/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/network-switcher/README.md", + "input": ".", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/network-switcher/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/network-switcher"], + "options": { + "jestConfig": "libs/network-switcher/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/network-switcher/src/components/index.ts b/libs/network-switcher/src/components/index.ts new file mode 100644 index 000000000..a62c7028f --- /dev/null +++ b/libs/network-switcher/src/components/index.ts @@ -0,0 +1,2 @@ +export * from './network-switcher'; +export * from './network-switcher-dialog'; diff --git a/libs/network-switcher/src/components/network-switcher-dialog/index.tsx b/libs/network-switcher/src/components/network-switcher-dialog/index.tsx new file mode 100644 index 000000000..6577ad17b --- /dev/null +++ b/libs/network-switcher/src/components/network-switcher-dialog/index.tsx @@ -0,0 +1 @@ +export { NetworkSwitcherDialog } from './network-switcher-dialog'; diff --git a/libs/network-switcher/src/components/network-switcher-dialog/network-switcher-dialog.tsx b/libs/network-switcher/src/components/network-switcher-dialog/network-switcher-dialog.tsx new file mode 100644 index 000000000..b1be6941f --- /dev/null +++ b/libs/network-switcher/src/components/network-switcher-dialog/network-switcher-dialog.tsx @@ -0,0 +1,37 @@ +import type { ComponentProps } from 'react'; +import { t } from '@vegaprotocol/react-helpers'; +import type { Intent } from '@vegaprotocol/ui-toolkit'; +import { Dialog } from '@vegaprotocol/ui-toolkit'; +import { NetworkSwitcher } from '../network-switcher'; + +type NetworkSwitcherDialogProps = Pick< + ComponentProps, + 'onConnect' | 'onError' +> & { + dialogOpen: boolean; + setDialogOpen: (dialogOpen: boolean) => void; + intent?: Intent; +}; + +export const NetworkSwitcherDialog = ({ + dialogOpen, + setDialogOpen, + intent, + onConnect, + onError, +}: NetworkSwitcherDialogProps) => { + return ( + + setDialogOpen(false)} + /> + + ); +}; diff --git a/libs/network-switcher/src/components/network-switcher/index.tsx b/libs/network-switcher/src/components/network-switcher/index.tsx new file mode 100644 index 000000000..d0042d5f6 --- /dev/null +++ b/libs/network-switcher/src/components/network-switcher/index.tsx @@ -0,0 +1 @@ +export { NetworkSwitcher } from './network-switcher'; diff --git a/libs/network-switcher/src/components/network-switcher/network-switcher.tsx b/libs/network-switcher/src/components/network-switcher/network-switcher.tsx new file mode 100644 index 000000000..cff092bef --- /dev/null +++ b/libs/network-switcher/src/components/network-switcher/network-switcher.tsx @@ -0,0 +1,47 @@ +import { useForm, Controller } from 'react-hook-form'; +import { Button, Select } from '@vegaprotocol/ui-toolkit'; +import type { Networks } from '@vegaprotocol/react-helpers'; +import { t } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '../../hooks'; + +type NetworkState = { + network: Networks; +}; + +type NetworkSwitcherProps = { + onConnect: (network: NetworkState) => void; + onError?: () => void; + onClose: () => void; +}; + +export const NetworkSwitcher = ({ onConnect }: NetworkSwitcherProps) => { + const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment(); + const { control, handleSubmit } = useForm({ + defaultValues: { + network: VEGA_ENV, + }, + }); + + return ( +
    +
    + ( + + )} + /> +
    +
    + +
    +
    + ); +}; diff --git a/libs/network-switcher/src/hooks/index.ts b/libs/network-switcher/src/hooks/index.ts new file mode 100644 index 000000000..edc0925c5 --- /dev/null +++ b/libs/network-switcher/src/hooks/index.ts @@ -0,0 +1 @@ +export * from './use-environment'; diff --git a/libs/react-helpers/src/hooks/use-environment.tsx b/libs/network-switcher/src/hooks/use-environment.tsx similarity index 73% rename from libs/react-helpers/src/hooks/use-environment.tsx rename to libs/network-switcher/src/hooks/use-environment.tsx index fdf77f7f5..50d426708 100644 --- a/libs/react-helpers/src/hooks/use-environment.tsx +++ b/libs/network-switcher/src/hooks/use-environment.tsx @@ -1,6 +1,9 @@ +import { useState } from 'react'; import type { ReactNode } from 'react'; import { createContext, useContext } from 'react'; -import type { Networks } from '../lib/environment'; +import type { Networks } from '@vegaprotocol/react-helpers'; + +const isBrowser = typeof window !== 'undefined'; declare global { interface Window { @@ -50,13 +53,14 @@ export const ContractAddresses: { [key in Networks]: VegaContracts } = { }; type EnvironmentProviderProps = { - definintions?: Partial; + definitions?: Partial; children?: ReactNode; }; export const ENV_KEYS = [ 'VEGA_URL', 'VEGA_ENV', + 'VEGA_NETWORKS', 'ETHEREUM_CHAIN_ID', 'ETHEREUM_PROVIDER_URL', 'ETHERSCAN_URL', @@ -69,12 +73,15 @@ type RawEnvironment = Record; export type Environment = { VEGA_URL: string; VEGA_ENV: Networks; + VEGA_NETWORKS: Record; ETHEREUM_CHAIN_ID: number; ETHEREUM_PROVIDER_URL: string; ETHERSCAN_URL: string; ADDRESSES: VegaContracts; }; +type EnvironmentState = Environment; + const getBundledEnvironmentValue = (key: EnvKey) => { switch (key) { // need to have these hardcoded so on build time we can insert sensible defaults @@ -88,6 +95,8 @@ const getBundledEnvironmentValue = (key: EnvKey) => { return process.env['NX_ETHEREUM_PROVIDER_URL']; case 'ETHERSCAN_URL': return process.env['NX_ETHERSCAN_URL']; + case 'VEGA_NETWORKS': + return process.env['NX_VEGA_NETWORKS']; } }; @@ -97,38 +106,67 @@ const transformValue = (key: EnvKey, value?: string) => { return value as Networks; case 'ETHEREUM_CHAIN_ID': return value && Number(value); + case 'VEGA_NETWORKS': { + if (value) { + try { + return JSON.parse(value); + } catch (e) { + throw new Error( + 'Error parsing the "NX_VEGA_NETWORKS" environment variable. Make sure it has a valid JSON format.' + ); + } + } + return undefined; + } default: return value; } }; -const getValue = (key: EnvKey, definintions: Partial = {}) => { - if (typeof window === 'undefined') { +const getValue = (key: EnvKey, definitions: Partial = {}) => { + if (!isBrowser) { return transformValue( key, - definintions[key] ?? getBundledEnvironmentValue(key) + definitions[key] ?? getBundledEnvironmentValue(key) ); } return transformValue( key, - window._ENV?.[key] ?? definintions[key] ?? getBundledEnvironmentValue(key) + window._ENV?.[key] ?? definitions[key] ?? getBundledEnvironmentValue(key) ); }; -const EnvironmentContext = createContext({} as Environment); - -export const EnvironmentProvider = ({ - definintions, - children, -}: EnvironmentProviderProps) => { +const compileEnvironment = ( + definitions?: Partial +): Environment => { const environment = ENV_KEYS.reduce( (acc, key) => ({ ...acc, - [key]: getValue(key, definintions), + [key]: getValue(key, definitions), }), {} as Environment ); + return { + ...environment, + ADDRESSES: ContractAddresses[environment['VEGA_ENV']], + VEGA_NETWORKS: { + [environment.VEGA_ENV]: isBrowser + ? window.location.href + : environment.VEGA_NETWORKS[environment.VEGA_ENV], + ...environment.VEGA_NETWORKS, + }, + }; +}; + +const EnvironmentContext = createContext({} as EnvironmentState); + +export const EnvironmentProvider = ({ + definitions, + children, +}: EnvironmentProviderProps) => { + const [environment] = useState(compileEnvironment(definitions)); + const missingKeys = Object.keys(environment) .filter((key) => typeof environment[key as EnvKey] === undefined) .map((key) => `"${key}"`) @@ -141,12 +179,7 @@ export const EnvironmentProvider = ({ } return ( - + {children} ); diff --git a/libs/network-switcher/src/index.ts b/libs/network-switcher/src/index.ts new file mode 100644 index 000000000..171999fdb --- /dev/null +++ b/libs/network-switcher/src/index.ts @@ -0,0 +1,5 @@ +// Components +export * from './components'; + +// Hooks +export * from './hooks'; diff --git a/libs/network-switcher/tsconfig.json b/libs/network-switcher/tsconfig.json new file mode 100644 index 000000000..1eabf319c --- /dev/null +++ b/libs/network-switcher/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/network-switcher/tsconfig.lib.json b/libs/network-switcher/tsconfig.lib.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/libs/network-switcher/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/network-switcher/tsconfig.spec.json b/libs/network-switcher/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/libs/network-switcher/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/react-helpers/src/hooks/index.ts b/libs/react-helpers/src/hooks/index.ts index 08fe1f20c..8af73c02a 100644 --- a/libs/react-helpers/src/hooks/index.ts +++ b/libs/react-helpers/src/hooks/index.ts @@ -2,4 +2,3 @@ export * from './use-apply-grid-transaction'; export * from './use-data-provider'; export * from './use-theme-switcher'; export * from './use-fetch'; -export * from './use-environment'; diff --git a/libs/web3/src/lib/transaction-dialog/dialog-rows.tsx b/libs/web3/src/lib/transaction-dialog/dialog-rows.tsx index 2bd91274c..82dbfdd28 100644 --- a/libs/web3/src/lib/transaction-dialog/dialog-rows.tsx +++ b/libs/web3/src/lib/transaction-dialog/dialog-rows.tsx @@ -1,6 +1,6 @@ import { t } from '@vegaprotocol/react-helpers'; import { Link } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { EthTxStatus } from '../use-ethereum-transaction'; const ACTIVE_CLASSES = 'text-black dark:text-white'; diff --git a/libs/withdraws/src/lib/withdraw-dialog.tsx b/libs/withdraws/src/lib/withdraw-dialog.tsx index a5d360565..bf79e27e0 100644 --- a/libs/withdraws/src/lib/withdraw-dialog.tsx +++ b/libs/withdraws/src/lib/withdraw-dialog.tsx @@ -1,5 +1,5 @@ import { Link, Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import type { VegaTxState } from '@vegaprotocol/wallet'; import { VegaTxStatus } from '@vegaprotocol/wallet'; import type { ReactNode } from 'react'; diff --git a/libs/withdraws/src/lib/withdrawals-table.tsx b/libs/withdraws/src/lib/withdrawals-table.tsx index 83e20a48f..5ab28cde4 100644 --- a/libs/withdraws/src/lib/withdrawals-table.tsx +++ b/libs/withdraws/src/lib/withdrawals-table.tsx @@ -11,7 +11,7 @@ import { } from '@vegaprotocol/react-helpers'; import { WithdrawalStatus } from '@vegaprotocol/types'; import { Link, AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; -import { useEnvironment } from '@vegaprotocol/react-helpers'; +import { useEnvironment } from '@vegaprotocol/network-switcher'; import { TransactionDialog } from '@vegaprotocol/web3'; import { useCompleteWithdraw } from './use-complete-withdraw'; import type { Withdrawals_party_withdrawals } from './__generated__/Withdrawals'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 01b599145..5654dfc9a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,6 +24,7 @@ "@vegaprotocol/market-depth": ["libs/market-depth/src/index.ts"], "@vegaprotocol/market-list": ["libs/market-list/src/index.ts"], "@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"], + "@vegaprotocol/network-switcher": ["libs/network-switcher/src/index.ts"], "@vegaprotocol/order-list": ["libs/order-list/src/index.ts"], "@vegaprotocol/positions": ["libs/positions/src/index.ts"], "@vegaprotocol/react-helpers": ["libs/react-helpers/src/index.ts"], diff --git a/workspace.json b/workspace.json index 2aa1f9a70..4836649a8 100644 --- a/workspace.json +++ b/workspace.json @@ -11,6 +11,7 @@ "market-depth": "libs/market-depth", "market-list": "libs/market-list", "network-stats": "libs/network-stats", + "network-switcher": "libs/network-switcher", "order-list": "libs/order-list", "positions": "libs/positions", "react-helpers": "libs/react-helpers", From 9266ff4bd80dddfd020566116a4e7a483dd72ced Mon Sep 17 00:00:00 2001 From: Dexter Edwards Date: Fri, 10 Jun 2022 11:36:38 +0100 Subject: [PATCH 13/27] Feat/smart contracts update (#536) * feat: add new bridge contract logic * chore: remove unused contract from provider * chore: remove minimum as no longer exists * feat: use new withdrawals contract, but allow for old contract for token * feat: power contracts selection by a flag * style: lint * Update libs/smart-contracts/src/contracts/collateral-bridge-new.ts Co-authored-by: Matthew Russell * chore: rename env var as per feedback * chore: consistent varaible names as per PR comments * chore: add back in checks for minimum as per PR comments * style: formatting Co-authored-by: Matthew Russell --- apps/token/.env | 1 + apps/token/src/config/flags.ts | 3 + .../contexts/contracts/contracts-context.ts | 2 - .../contexts/contracts/contracts-provider.tsx | 11 +- apps/token/src/routes/withdraw/index.tsx | 7 +- apps/token/src/routes/withdrawals/index.tsx | 5 +- .../withdraw/withdraw-page-container.tsx | 1 + .../__generated__/DealTicketQuery.ts | 112 ++++ .../src/hooks/__generated__/OrderEvent.ts | 108 ++++ libs/deposits/src/lib/deposit-form.spec.tsx | 23 +- libs/deposits/src/lib/deposit-form.tsx | 27 +- libs/deposits/src/lib/deposit-limits.tsx | 6 - .../src/lib/use-get-deposit-limits.ts | 11 +- libs/deposits/src/lib/use-submit-deposit.ts | 2 +- .../__generated__/MarketDepthSubscription.ts | 2 +- .../src/abis/erc20_bridge_abi.json | 198 +++++- .../src/abis/erc20_bridge_new_abi.json | 589 ++++++++++++++++++ .../src/contracts/collateral-bridge-new.ts | 51 ++ .../src/contracts/collateral-bridge.ts | 1 + libs/smart-contracts/src/contracts/index.ts | 1 + libs/types/.env | 1 + libs/web3/src/lib/use-bridge-contract.ts | 22 +- .../src/lib/__generated__/Erc20ApprovalNew.ts | 52 ++ libs/withdraws/src/lib/queries.ts | 14 + .../src/lib/use-complete-withdraw.spec.tsx | 19 +- .../src/lib/use-complete-withdraw.ts | 58 +- libs/withdraws/src/lib/use-withdraw.spec.tsx | 11 +- libs/withdraws/src/lib/use-withdraw.ts | 58 +- .../src/lib/withdraw-manager.spec.tsx | 1 + libs/withdraws/src/lib/withdraw-manager.tsx | 5 +- libs/withdraws/src/lib/withdrawals-table.tsx | 2 +- 31 files changed, 1254 insertions(+), 150 deletions(-) create mode 100644 libs/deal-ticket/src/components/__generated__/DealTicketQuery.ts create mode 100644 libs/deal-ticket/src/hooks/__generated__/OrderEvent.ts create mode 100644 libs/smart-contracts/src/abis/erc20_bridge_new_abi.json create mode 100644 libs/smart-contracts/src/contracts/collateral-bridge-new.ts create mode 100644 libs/types/.env create mode 100644 libs/withdraws/src/lib/__generated__/Erc20ApprovalNew.ts diff --git a/apps/token/.env b/apps/token/.env index e58d5dd3c..4eacba644 100644 --- a/apps/token/.env +++ b/apps/token/.env @@ -25,6 +25,7 @@ NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" NX_FAIRGROUND = false +NX_IS_NEW_BRIDGE_CONTRACT = true NX_VEGA_NETWORKS='{\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET\":\"https://dev.token.vega.xyz\",\"STAGNET2\":\"staging2.token.vega.xyz\",\"TESTNET\":\"token.fairground.wtf\",\"MAINNET\":\"token.vega.xyz\"}' #Test configuration variables diff --git a/apps/token/src/config/flags.ts b/apps/token/src/config/flags.ts index d02e3c34b..6ef78b9cf 100644 --- a/apps/token/src/config/flags.ts +++ b/apps/token/src/config/flags.ts @@ -8,4 +8,7 @@ export const Flags = { MOCK: TRUTHY.includes(process.env['NX_MOCKED'] as string), FAIRGROUND: TRUTHY.includes(process.env['NX_FAIRGROUND'] as string), NETWORK_LIMITS: TRUTHY.includes(process.env['NX_NETWORK_LIMITS'] as string), + USE_NEW_BRIDGE_CONTRACT: TRUTHY.includes( + process.env['NX_IS_NEW_BRIDGE_CONTRACT'] as string + ), }; diff --git a/apps/token/src/contexts/contracts/contracts-context.ts b/apps/token/src/contexts/contracts/contracts-context.ts index a415f68c8..253874265 100644 --- a/apps/token/src/contexts/contracts/contracts-context.ts +++ b/apps/token/src/contexts/contracts/contracts-context.ts @@ -1,6 +1,5 @@ import type { Claim, - CollateralBridge, Token, TokenVesting, StakingBridge, @@ -12,7 +11,6 @@ export interface ContractsContextShape { staking: StakingBridge; vesting: TokenVesting; claim: Claim; - erc20Bridge: CollateralBridge; } export const ContractsContext = React.createContext< diff --git a/apps/token/src/contexts/contracts/contracts-provider.tsx b/apps/token/src/contexts/contracts/contracts-provider.tsx index 0d288cf99..e11bf8b9a 100644 --- a/apps/token/src/contexts/contracts/contracts-provider.tsx +++ b/apps/token/src/contexts/contracts/contracts-provider.tsx @@ -2,7 +2,6 @@ import { Token, TokenVesting, Claim, - CollateralBridge, StakingBridge, } from '@vegaprotocol/smart-contracts'; import { Splash } from '@vegaprotocol/ui-toolkit'; @@ -23,10 +22,8 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => { const { provider: activeProvider, account } = useWeb3React(); const { config } = useEthereumConfig(); const { VEGA_ENV, ADDRESSES } = useEnvironment(); - const [contracts, setContracts] = React.useState | null>(null); + const [contracts, setContracts] = + React.useState(null); // Create instances of contract classes. If we have an account use a signer for the // contracts so that we can sign transactions, otherwise use the provider for just @@ -56,10 +53,6 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => { signer || provider ), claim: new Claim(ADDRESSES.claimAddress, signer || provider), - erc20Bridge: new CollateralBridge( - config.collateral_bridge_contract.address, - signer || provider - ), }); } }, [activeProvider, account, config, ADDRESSES, VEGA_ENV]); diff --git a/apps/token/src/routes/withdraw/index.tsx b/apps/token/src/routes/withdraw/index.tsx index d8b6d47e8..0f063d324 100644 --- a/apps/token/src/routes/withdraw/index.tsx +++ b/apps/token/src/routes/withdraw/index.tsx @@ -15,6 +15,7 @@ import type { WithdrawPageVariables, } from './__generated__/WithdrawPage'; import { WithdrawManager } from '@vegaprotocol/withdraws'; +import { Flags } from '../../config'; const Withdraw = () => { const { t } = useTranslation(); @@ -149,7 +150,11 @@ export const WithdrawContainer = ({ currVegaKey }: WithdrawContainerProps) => {
    )} - + ); }; diff --git a/apps/token/src/routes/withdrawals/index.tsx b/apps/token/src/routes/withdrawals/index.tsx index fea7ecd1d..b00aae9ba 100644 --- a/apps/token/src/routes/withdrawals/index.tsx +++ b/apps/token/src/routes/withdrawals/index.tsx @@ -18,6 +18,7 @@ import type { Withdrawals_party_withdrawals } from '@vegaprotocol/withdraws'; import { useCompleteWithdraw, useWithdrawals } from '@vegaprotocol/withdraws'; import { TransactionDialog } from '@vegaprotocol/web3'; import { WithdrawalStatus } from '../../__generated__/globalTypes'; +import { Flags } from '../../config'; const Withdrawals = () => { const { t } = useTranslation(); @@ -34,7 +35,9 @@ const Withdrawals = () => { const WithdrawPendingContainer = () => { const { t } = useTranslation(); - const { transaction, submit } = useCompleteWithdraw(); + const { transaction, submit } = useCompleteWithdraw( + Flags.USE_NEW_BRIDGE_CONTRACT + ); const { data, loading, error } = useWithdrawals(); const withdrawals = React.useMemo(() => { diff --git a/apps/trading/pages/portfolio/withdraw/withdraw-page-container.tsx b/apps/trading/pages/portfolio/withdraw/withdraw-page-container.tsx index 091e81d08..4e28898ed 100644 --- a/apps/trading/pages/portfolio/withdraw/withdraw-page-container.tsx +++ b/apps/trading/pages/portfolio/withdraw/withdraw-page-container.tsx @@ -83,6 +83,7 @@ export const WithdrawPageContainer = ({ assets={data.assets} accounts={data.party?.accounts || []} initialAssetId={assetId} + isNewContract={true} /> ); diff --git a/libs/deal-ticket/src/components/__generated__/DealTicketQuery.ts b/libs/deal-ticket/src/components/__generated__/DealTicketQuery.ts new file mode 100644 index 000000000..5bb0c3d36 --- /dev/null +++ b/libs/deal-ticket/src/components/__generated__/DealTicketQuery.ts @@ -0,0 +1,112 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { MarketState, MarketTradingMode } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: DealTicketQuery +// ==================================================== + +export interface DealTicketQuery_market_tradableInstrument_instrument_product { + __typename: "Future"; + /** + * String representing the quote (e.g. BTCUSD -> USD is quote) + */ + quoteName: string; +} + +export interface DealTicketQuery_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * A reference to or instance of a fully specified product, including all required product parameters for that product (Product union) + */ + product: DealTicketQuery_market_tradableInstrument_instrument_product; +} + +export interface DealTicketQuery_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: DealTicketQuery_market_tradableInstrument_instrument; +} + +export interface DealTicketQuery_market_depth_lastTrade { + __typename: "Trade"; + /** + * The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64) + */ + price: string; +} + +export interface DealTicketQuery_market_depth { + __typename: "MarketDepth"; + /** + * Last trade for the given market (if available) + */ + lastTrade: DealTicketQuery_market_depth_lastTrade | null; +} + +export interface DealTicketQuery_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Market full name + */ + name: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; + /** + * positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64). + * i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes. + * 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market. + */ + positionDecimalPlaces: number; + /** + * Current state of the market + */ + state: MarketState; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: DealTicketQuery_market_tradableInstrument; + /** + * Current depth on the order book for this market + */ + depth: DealTicketQuery_market_depth; +} + +export interface DealTicketQuery { + /** + * An instrument that is trading on the VEGA network + */ + market: DealTicketQuery_market | null; +} + +export interface DealTicketQueryVariables { + marketId: string; +} diff --git a/libs/deal-ticket/src/hooks/__generated__/OrderEvent.ts b/libs/deal-ticket/src/hooks/__generated__/OrderEvent.ts new file mode 100644 index 000000000..7aafc6716 --- /dev/null +++ b/libs/deal-ticket/src/hooks/__generated__/OrderEvent.ts @@ -0,0 +1,108 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { BusEventType, OrderType, OrderStatus, OrderRejectionReason } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL subscription operation: OrderEvent +// ==================================================== + +export interface OrderEvent_busEvents_event_TimeUpdate { + __typename: "TimeUpdate" | "MarketEvent" | "TransferResponses" | "PositionResolution" | "Trade" | "Account" | "Party" | "MarginLevels" | "Proposal" | "Vote" | "MarketData" | "NodeSignature" | "LossSocialization" | "SettlePosition" | "Market" | "Asset" | "MarketTick" | "SettleDistressed" | "AuctionEvent" | "RiskFactor" | "Deposit" | "Withdrawal" | "OracleSpec" | "LiquidityProvision"; +} + +export interface OrderEvent_busEvents_event_Order_market { + __typename: "Market"; + /** + * Market full name + */ + name: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; +} + +export interface OrderEvent_busEvents_event_Order { + __typename: "Order"; + /** + * Type the order type (defaults to PARTY) + */ + type: OrderType | null; + /** + * Hash of the order data + */ + id: string; + /** + * The status of an order, for example 'Active' + */ + status: OrderStatus; + /** + * Reason for the order to be rejected + */ + rejectionReason: OrderRejectionReason | null; + /** + * RFC3339Nano formatted date and time for when the order was created (timestamp) + */ + createdAt: string; + /** + * Total number of contracts that may be bought or sold (immutable) (uint64) + */ + size: string; + /** + * The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64) + */ + price: string; + /** + * The market the order is trading on (probably stored internally as a hash of the market details) + */ + market: OrderEvent_busEvents_event_Order_market | null; +} + +export type OrderEvent_busEvents_event = OrderEvent_busEvents_event_TimeUpdate | OrderEvent_busEvents_event_Order; + +export interface OrderEvent_busEvents { + __typename: "BusEvent"; + /** + * the id for this event + */ + eventId: string; + /** + * the block hash + */ + block: string; + /** + * the type of event we're dealing with + */ + type: BusEventType; + /** + * the payload - the wrapped event + */ + event: OrderEvent_busEvents_event; +} + +export interface OrderEvent { + /** + * Subscribe to event data from the event bus + */ + busEvents: OrderEvent_busEvents[] | null; +} + +export interface OrderEventVariables { + partyId: string; +} diff --git a/libs/deposits/src/lib/deposit-form.spec.tsx b/libs/deposits/src/lib/deposit-form.spec.tsx index 27785a797..1f42f0e91 100644 --- a/libs/deposits/src/lib/deposit-form.spec.tsx +++ b/libs/deposits/src/lib/deposit-form.spec.tsx @@ -105,12 +105,7 @@ it('Form validation', async () => { await screen.findByText('Amount is above permitted maximum') ).toBeInTheDocument(); - rerender( - - ); + rerender(); const amountMoreThanAllowance = '31'; fireEvent.change(screen.getByLabelText('Amount'), { @@ -130,16 +125,11 @@ it('Form validation', async () => { expect(await screen.findByText('Value is below minimum')).toBeInTheDocument(); - rerender( - - ); - const amountLessThanLimit = '5'; + const amountLessThanZero = '-0.00001'; fireEvent.change(screen.getByLabelText('Amount'), { - target: { value: amountLessThanLimit }, + target: { value: amountLessThanZero }, }); + expect(await screen.findByText('Value is below minimum')).toBeInTheDocument(); }); @@ -190,10 +180,7 @@ it('Deposit', async () => { /> ); - // Check deposit limits are displayed - expect( - screen.getByText('Minimum', { selector: 'th' }).nextElementSibling - ).toHaveTextContent(limits.min.toString()); + // Check deposit limit is displayed expect( screen.getByText('Maximum', { selector: 'th' }).nextElementSibling ).toHaveTextContent(limits.max.toString()); diff --git a/libs/deposits/src/lib/deposit-form.tsx b/libs/deposits/src/lib/deposit-form.tsx index 3111dda78..fd690ee76 100644 --- a/libs/deposits/src/lib/deposit-form.tsx +++ b/libs/deposits/src/lib/deposit-form.tsx @@ -1,5 +1,4 @@ import { - addDecimal, removeDecimal, t, ethereumAddress, @@ -7,6 +6,7 @@ import { vegaPublicKey, minSafe, maxSafe, + addDecimal, } from '@vegaprotocol/react-helpers'; import { Button, @@ -46,7 +46,6 @@ export interface DepositFormProps { }) => Promise; requestFaucet: () => Promise; limits: { - min: BigNumber; max: BigNumber; } | null; allowance: BigNumber | undefined; @@ -97,19 +96,6 @@ export const DepositForm = ({ const assetId = useWatch({ name: 'asset', control }); const amount = useWatch({ name: 'amount', control }); - const min = useMemo(() => { - // Min viable amount given asset decimals EG for WEI 0.000000000000000001 - const minViableAmount = selectedAsset - ? new BigNumber(addDecimal('1', selectedAsset.decimals)) - : new BigNumber(0); - - const min = limits - ? BigNumber.maximum(minViableAmount, limits.min) - : minViableAmount; - - return min; - }, [limits, selectedAsset]); - const max = useMemo(() => { const maxApproved = allowance ? allowance : new BigNumber(Infinity); const maxAvailable = available ? available : new BigNumber(Infinity); @@ -127,6 +113,15 @@ export const DepositForm = ({ }; }, [limits, allowance, available]); + const min = useMemo(() => { + // Min viable amount given asset decimals EG for WEI 0.000000000000000001 + const minViableAmount = selectedAsset + ? new BigNumber(addDecimal('1', selectedAsset.decimals)) + : new BigNumber(0); + + return minViableAmount; + }, [selectedAsset]); + useEffect(() => { onSelectAsset(assetId); }, [assetId, onSelectAsset]); @@ -207,7 +202,7 @@ export const DepositForm = ({ {...register('amount', { validate: { required, - minSafe: (value) => minSafe(min)(value), + minSafe: (value) => minSafe(new BigNumber(min))(value), maxSafe: (v) => { const value = new BigNumber(v); if (value.isGreaterThan(max.approved)) { diff --git a/libs/deposits/src/lib/deposit-limits.tsx b/libs/deposits/src/lib/deposit-limits.tsx index b355c011a..2677f254b 100644 --- a/libs/deposits/src/lib/deposit-limits.tsx +++ b/libs/deposits/src/lib/deposit-limits.tsx @@ -3,13 +3,11 @@ import type BigNumber from 'bignumber.js'; interface DepositLimitsProps { limits: { - min: BigNumber; max: BigNumber; }; } export const DepositLimits = ({ limits }: DepositLimitsProps) => { - const minLimit = limits.min.toString(); const maxLimit = limits.max.isEqualTo(Infinity) ? t('No limit') : limits.max.toString(); @@ -18,10 +16,6 @@ export const DepositLimits = ({ limits }: DepositLimitsProps) => {

    {t('Temporary deposit limits')}

    - - - - diff --git a/libs/deposits/src/lib/use-get-deposit-limits.ts b/libs/deposits/src/lib/use-get-deposit-limits.ts index c6c7c130c..5e1d5620f 100644 --- a/libs/deposits/src/lib/use-get-deposit-limits.ts +++ b/libs/deposits/src/lib/use-get-deposit-limits.ts @@ -5,16 +5,13 @@ import BigNumber from 'bignumber.js'; import { addDecimal } from '@vegaprotocol/react-helpers'; export const useGetDepositLimits = (asset?: Asset, decimals?: number) => { - const contract = useBridgeContract(); + const contract = useBridgeContract(true); const getLimits = useCallback(async () => { if (!contract || !asset || asset.source.__typename !== 'ERC20') { return; } - return Promise.all([ - contract.getDepositMinimum(asset.source.contractAddress), - contract.getDepositMaximum(asset.source.contractAddress), - ]); + return contract.getDepositMaximum(asset.source.contractAddress); }, [asset, contract]); const { @@ -23,11 +20,9 @@ export const useGetDepositLimits = (asset?: Asset, decimals?: number) => { if (!data || !decimals) return null; - const min = new BigNumber(addDecimal(data[0].toString(), decimals)); - const max = new BigNumber(addDecimal(data[1].toString(), decimals)); + const max = new BigNumber(addDecimal(data.toString(), decimals)); return { - min, max: max.isEqualTo(0) ? new BigNumber(Infinity) : max, }; }; diff --git a/libs/deposits/src/lib/use-submit-deposit.ts b/libs/deposits/src/lib/use-submit-deposit.ts index 9a65a6fda..599a2622c 100644 --- a/libs/deposits/src/lib/use-submit-deposit.ts +++ b/libs/deposits/src/lib/use-submit-deposit.ts @@ -29,7 +29,7 @@ const DEPOSIT_EVENT_SUB = gql` export const useSubmitDeposit = () => { const { config } = useEthereumConfig(); - const contract = useBridgeContract(); + const contract = useBridgeContract(true); const [confirmationEvent, setConfirmationEvent] = useState(null); // Store public key from contract arguments for use in the subscription, diff --git a/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts b/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts index 9981dd8c9..7a81dd677 100644 --- a/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts +++ b/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts @@ -86,7 +86,7 @@ export interface MarketDepthSubscription_marketDepthUpdate { */ buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null; /** - * Sequence number for the current snapshot of the market depth + * Sequence number for the current snapshot of the market depth. It is always increasing but not monotonic. */ sequenceNumber: string; } diff --git a/libs/smart-contracts/src/abis/erc20_bridge_abi.json b/libs/smart-contracts/src/abis/erc20_bridge_abi.json index 27b901a26..0c3ea7b6d 100644 --- a/libs/smart-contracts/src/abis/erc20_bridge_abi.json +++ b/libs/smart-contracts/src/abis/erc20_bridge_abi.json @@ -173,8 +173,16 @@ }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, { "internalType": "bytes32", "name": "vega_public_key", @@ -188,62 +196,134 @@ }, { "inputs": [ - { "internalType": "bytes32", "name": "vega_asset_id", "type": "bytes32" } + { + "internalType": "bytes32", + "name": "vega_asset_id", + "type": "bytes32" + } ], "name": "get_asset_source", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" } + { + "internalType": "address", + "name": "asset_source", + "type": "address" + } ], "name": "get_deposit_maximum", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" } + { + "internalType": "address", + "name": "asset_source", + "type": "address" + } ], "name": "get_deposit_minimum", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "get_multisig_control_address", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" } + { + "internalType": "address", + "name": "asset_source", + "type": "address" + } ], "name": "get_vega_asset_id", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" } + { + "internalType": "address", + "name": "asset_source", + "type": "address" + } ], "name": "is_asset_listed", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" }, - { "internalType": "bytes32", "name": "vega_asset_id", "type": "bytes32" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "bytes", "name": "signatures", "type": "bytes" } + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "vega_asset_id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } ], "name": "list_asset", "outputs": [], @@ -252,9 +332,21 @@ }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "bytes", "name": "signatures", "type": "bytes" } + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } ], "name": "remove_asset", "outputs": [], @@ -263,14 +355,26 @@ }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" }, + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, { "internalType": "uint256", "name": "maximum_amount", "type": "uint256" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "bytes", "name": "signatures", "type": "bytes" } + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } ], "name": "set_deposit_maximum", "outputs": [], @@ -279,14 +383,26 @@ }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" }, + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, { "internalType": "uint256", "name": "minimum_amount", "type": "uint256" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "bytes", "name": "signatures", "type": "bytes" } + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } ], "name": "set_deposit_minimum", "outputs": [], @@ -295,11 +411,31 @@ }, { "inputs": [ - { "internalType": "address", "name": "asset_source", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "bytes", "name": "signatures", "type": "bytes" } + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } ], "name": "withdraw_asset", "outputs": [], diff --git a/libs/smart-contracts/src/abis/erc20_bridge_new_abi.json b/libs/smart-contracts/src/abis/erc20_bridge_new_abi.json new file mode 100644 index 000000000..e2a19660b --- /dev/null +++ b/libs/smart-contracts/src/abis/erc20_bridge_new_abi.json @@ -0,0 +1,589 @@ +[ + { + "inputs": [ + { + "internalType": "address payable", + "name": "erc20_asset_pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user_address", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "vega_public_key", + "type": "bytes32" + } + ], + "name": "Asset_Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lifetime_limit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "withdraw_threshold", + "type": "uint256" + } + ], + "name": "Asset_Limits_Updated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "vega_asset_id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "Asset_Listed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "Asset_Removed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user_address", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "Asset_Withdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Bridge_Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Bridge_Stopped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "withdraw_delay", + "type": "uint256" + } + ], + "name": "Bridge_Withdraw_Delay_Set", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + } + ], + "name": "Depositor_Exempted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + } + ], + "name": "Depositor_Exemption_Revoked", + "type": "event" + }, + { + "inputs": [], + "name": "default_withdraw_delay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "vega_public_key", + "type": "bytes32" + } + ], + "name": "deposit_asset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "erc20_asset_pool_address", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "exempt_depositor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + } + ], + "name": "get_asset_deposit_lifetime_limit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "vega_asset_id", + "type": "bytes32" + } + ], + "name": "get_asset_source", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "get_multisig_control_address", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + } + ], + "name": "get_vega_asset_id", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + } + ], + "name": "get_withdraw_threshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "global_resume", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "global_stop", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + } + ], + "name": "is_asset_listed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "depositor", + "type": "address" + } + ], + "name": "is_exempt_depositor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "is_stopped", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "vega_asset_id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "lifetime_limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdraw_threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "list_asset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "remove_asset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "revoke_exempt_depositor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lifetime_limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "set_asset_limits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "delay", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "set_withdraw_delay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset_source", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "creation", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "withdraw_asset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/libs/smart-contracts/src/contracts/collateral-bridge-new.ts b/libs/smart-contracts/src/contracts/collateral-bridge-new.ts new file mode 100644 index 000000000..5908f3974 --- /dev/null +++ b/libs/smart-contracts/src/contracts/collateral-bridge-new.ts @@ -0,0 +1,51 @@ +import type { BigNumber } from 'ethers'; +import { ethers } from 'ethers'; +import abi from '../abis/erc20_bridge_new_abi.json'; + +export class CollateralBridgeNew { + public contract: ethers.Contract; + public isNewContract = true; + + constructor( + address: string, + signerOrProvider: ethers.Signer | ethers.providers.Provider + ) { + this.contract = new ethers.Contract(address, abi, signerOrProvider); + } + + depositAsset(assetSource: string, amount: string, vegaPublicKey: string) { + return this.contract.deposit_asset(assetSource, amount, vegaPublicKey); + } + getAssetSource(vegaAssetId: string) { + return this.contract.get_asset_source(vegaAssetId); + } + getDepositMaximum(assetSource: string): Promise { + return this.contract.get_asset_deposit_lifetime_limit(assetSource); + } + getMultisigControlAddres() { + return this.contract.get_multisig_control_address(); + } + getVegaAssetId(address: string) { + return this.contract.get_vega_asset_id(address); + } + isAssetListed(address: string) { + return this.contract.is_asset_listed(address); + } + withdrawAsset( + assetSource: string, + amount: string, + target: string, + creation: string, + nonce: string, + signatures: string + ) { + return this.contract.withdraw_asset( + assetSource, + amount, + target, + creation, + nonce, + signatures + ); + } +} diff --git a/libs/smart-contracts/src/contracts/collateral-bridge.ts b/libs/smart-contracts/src/contracts/collateral-bridge.ts index 3824491a8..82de09551 100644 --- a/libs/smart-contracts/src/contracts/collateral-bridge.ts +++ b/libs/smart-contracts/src/contracts/collateral-bridge.ts @@ -4,6 +4,7 @@ import abi from '../abis/erc20_bridge_abi.json'; export class CollateralBridge { public contract: ethers.Contract; + public isNewContract = false; constructor( address: string, diff --git a/libs/smart-contracts/src/contracts/index.ts b/libs/smart-contracts/src/contracts/index.ts index 15ec967b1..1f5a6bd57 100644 --- a/libs/smart-contracts/src/contracts/index.ts +++ b/libs/smart-contracts/src/contracts/index.ts @@ -2,6 +2,7 @@ export * from './vega-web3-types'; export * from './claim'; export * from './collateral-bridge'; +export * from './collateral-bridge-new'; export * from './staking-bridge'; export * from './token-vesting'; export * from './token'; diff --git a/libs/types/.env b/libs/types/.env new file mode 100644 index 000000000..45bf75a13 --- /dev/null +++ b/libs/types/.env @@ -0,0 +1 @@ +NX_VEGA_URL = "https://n04.d.vega.xyz/query" diff --git a/libs/web3/src/lib/use-bridge-contract.ts b/libs/web3/src/lib/use-bridge-contract.ts index db7637bf2..f3c15eadc 100644 --- a/libs/web3/src/lib/use-bridge-contract.ts +++ b/libs/web3/src/lib/use-bridge-contract.ts @@ -1,9 +1,12 @@ -import { CollateralBridge } from '@vegaprotocol/smart-contracts'; +import { + CollateralBridge, + CollateralBridgeNew, +} from '@vegaprotocol/smart-contracts'; import { useWeb3React } from '@web3-react/core'; import { useMemo } from 'react'; import { useEthereumConfig } from './use-ethereum-config'; -export const useBridgeContract = () => { +export const useBridgeContract = (isNewContract: boolean) => { const { provider } = useWeb3React(); const { config } = useEthereumConfig(); @@ -14,11 +17,16 @@ export const useBridgeContract = () => { const signer = provider.getSigner(); - return new CollateralBridge( - config.collateral_bridge_contract.address, - signer || provider - ); - }, [provider, config]); + return isNewContract + ? new CollateralBridgeNew( + config.collateral_bridge_contract.address, + signer || provider + ) + : new CollateralBridge( + config.collateral_bridge_contract.address, + signer || provider + ); + }, [provider, config, isNewContract]); return contract; }; diff --git a/libs/withdraws/src/lib/__generated__/Erc20ApprovalNew.ts b/libs/withdraws/src/lib/__generated__/Erc20ApprovalNew.ts new file mode 100644 index 000000000..9a48ec9b6 --- /dev/null +++ b/libs/withdraws/src/lib/__generated__/Erc20ApprovalNew.ts @@ -0,0 +1,52 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: Erc20ApprovalNew +// ==================================================== + +export interface Erc20ApprovalNew_erc20WithdrawalApproval { + __typename: "Erc20WithdrawalApproval"; + /** + * The source asset in the ethereum network + */ + assetSource: string; + /** + * The amount to be withdrawn + */ + amount: string; + /** + * The nonce to be used in the request + */ + nonce: string; + /** + * Signature aggregate from the nodes, in the following format: + * 0x + sig1 + sig2 + ... + sigN + */ + signatures: string; + /** + * The target address which will receive the funds + */ + targetAddress: string; + /** + * Timestamp in seconds for expiry of the approval + */ + expiry: string; + /** + * Timestamp at which the withdrawal was created + */ + creation: string; +} + +export interface Erc20ApprovalNew { + /** + * find an erc20 withdrawal approval using its withdrawal id + */ + erc20WithdrawalApproval: Erc20ApprovalNew_erc20WithdrawalApproval | null; +} + +export interface Erc20ApprovalNewVariables { + withdrawalId: string; +} diff --git a/libs/withdraws/src/lib/queries.ts b/libs/withdraws/src/lib/queries.ts index 9cb86846c..0347c90b1 100644 --- a/libs/withdraws/src/lib/queries.ts +++ b/libs/withdraws/src/lib/queries.ts @@ -12,3 +12,17 @@ export const ERC20_APPROVAL_QUERY = gql` } } `; + +export const ERC20_APPROVAL_QUERY_NEW = gql` + query Erc20ApprovalNew($withdrawalId: ID!) { + erc20WithdrawalApproval(withdrawalId: $withdrawalId) { + assetSource + amount + nonce + signatures + targetAddress + expiry + creation + } + } +`; diff --git a/libs/withdraws/src/lib/use-complete-withdraw.spec.tsx b/libs/withdraws/src/lib/use-complete-withdraw.spec.tsx index 2eb5c1ebc..e5a80c287 100644 --- a/libs/withdraws/src/lib/use-complete-withdraw.spec.tsx +++ b/libs/withdraws/src/lib/use-complete-withdraw.spec.tsx @@ -4,13 +4,11 @@ import type { MockedResponse } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing'; import type { ReactNode } from 'react'; import { useCompleteWithdraw } from './use-complete-withdraw'; -import type { - Erc20Approval, - Erc20Approval_erc20WithdrawalApproval, -} from './__generated__/Erc20Approval'; -import { ERC20_APPROVAL_QUERY } from './queries'; +import type { Erc20Approval } from './__generated__/Erc20Approval'; +import { ERC20_APPROVAL_QUERY_NEW } from './queries'; import * as web3 from '@vegaprotocol/web3'; import * as sentry from '@sentry/nextjs'; +import type { Erc20ApprovalNew_erc20WithdrawalApproval } from './__generated__/Erc20ApprovalNew'; jest.mock('@vegaprotocol/web3', () => ({ useBridgeContract: jest.fn(), @@ -21,23 +19,24 @@ function setup(mocks?: MockedResponse[]) { const wrapper = ({ children }: { children: ReactNode }) => ( {children} ); - return renderHook(() => useCompleteWithdraw(), { wrapper }); + return renderHook(() => useCompleteWithdraw(true), { wrapper }); } it('Should perform the Ethereum transaction with the fetched approval', async () => { const withdrawalId = 'withdrawal-id'; - const erc20WithdrawalApproval: Erc20Approval_erc20WithdrawalApproval = { + const erc20WithdrawalApproval: Erc20ApprovalNew_erc20WithdrawalApproval = { __typename: 'Erc20WithdrawalApproval', assetSource: 'asset-source', amount: '100', nonce: '1', + creation: '1', signatures: 'signatures', targetAddress: 'target-address', expiry: 'expiry', }; const mockERC20Approval: MockedResponse = { request: { - query: ERC20_APPROVAL_QUERY, + query: ERC20_APPROVAL_QUERY_NEW, variables: { withdrawalId }, }, result: { @@ -66,7 +65,7 @@ it('Captures an error if the erc20Withdrawal is not found', async () => { const withdrawalId = 'withdrawal-id'; const mockERC20Approval: MockedResponse = { request: { - query: ERC20_APPROVAL_QUERY, + query: ERC20_APPROVAL_QUERY_NEW, variables: { withdrawalId }, }, result: { @@ -97,7 +96,7 @@ it('Captures an error if erc20 approval query fails', async () => { const withdrawalId = 'withdrawal-id'; const mockERC20Approval: MockedResponse = { request: { - query: ERC20_APPROVAL_QUERY, + query: ERC20_APPROVAL_QUERY_NEW, variables: { withdrawalId }, }, error: new Error('query failed'), diff --git a/libs/withdraws/src/lib/use-complete-withdraw.ts b/libs/withdraws/src/lib/use-complete-withdraw.ts index 5171ee2d8..dac2e9189 100644 --- a/libs/withdraws/src/lib/use-complete-withdraw.ts +++ b/libs/withdraws/src/lib/use-complete-withdraw.ts @@ -1,12 +1,17 @@ import { gql, useApolloClient } from '@apollo/client'; import { captureException } from '@sentry/nextjs'; +import type { + CollateralBridge, + CollateralBridgeNew, +} from '@vegaprotocol/smart-contracts'; import { useBridgeContract, useEthereumTransaction } from '@vegaprotocol/web3'; import { useCallback, useEffect, useState } from 'react'; -import { ERC20_APPROVAL_QUERY } from './queries'; +import { ERC20_APPROVAL_QUERY, ERC20_APPROVAL_QUERY_NEW } from './queries'; import type { Erc20Approval, Erc20ApprovalVariables, } from './__generated__/Erc20Approval'; +import type { Erc20ApprovalNew } from './__generated__/Erc20ApprovalNew'; import type { PendingWithdrawal } from './__generated__/PendingWithdrawal'; export const PENDING_WITHDRAWAL_FRAGMMENT = gql` @@ -16,6 +21,15 @@ export const PENDING_WITHDRAWAL_FRAGMMENT = gql` } `; +export interface NewWithdrawTransactionArgs { + assetSource: string; + amount: string; + nonce: string; + signatures: string; + targetAddress: string; + creation: string; +} + export interface WithdrawTransactionArgs { assetSource: string; amount: string; @@ -24,30 +38,48 @@ export interface WithdrawTransactionArgs { targetAddress: string; } -export const useCompleteWithdraw = () => { +export const useCompleteWithdraw = (isNewContract: boolean) => { const { query, cache } = useApolloClient(); - const contract = useBridgeContract(); + const contract = useBridgeContract(isNewContract); const [id, setId] = useState(''); - const { transaction, perform } = - useEthereumTransaction((args) => { - if (!contract) { - return null; - } - return contract.withdrawAsset( + const { transaction, perform } = useEthereumTransaction< + WithdrawTransactionArgs | NewWithdrawTransactionArgs + >((args) => { + if (!contract) { + return null; + } + if (contract.isNewContract) { + const withdrawalData = args as NewWithdrawTransactionArgs; + return (contract as CollateralBridgeNew).withdrawAsset( + withdrawalData.assetSource, + withdrawalData.amount, + withdrawalData.targetAddress, + withdrawalData.creation, + withdrawalData.nonce, + withdrawalData.signatures + ); + } else { + return (contract as CollateralBridge).withdrawAsset( args.assetSource, args.amount, args.targetAddress, args.nonce, args.signatures ); - }); + } + }); const submit = useCallback( async (withdrawalId: string) => { setId(withdrawalId); try { - const res = await query({ - query: ERC20_APPROVAL_QUERY, + const res = await query< + Erc20Approval | Erc20ApprovalNew, + Erc20ApprovalVariables + >({ + query: isNewContract + ? ERC20_APPROVAL_QUERY_NEW + : ERC20_APPROVAL_QUERY, variables: { withdrawalId }, }); @@ -60,7 +92,7 @@ export const useCompleteWithdraw = () => { captureException(err); } }, - [query, perform] + [query, isNewContract, perform] ); useEffect(() => { diff --git a/libs/withdraws/src/lib/use-withdraw.spec.tsx b/libs/withdraws/src/lib/use-withdraw.spec.tsx index a2dea26d8..226b078d7 100644 --- a/libs/withdraws/src/lib/use-withdraw.spec.tsx +++ b/libs/withdraws/src/lib/use-withdraw.spec.tsx @@ -2,12 +2,12 @@ import { act, renderHook } from '@testing-library/react-hooks'; import type { MockedResponse } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing'; import type { ReactNode } from 'react'; -import type { Erc20Approval } from './__generated__/Erc20Approval'; -import { ERC20_APPROVAL_QUERY } from './queries'; +import { ERC20_APPROVAL_QUERY_NEW } from './queries'; import * as web3 from '@vegaprotocol/web3'; import * as wallet from '@vegaprotocol/wallet'; import type { WithdrawalFields } from './use-withdraw'; import { useWithdraw } from './use-withdraw'; +import type { Erc20ApprovalNew } from './__generated__/Erc20ApprovalNew'; jest.mock('@vegaprotocol/web3', () => ({ useBridgeContract: jest.fn(), @@ -27,7 +27,7 @@ function setup(mocks?: MockedResponse[], cancelled = false) { const wrapper = ({ children }: { children: ReactNode }) => ( {children} ); - return renderHook(() => useWithdraw(cancelled), { wrapper }); + return renderHook(() => useWithdraw(cancelled, true), { wrapper }); } const signature = @@ -49,7 +49,7 @@ let mockPerform: jest.Mock; let mockEthReset: jest.Mock; let mockVegaReset: jest.Mock; let withdrawalInput: WithdrawalFields; -let mockERC20Approval: MockedResponse; +let mockERC20Approval: MockedResponse; beforeEach(() => { pubkey = 'pubkey'; @@ -82,7 +82,7 @@ beforeEach(() => { }; mockERC20Approval = { request: { - query: ERC20_APPROVAL_QUERY, + query: ERC20_APPROVAL_QUERY_NEW, variables: { withdrawalId: derivedWithdrawalId }, }, result: { @@ -95,6 +95,7 @@ beforeEach(() => { signatures: 'signatures', targetAddress: 'targetAddress', expiry: 'expiry', + creation: '1', }, }, }, diff --git a/libs/withdraws/src/lib/use-withdraw.ts b/libs/withdraws/src/lib/use-withdraw.ts index 1f242c524..9ebbc218c 100644 --- a/libs/withdraws/src/lib/use-withdraw.ts +++ b/libs/withdraws/src/lib/use-withdraw.ts @@ -3,13 +3,21 @@ import { determineId } from '@vegaprotocol/react-helpers'; import { useBridgeContract, useEthereumTransaction } from '@vegaprotocol/web3'; import { useVegaTransaction, useVegaWallet } from '@vegaprotocol/wallet'; import { useCallback, useEffect, useState } from 'react'; -import { ERC20_APPROVAL_QUERY } from './queries'; -import type { WithdrawTransactionArgs } from './use-complete-withdraw'; +import { ERC20_APPROVAL_QUERY, ERC20_APPROVAL_QUERY_NEW } from './queries'; +import type { + NewWithdrawTransactionArgs, + WithdrawTransactionArgs, +} from './use-complete-withdraw'; import type { Erc20Approval, Erc20ApprovalVariables, Erc20Approval_erc20WithdrawalApproval, } from './__generated__/Erc20Approval'; +import type { + CollateralBridge, + CollateralBridgeNew, +} from '@vegaprotocol/smart-contracts'; +import type { Erc20ApprovalNew } from './__generated__/Erc20ApprovalNew'; export interface WithdrawalFields { amount: string; @@ -17,12 +25,12 @@ export interface WithdrawalFields { receiverAddress: string; } -export const useWithdraw = (cancelled: boolean) => { +export const useWithdraw = (cancelled: boolean, isNewContract: boolean) => { const [withdrawalId, setWithdrawalId] = useState(null); const [approval, setApproval] = useState(null); - const contract = useBridgeContract(); + const contract = useBridgeContract(isNewContract); const { keypair } = useVegaWallet(); const { transaction: vegaTx, @@ -38,23 +46,35 @@ export const useWithdraw = (cancelled: boolean) => { if (!contract) { return null; } - return contract.withdrawAsset( - args.assetSource, - args.amount, - args.targetAddress, - args.nonce, - args.signatures - ); + if (contract.isNewContract) { + const withdrawalArguments = args as NewWithdrawTransactionArgs; + return (contract as CollateralBridgeNew).withdrawAsset( + withdrawalArguments.assetSource, + withdrawalArguments.amount, + withdrawalArguments.targetAddress, + withdrawalArguments.creation, + withdrawalArguments.nonce, + withdrawalArguments.signatures + ); + } else { + return (contract as CollateralBridge).withdrawAsset( + args.assetSource, + args.amount, + args.targetAddress, + args.nonce, + args.signatures + ); + } }); - const { data, stopPolling } = useQuery( - ERC20_APPROVAL_QUERY, - { - variables: { withdrawalId: withdrawalId || '' }, - skip: !withdrawalId, - pollInterval: 1000, - } - ); + const { data, stopPolling } = useQuery< + Erc20Approval | Erc20ApprovalNew, + Erc20ApprovalVariables + >(isNewContract ? ERC20_APPROVAL_QUERY_NEW : ERC20_APPROVAL_QUERY, { + variables: { withdrawalId: withdrawalId || '' }, + skip: !withdrawalId, + pollInterval: 1000, + }); const submit = useCallback( async (withdrawal: WithdrawalFields) => { diff --git a/libs/withdraws/src/lib/withdraw-manager.spec.tsx b/libs/withdraws/src/lib/withdraw-manager.spec.tsx index 35dd4fac9..674948fcf 100644 --- a/libs/withdraws/src/lib/withdraw-manager.spec.tsx +++ b/libs/withdraws/src/lib/withdraw-manager.spec.tsx @@ -27,6 +27,7 @@ beforeEach(() => { assets: [generateAsset()], accounts: [generateAccount()], initialAssetId: undefined, + isNewContract: true, }; mockSubmit = jest.fn(); mockReset = jest.fn(); diff --git a/libs/withdraws/src/lib/withdraw-manager.tsx b/libs/withdraws/src/lib/withdraw-manager.tsx index 8c68037af..f4639af84 100644 --- a/libs/withdraws/src/lib/withdraw-manager.tsx +++ b/libs/withdraws/src/lib/withdraw-manager.tsx @@ -15,12 +15,14 @@ export interface WithdrawManagerProps { assets: Asset[]; accounts: Account[]; initialAssetId?: string; + isNewContract: boolean; } export const WithdrawManager = ({ assets, accounts, initialAssetId, + isNewContract, }: WithdrawManagerProps) => { const dialogDismissed = useRef(false); const [dialogOpen, setDialogOpen] = useState(false); @@ -28,7 +30,8 @@ export const WithdrawManager = ({ const { account: ethereumAccount } = useWeb3React(); const { ethTx, vegaTx, approval, submit, reset } = useWithdraw( - dialogDismissed.current + dialogDismissed.current, + isNewContract ); // Find the asset object from the select box diff --git a/libs/withdraws/src/lib/withdrawals-table.tsx b/libs/withdraws/src/lib/withdrawals-table.tsx index 5ab28cde4..53b7e63cd 100644 --- a/libs/withdraws/src/lib/withdrawals-table.tsx +++ b/libs/withdraws/src/lib/withdrawals-table.tsx @@ -22,7 +22,7 @@ export interface WithdrawalsTableProps { export const WithdrawalsTable = ({ withdrawals }: WithdrawalsTableProps) => { const { ETHERSCAN_URL } = useEnvironment(); - const { transaction, submit } = useCompleteWithdraw(); + const { transaction, submit } = useCompleteWithdraw(true); return ( <> From 14e0c6d97382497d82567d0f219c3a6c5355299c Mon Sep 17 00:00:00 2001 From: Dexter Date: Fri, 10 Jun 2022 12:11:41 +0100 Subject: [PATCH 14/27] ci: run on push --- .github/workflows/process-tranches.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/process-tranches.yml b/.github/workflows/process-tranches.yml index 82face594..f8e3c5a78 100644 --- a/.github/workflows/process-tranches.yml +++ b/.github/workflows/process-tranches.yml @@ -1,8 +1,9 @@ name: Generate tranches on: - schedule: - - cron: '0 8 * * *' + push: + branches: + - master jobs: master: From 33a4f1998f84554f11f2c6c1d8a732422bff2e21 Mon Sep 17 00:00:00 2001 From: dexturr Date: Fri, 10 Jun 2022 11:14:12 +0000 Subject: [PATCH 15/27] chore: update tranches Signed-off-by: github-actions[bot] --- apps/static/src/assets/mainnet-tranches.json | 45 ++++++++++------- apps/static/src/assets/stagnet1-tranches.json | 4 +- apps/static/src/assets/testnet-tranches.json | 50 +++++++++++++++++-- 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/apps/static/src/assets/mainnet-tranches.json b/apps/static/src/assets/mainnet-tranches.json index fbe954323..33680b70a 100644 --- a/apps/static/src/assets/mainnet-tranches.json +++ b/apps/static/src/assets/mainnet-tranches.json @@ -38,7 +38,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "129999.45", "total_removed": "0", - "locked_amount": "128733.531922131624800445", + "locked_amount": "128702.258821810997160075", "deposits": [ { "amount": "129999.45", @@ -488,7 +488,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "97499.58", "total_removed": "0", - "locked_amount": "69497.804367586511875314", + "locked_amount": "69467.128369393065306816", "deposits": [ { "amount": "97499.58", @@ -521,7 +521,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "135173.4239508", "total_removed": "0", - "locked_amount": "94991.50027510763958279454272", + "locked_amount": "94949.57148156734313578085456", "deposits": [ { "amount": "135173.4239508", @@ -554,7 +554,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "32499.86", "total_removed": "0", - "locked_amount": "29236.526134556975979928", + "locked_amount": "29223.621271863148204368", "deposits": [ { "amount": "32499.86", @@ -587,7 +587,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "10833.29", "total_removed": "0", - "locked_amount": "9516.20555226409271242", + "locked_amount": "9512.005144683075076614", "deposits": [ { "amount": "10833.29", @@ -675,7 +675,7 @@ "tranche_end": "2022-11-01T00:00:00.000Z", "total_added": "22500", "total_removed": "0", - "locked_amount": "17567.56538722826325", + "locked_amount": "17551.4450294384055", "deposits": [ { "amount": "15000", @@ -761,7 +761,7 @@ "tranche_end": "2023-06-02T00:00:00.000Z", "total_added": "1939928.38", "total_removed": "0", - "locked_amount": "1895621.722373567283390646", + "locked_amount": "1894921.069651338677773708", "deposits": [ { "amount": "1852091.69", @@ -1777,7 +1777,7 @@ "tranche_end": "2022-09-30T00:00:00.000Z", "total_added": "60916.66666633337", "total_removed": "17896.108295511846757997", - "locked_amount": "17441.4797230171041555920584206535", + "locked_amount": "17420.888530195123279803636440392", "deposits": [ { "amount": "2833.333333", @@ -5074,7 +5074,7 @@ "tranche_end": "2022-09-03T00:00:00.000Z", "total_added": "15188.000000000000000003", "total_removed": "2940.67574505453", - "locked_amount": "3522.93548744292251660069586558219178085", + "locked_amount": "3517.4499686707260972006947820585996957", "deposits": [ { "amount": "30", @@ -12995,7 +12995,7 @@ "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "3732368.4671", "total_removed": "74162.9780761646031", - "locked_amount": "2937411.92663141290263259791", + "locked_amount": "2936335.26733871597192697778", "deposits": [ { "amount": "1998.95815", @@ -13708,7 +13708,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "15788853.065470999700000001", "total_removed": "0", - "locked_amount": "15635103.2263417783727485046974350974330901", + "locked_amount": "15631305.0073042045689881912669725141051635", "deposits": [ { "amount": "16249.93", @@ -15659,8 +15659,8 @@ "tranche_start": "2021-11-05T00:00:00.000Z", "tranche_end": "2023-05-05T00:00:00.000Z", "total_added": "14597706.0446472999", - "total_removed": "2060364.221154936478447272", - "locked_amount": "8787060.96966496232042363690370807", + "total_removed": "2061059.540233251105626022", + "locked_amount": "8783536.43364863776014796073499585", "deposits": [ { "amount": "129284.449", @@ -15899,6 +15899,11 @@ "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", "tx": "0x9f5bbd82ea1e9bf1851226eff6ac9f9f5812049a00495af3b5a9a52583ccdc12" }, + { + "amount": "695.31907831462717875", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0xb03cf0af0c643353e144ed7c303e2e9a67daa280207517d8384bead582bc2b4f" + }, { "amount": "1384.357697656285885", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", @@ -17405,6 +17410,12 @@ "tranche_id": 3, "tx": "0x9f5bbd82ea1e9bf1851226eff6ac9f9f5812049a00495af3b5a9a52583ccdc12" }, + { + "amount": "695.31907831462717875", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0xb03cf0af0c643353e144ed7c303e2e9a67daa280207517d8384bead582bc2b4f" + }, { "amount": "1384.357697656285885", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", @@ -18529,8 +18540,8 @@ } ], "total_tokens": "359123.469575", - "withdrawn_tokens": "142331.306095719943488", - "remaining_tokens": "216792.163479280056512" + "withdrawn_tokens": "143026.62517403457066675", + "remaining_tokens": "216096.84440096542933325" }, { "address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB", @@ -19521,7 +19532,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "5778205.3912159303", "total_removed": "1354392.149662275357179742", - "locked_amount": "3154917.52927673126070243605513068", + "locked_amount": "3153524.96378046480744648063298108", "deposits": [ { "amount": "552496.6455", @@ -20588,7 +20599,7 @@ "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "472355.6199999996", "total_removed": "0", - "locked_amount": "465449.7107371628403081293607306", + "locked_amount": "465279.1079177881946966991730086", "deposits": [ { "amount": "3000", diff --git a/apps/static/src/assets/stagnet1-tranches.json b/apps/static/src/assets/stagnet1-tranches.json index 6a6f02f0c..0bad41b19 100644 --- a/apps/static/src/assets/stagnet1-tranches.json +++ b/apps/static/src/assets/stagnet1-tranches.json @@ -38,7 +38,7 @@ "tranche_end": "2022-11-26T13:48:10.000Z", "total_added": "100", "total_removed": "0", - "locked_amount": "46.366796676813804", + "locked_amount": "46.33068556570269", "deposits": [ { "amount": "100", @@ -242,7 +242,7 @@ "tranche_end": "2022-10-12T00:53:20.000Z", "total_added": "100", "total_removed": "0", - "locked_amount": "33.890610730593607", + "locked_amount": "33.85449961948249", "deposits": [ { "amount": "100", diff --git a/apps/static/src/assets/testnet-tranches.json b/apps/static/src/assets/testnet-tranches.json index 4b358bf5d..3255264dc 100644 --- a/apps/static/src/assets/testnet-tranches.json +++ b/apps/static/src/assets/testnet-tranches.json @@ -67,12 +67,54 @@ "tranche_id": 2, "tranche_start": "2021-10-12T00:53:20.000Z", "tranche_end": "2022-10-12T00:53:20.000Z", - "total_added": "0", + "total_added": "1010.000000000000000001", "total_removed": "0", - "locked_amount": "0", - "deposits": [], + "locked_amount": "341.9304461567731490003385449961948249", + "deposits": [ + { + "amount": "1000", + "user": "0x0d18ACaa868f87BB4666F918326141cAEAe893Fa", + "tx": "0x6c22362848c237c4a5c7134e88c179d6ba90cb79498e0b7a33e4c76089448ddd" + }, + { + "amount": "10.000000000000000001", + "user": "0x970Bf6C66E55f90a7D455354954Af5CaBA11318C", + "tx": "0xcab88d53d9fe946b7089ce0b2e252ed6a589bc8a1900a733fdd6bd3a6fc6f514" + } + ], "withdrawals": [], - "users": [] + "users": [ + { + "address": "0x0d18ACaa868f87BB4666F918326141cAEAe893Fa", + "deposits": [ + { + "amount": "1000", + "user": "0x0d18ACaa868f87BB4666F918326141cAEAe893Fa", + "tranche_id": 2, + "tx": "0x6c22362848c237c4a5c7134e88c179d6ba90cb79498e0b7a33e4c76089448ddd" + } + ], + "withdrawals": [], + "total_tokens": "1000", + "withdrawn_tokens": "0", + "remaining_tokens": "1000" + }, + { + "address": "0x970Bf6C66E55f90a7D455354954Af5CaBA11318C", + "deposits": [ + { + "amount": "10.000000000000000001", + "user": "0x970Bf6C66E55f90a7D455354954Af5CaBA11318C", + "tranche_id": 2, + "tx": "0xcab88d53d9fe946b7089ce0b2e252ed6a589bc8a1900a733fdd6bd3a6fc6f514" + } + ], + "withdrawals": [], + "total_tokens": "10.000000000000000001", + "withdrawn_tokens": "0", + "remaining_tokens": "10.000000000000000001" + } + ] }, { "tranche_id": 3, From 7e3809ff0a713fadacaaf65acf2fd3ca9d92b4cc Mon Sep 17 00:00:00 2001 From: vega-ci-bot Date: Fri, 10 Jun 2022 11:16:59 +0000 Subject: [PATCH 16/27] chore: update tranches Signed-off-by: github-actions[bot] --- apps/static/src/assets/mainnet-tranches.json | 28 +++++++++---------- apps/static/src/assets/stagnet1-tranches.json | 4 +-- apps/static/src/assets/testnet-tranches.json | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/static/src/assets/mainnet-tranches.json b/apps/static/src/assets/mainnet-tranches.json index 33680b70a..f635ea2e2 100644 --- a/apps/static/src/assets/mainnet-tranches.json +++ b/apps/static/src/assets/mainnet-tranches.json @@ -38,7 +38,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "129999.45", "total_removed": "0", - "locked_amount": "128702.258821810997160075", + "locked_amount": "128701.7975504454231171", "deposits": [ { "amount": "129999.45", @@ -488,7 +488,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "97499.58", "total_removed": "0", - "locked_amount": "69467.128369393065306816", + "locked_amount": "69466.675905152803180158", "deposits": [ { "amount": "97499.58", @@ -521,7 +521,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "135173.4239508", "total_removed": "0", - "locked_amount": "94949.57148156734313578085456", + "locked_amount": "94948.95304106561999501366568", "deposits": [ { "amount": "135173.4239508", @@ -554,7 +554,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "32499.86", "total_removed": "0", - "locked_amount": "29223.621271863148204368", + "locked_amount": "29223.430927970910406854", "deposits": [ { "amount": "32499.86", @@ -587,7 +587,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "10833.29", "total_removed": "0", - "locked_amount": "9512.005144683075076614", + "locked_amount": "9511.943189593205427378", "deposits": [ { "amount": "10833.29", @@ -675,7 +675,7 @@ "tranche_end": "2022-11-01T00:00:00.000Z", "total_added": "22500", "total_removed": "0", - "locked_amount": "17551.4450294384055", + "locked_amount": "17551.207257699276", "deposits": [ { "amount": "15000", @@ -761,7 +761,7 @@ "tranche_end": "2023-06-02T00:00:00.000Z", "total_added": "1939928.38", "total_removed": "0", - "locked_amount": "1894921.069651338677773708", + "locked_amount": "1894910.735177472760483892", "deposits": [ { "amount": "1852091.69", @@ -1777,7 +1777,7 @@ "tranche_end": "2022-09-30T00:00:00.000Z", "total_added": "60916.66666633337", "total_removed": "17896.108295511846757997", - "locked_amount": "17420.888530195123279803636440392", + "locked_amount": "17420.5848146205769465391540042482", "deposits": [ { "amount": "2833.333333", @@ -5074,7 +5074,7 @@ "tranche_end": "2022-09-03T00:00:00.000Z", "total_added": "15188.000000000000000003", "total_removed": "2940.67574505453", - "locked_amount": "3517.4499686707260972006947820585996957", + "locked_amount": "3517.36905847285627304069476607686453574", "deposits": [ { "amount": "30", @@ -12995,7 +12995,7 @@ "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "3732368.4671", "total_removed": "74162.9780761646031", - "locked_amount": "2936335.26733871597192697778", + "locked_amount": "2936319.38685046585763031711", "deposits": [ { "amount": "1998.95815", @@ -13708,7 +13708,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "15788853.065470999700000001", "total_removed": "0", - "locked_amount": "15631305.0073042045689881912669725141051635", + "locked_amount": "15631248.984407074175570487773354615848878", "deposits": [ { "amount": "16249.93", @@ -15660,7 +15660,7 @@ "tranche_end": "2023-05-05T00:00:00.000Z", "total_added": "14597706.0446472999", "total_removed": "2061059.540233251105626022", - "locked_amount": "8783536.43364863776014796073499585", + "locked_amount": "8783484.44751599798964378496613199", "deposits": [ { "amount": "129284.449", @@ -19532,7 +19532,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "5778205.3912159303", "total_removed": "1354392.149662275357179742", - "locked_amount": "3153524.96378046480744648063298108", + "locked_amount": "3153504.42374504993534547505667121", "deposits": [ { "amount": "552496.6455", @@ -20599,7 +20599,7 @@ "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "472355.6199999996", "total_removed": "0", - "locked_amount": "465279.1079177881946966991730086", + "locked_amount": "465276.59156364815124428407102992", "deposits": [ { "amount": "3000", diff --git a/apps/static/src/assets/stagnet1-tranches.json b/apps/static/src/assets/stagnet1-tranches.json index 0bad41b19..63797fb38 100644 --- a/apps/static/src/assets/stagnet1-tranches.json +++ b/apps/static/src/assets/stagnet1-tranches.json @@ -38,7 +38,7 @@ "tranche_end": "2022-11-26T13:48:10.000Z", "total_added": "100", "total_removed": "0", - "locked_amount": "46.33068556570269", + "locked_amount": "46.33014967021817", "deposits": [ { "amount": "100", @@ -242,7 +242,7 @@ "tranche_end": "2022-10-12T00:53:20.000Z", "total_added": "100", "total_removed": "0", - "locked_amount": "33.85449961948249", + "locked_amount": "33.85396372399797", "deposits": [ { "amount": "100", diff --git a/apps/static/src/assets/testnet-tranches.json b/apps/static/src/assets/testnet-tranches.json index 3255264dc..0dca71b6e 100644 --- a/apps/static/src/assets/testnet-tranches.json +++ b/apps/static/src/assets/testnet-tranches.json @@ -69,7 +69,7 @@ "tranche_end": "2022-10-12T00:53:20.000Z", "total_added": "1010.000000000000000001", "total_removed": "0", - "locked_amount": "341.9304461567731490003385449961948249", + "locked_amount": "341.9250656392694170003385396689497717", "deposits": [ { "amount": "1000", From 86e29c98a3e7b5cb6caa03168f999a5aa1cb57da Mon Sep 17 00:00:00 2001 From: Dexter Date: Fri, 10 Jun 2022 12:11:41 +0100 Subject: [PATCH 17/27] Revert "ci: run on push" This reverts commit 14e0c6d97382497d82567d0f219c3a6c5355299c. --- .github/workflows/process-tranches.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/process-tranches.yml b/.github/workflows/process-tranches.yml index f8e3c5a78..82face594 100644 --- a/.github/workflows/process-tranches.yml +++ b/.github/workflows/process-tranches.yml @@ -1,9 +1,8 @@ name: Generate tranches on: - push: - branches: - - master + schedule: + - cron: '0 8 * * *' jobs: master: From 71226e3d754b190a5967085dafac9773e098faf1 Mon Sep 17 00:00:00 2001 From: botond <105208209+notbot00@users.noreply.github.com> Date: Fri, 10 Jun 2022 13:59:55 +0100 Subject: [PATCH 18/27] fix: network env var escaping (#541) * fix: network env var escaping * fix: remove logging --- apps/explorer/.env | 10 +++++----- apps/explorer/.env.capsule | 2 +- apps/explorer/.env.devnet | 2 +- apps/explorer/.env.mainnet | 2 +- apps/explorer/.env.stagnet1 | 2 +- apps/explorer/.env.stagnet2 | 2 +- apps/explorer/.env.testnet | 2 +- apps/token/.env | 2 +- apps/token/.env.devnet | 2 +- apps/token/.env.mainnet | 2 +- apps/token/.env.stagnet1 | 2 +- apps/token/.env.stagnet2 | 2 +- apps/token/.env.testnet | 2 +- libs/network-switcher/src/hooks/use-environment.tsx | 3 ++- 14 files changed, 19 insertions(+), 18 deletions(-) diff --git a/apps/explorer/.env b/apps/explorer/.env index a72be23cf..0c83efe3e 100644 --- a/apps/explorer/.env +++ b/apps/explorer/.env @@ -18,13 +18,13 @@ NX_URL=$URL NX_DEPLOY_URL=$DEPLOY_URL NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL -NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" -NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm" -NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket" -NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" +NX_CHAIN_EXPLORER_URL = 'https://explorer.vega.trading/.netlify/functions/chain-explorer-api' +NX_TENDERMINT_URL = 'https://lb.testnet.vega.xyz/tm' +NX_TENDERMINT_WEBSOCKET_URL = 'wss://lb.testnet.vega.xyz/tm/websocket' +NX_VEGA_URL = 'https://lb.testnet.vega.xyz/query' NX_VEGA_ENV = 'TESTNET' NX_VEGA_REST = 'https://lb.testnet.vega.xyz/datanode/rest' -NX_VEGA_NETWORKS = '{\"TESTNET\":\"https://explorer.fairground.wtf\",\"MAINNET\":\"https://explorer.vega.xyz\"}' +NX_VEGA_NETWORKS = '{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}' CYPRESS_VEGA_TENDERMINT_URL='https://lb.testnet.vega.xyz/tm' # App flags diff --git a/apps/explorer/.env.capsule b/apps/explorer/.env.capsule index a9da49665..4589e0022 100644 --- a/apps/explorer/.env.capsule +++ b/apps/explorer/.env.capsule @@ -3,7 +3,7 @@ NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain- NX_TENDERMINT_URL = "http://localhost:26617" NX_TENDERMINT_WEBSOCKET_URL = "wss://localhost:26617/websocket" NX_VEGA_URL = "http://localhost:3028/query" -NX_VEGA_NETWORKS = '{\"TESTNET\":\"https://explorer.fairground.wtf\",\"MAINNET\":\"https://explorer.vega.xyz\"}' +NX_VEGA_NETWORKS = '{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}' NX_VEGA_ENV = 'LOCAL' NX_VEGA_REST = 'http://localhost:3029' diff --git a/apps/explorer/.env.devnet b/apps/explorer/.env.devnet index 838dc8b5e..047a94ad3 100644 --- a/apps/explorer/.env.devnet +++ b/apps/explorer/.env.devnet @@ -3,6 +3,6 @@ NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain- NX_TENDERMINT_URL = "https://n04.d.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://n04.d.vega.xyz/tm/websocket" NX_VEGA_URL = "https://n04.d.vega.xyz/query" -NX_VEGA_NETWORKS = '{\"TESTNET\":\"https://explorer.fairground.wtf\",\"MAINNET\":\"https://explorer.vega.xyz\"}' +NX_VEGA_NETWORKS = '{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}' NX_VEGA_ENV = 'DEVNET' NX_VEGA_REST = 'https://n04.d.vega.xyz/datanode/rest' diff --git a/apps/explorer/.env.mainnet b/apps/explorer/.env.mainnet index 5c8dcad60..d982efa5c 100644 --- a/apps/explorer/.env.mainnet +++ b/apps/explorer/.env.mainnet @@ -3,6 +3,6 @@ NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain- NX_TENDERMINT_URL = "https://mainnet-observer-proxy01.ops.vega.xyz/" NX_TENDERMINT_WEBSOCKET_URL = "wss://mainnet-observer-proxy01.ops.vega.xyz/websocket" NX_VEGA_URL = "https://api.token.vega.xyz/query" -NX_VEGA_NETWORKS = '{\"TESTNET\":\"https://explorer.fairground.wtf\",\"MAINNET\":\"https://explorer.vega.xyz\"}' +NX_VEGA_NETWORKS = '{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}' NX_VEGA_ENV = 'MAINNET' NX_VEGA_REST = 'https://api.token.vega.xyz/' diff --git a/apps/explorer/.env.stagnet1 b/apps/explorer/.env.stagnet1 index 544db503d..4e6859861 100644 --- a/apps/explorer/.env.stagnet1 +++ b/apps/explorer/.env.stagnet1 @@ -3,6 +3,6 @@ NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain- NX_TENDERMINT_URL = "https://n03.s.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.s.vega.xyz/tm/websocket" NX_VEGA_URL = "https://n03.s.vega.xyz/query" -NX_VEGA_NETWORKS = '{\"TESTNET\":\"https://explorer.fairground.wtf\",\"MAINNET\":\"https://explorer.vega.xyz\"}' +NX_VEGA_NETWORKS = '{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}' NX_VEGA_ENV = 'STAGNET' NX_VEGA_REST = 'https://n03.s.vega.xyz/datanode/rest' diff --git a/apps/explorer/.env.stagnet2 b/apps/explorer/.env.stagnet2 index 7b8772813..9b1e479c0 100644 --- a/apps/explorer/.env.stagnet2 +++ b/apps/explorer/.env.stagnet2 @@ -3,6 +3,6 @@ NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain- NX_TENDERMINT_URL = "https://n03.stagnet2.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.stagnet2.vega.xyz/tm/websocket" NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query" -NX_VEGA_NETWORKS = '{\"TESTNET\":\"https://explorer.fairground.wtf\",\"MAINNET\":\"https://explorer.vega.xyz\"}' +NX_VEGA_NETWORKS = '{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}' NX_VEGA_ENV = 'STAGNET2' NX_VEGA_REST = 'https://n01.stagnet2.vega.xyz/datanode/rest' diff --git a/apps/explorer/.env.testnet b/apps/explorer/.env.testnet index c9b04da85..aabe2f8bc 100644 --- a/apps/explorer/.env.testnet +++ b/apps/explorer/.env.testnet @@ -3,6 +3,6 @@ NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain- NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket" NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" -NX_VEGA_NETWORKS = '{\"TESTNET\":\"https://explorer.fairground.wtf\",\"MAINNET\":\"https://explorer.vega.xyz\"}' +NX_VEGA_NETWORKS = '{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}' NX_VEGA_ENV = 'TESTNET' NX_VEGA_REST = 'https://lb.testnet.vega.xyz/datanode/rest' diff --git a/apps/token/.env b/apps/token/.env index 4eacba644..f93bd20e5 100644 --- a/apps/token/.env +++ b/apps/token/.env @@ -26,7 +26,7 @@ NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7 NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" NX_FAIRGROUND = false NX_IS_NEW_BRIDGE_CONTRACT = true -NX_VEGA_NETWORKS='{\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET\":\"https://dev.token.vega.xyz\",\"STAGNET2\":\"staging2.token.vega.xyz\",\"TESTNET\":\"token.fairground.wtf\",\"MAINNET\":\"token.vega.xyz\"}' +NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}' #Test configuration variables CYPRESS_FAIRGROUND = false diff --git a/apps/token/.env.devnet b/apps/token/.env.devnet index 303b1686a..c236b7ccd 100644 --- a/apps/token/.env.devnet +++ b/apps/token/.env.devnet @@ -1,7 +1,7 @@ # App configuration variables NX_VEGA_ENV = "DEVNET" NX_VEGA_URL = "https://n04.d.vega.xyz/query" -NX_VEGA_NETWORKS='{\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET\":\"https://dev.token.vega.xyz\",\"STAGNET2\":\"staging2.token.vega.xyz\",\"TESTNET\":\"token.fairground.wtf\",\"MAINNET\":\"token.vega.xyz\"}' +NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}' NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" diff --git a/apps/token/.env.mainnet b/apps/token/.env.mainnet index 7035d8405..bf5ba3311 100644 --- a/apps/token/.env.mainnet +++ b/apps/token/.env.mainnet @@ -1,7 +1,7 @@ # App configuration variables NX_VEGA_ENV = "MAINNET" NX_VEGA_URL = "https://api.token.vega.xyz/query" -NX_VEGA_NETWORKS='{\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET\":\"https://dev.token.vega.xyz\",\"STAGNET2\":\"staging2.token.vega.xyz\",\"TESTNET\":\"token.fairground.wtf\",\"MAINNET\":\"token.vega.xyz\"}' +NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}' NX_ETHEREUM_CHAIN_ID = 1 NX_ETHEREUM_PROVIDER_URL = "https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" NX_ETHERSCAN_URL = "https://etherscan.io" diff --git a/apps/token/.env.stagnet1 b/apps/token/.env.stagnet1 index f69446951..e27160cc9 100644 --- a/apps/token/.env.stagnet1 +++ b/apps/token/.env.stagnet1 @@ -1,7 +1,7 @@ # App configuration variables NX_VEGA_ENV = "STAGNET" NX_VEGA_URL = "https://n03.s.vega.xyz/query" -NX_VEGA_NETWORKS='{\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET\":\"https://dev.token.vega.xyz\",\"STAGNET2\":\"staging2.token.vega.xyz\",\"TESTNET\":\"token.fairground.wtf\",\"MAINNET\":\"token.vega.xyz\"}' +NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}' NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" diff --git a/apps/token/.env.stagnet2 b/apps/token/.env.stagnet2 index 6ae4725d3..64d0833f7 100644 --- a/apps/token/.env.stagnet2 +++ b/apps/token/.env.stagnet2 @@ -1,7 +1,7 @@ # App configuration variables NX_VEGA_ENV = "STAGNET2" NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query" -NX_VEGA_NETWORKS='{\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET\":\"https://dev.token.vega.xyz\",\"STAGNET2\":\"staging2.token.vega.xyz\",\"TESTNET\":\"token.fairground.wtf\",\"MAINNET\":\"token.vega.xyz\"}' +NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}' NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" diff --git a/apps/token/.env.testnet b/apps/token/.env.testnet index a3f050437..47d22c4ca 100644 --- a/apps/token/.env.testnet +++ b/apps/token/.env.testnet @@ -1,7 +1,7 @@ # App configuration variables NX_VEGA_ENV = "TESTNET" NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" -NX_VEGA_NETWORKS='{\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET\":\"https://dev.token.vega.xyz\",\"STAGNET2\":\"staging2.token.vega.xyz\",\"TESTNET\":\"token.fairground.wtf\",\"MAINNET\":\"token.vega.xyz\"}' +NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}' NX_ETHEREUM_CHAIN_ID = 3 NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8" NX_ETHERSCAN_URL = "https://ropsten.etherscan.io" diff --git a/libs/network-switcher/src/hooks/use-environment.tsx b/libs/network-switcher/src/hooks/use-environment.tsx index 50d426708..f77497fc6 100644 --- a/libs/network-switcher/src/hooks/use-environment.tsx +++ b/libs/network-switcher/src/hooks/use-environment.tsx @@ -111,9 +111,10 @@ const transformValue = (key: EnvKey, value?: string) => { try { return JSON.parse(value); } catch (e) { - throw new Error( + console.warn( 'Error parsing the "NX_VEGA_NETWORKS" environment variable. Make sure it has a valid JSON format.' ); + return undefined; } } return undefined; From d0452aeb81cc917f6ca46d4b0b61d806b6d13665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Fri, 10 Jun 2022 15:52:39 +0200 Subject: [PATCH 19/27] Feature/303 orderbook improvements (#312) * [#151] market-depth code cleanup * [#303] Make ask and bid relative volume bars relative to maximum bid or ask volume * [#151] align cmulative vol bars to left * [#151] replace orderbook zoom in zoom out buttons with dropdown * [#151] fill gaps in orderbook data * Order book mocks added * [#151] mark mid price in orderbook * [303] Show number in orderbook cumulative volume column * [#808] show indicative uncrossing volume instead of volume if market is in auction mode * Method for asserting order book style * [#303] Add test id attributes to orderbook cells * Cleanup steps after merge * Order book test passing * Change method name * Revert "[#151] fill gaps in orderbook data" This reverts commit 90ea4e4ab381748d990095a4c332f1c98205d8f9. * [#303] Orderbook rows render optimization * test: update feature with @todo tests Same tests can be found in Notion * [#303] Orderbook scroll to mid price * [#303] orderbook scroll to row pixel perfect alignment * [#303] Bring back best offer horizontal lines * [#303] Preserve center price level on row number change, adjust indicativePrice to resoluton * feat(orderbook): add storybook Refs: #303 * feat(orderbook): fix no rows handling Refs: #303 * feat(orderbook): add orderbook stories for auction and continous market Refs: #303 * feat(orderbook): add stories for empty orderbook Refs: #303 * feat(orderbook): fix footer position when there is no data Refs: #303 * feat(orderbook): seperate number of rows for buy and sell in storybook Refs: #303 * feat(orderbook): keep mid price in middle until user will scroll Refs: #303 * feat(orderbook): style scrollbar * feat(orderbook): style scrollbar * feat(orderbook): adjust gaps * feat(orderbook): adjust gaps * test: addition for autofilled order and mid price lines * fix: lint * feat(orderbook): make it posiible to write RTL tests * feat(orderbook): fix price focus, add unit tests * feat(orderbook): fix price scroll to mid proce, add unit tests * feat(orderbook): improvements - fix scrollbar colors in firefox - bring back resolution dropdown chevron - hide go to mid button when locked on mid price - right align ask vol bar - change grid gap to 5px - add vertical lines between columns - display "No data" if theis no orderbook data - align header labels to right * feat(orderbook): fix formatting * feat(orderbook): add 5px gap * feat(orderbook): improvements after code review * feat(orderbook): display full height vertical lines * fix: change in mid position * feat(orderbook): fix number cannot be converted to BigInt because it is not integer * feat(orderbook): fix TS2307 in trading-e2e caused by .module.scss import Co-authored-by: Joe --- apps/trading-e2e/declaration.d.ts | 1 + .../src/integration/trading-page.feature | 44 +- .../src/support/mocks/generate-order-book.ts | 125 ++++ .../src/support/pages/trading-page.ts | 3 +- .../step_definitions/trading-page.step.ts | 86 ++- .../support/trading-windows/orderbook-list.ts | 129 +++++ apps/trading-e2e/tsconfig.json | 2 +- libs/market-depth/.storybook/main.js | 28 + .../market-depth/.storybook/preview-head.html | 1 + libs/market-depth/.storybook/preview.js | 12 + libs/market-depth/.storybook/tsconfig.json | 19 + libs/market-depth/README.md | 2 +- libs/market-depth/jest.config.js | 9 +- libs/market-depth/postcss.config.js | 10 + libs/market-depth/project.json | 31 + libs/market-depth/src/index.ts | 1 + .../src/lib/__generated__/MarketDepth.ts | 26 +- .../__generated__/MarketDepthSubscription.ts | 26 +- libs/market-depth/src/lib/depth-chart.tsx | 8 +- .../src/lib/market-depth-data-provider.ts | 19 +- .../market-depth/src/lib/market-depth-mock.ts | 57 -- .../src/lib/orderbook-container.tsx | 32 +- .../src/lib/orderbook-data.spec.ts | 126 ++-- libs/market-depth/src/lib/orderbook-data.ts | 215 +++++-- .../src/lib/orderbook-manager.tsx | 56 +- libs/market-depth/src/lib/orderbook-row.tsx | 45 +- .../src/lib/orderbook.module.scss | 22 + libs/market-depth/src/lib/orderbook.spec.tsx | 167 +++++- .../src/lib/orderbook.stories.tsx | 71 +++ libs/market-depth/src/lib/orderbook.tsx | 417 +++++++++++++- libs/market-depth/src/setup-tests.ts | 1 + libs/market-depth/src/styles.scss | 3 + libs/market-depth/tailwind.config.js | 17 + libs/market-depth/tsconfig.json | 3 + libs/market-depth/tsconfig.lib.json | 6 +- libs/market-depth/tsconfig.spec.json | 2 +- .../src/hooks/use-data-provider.ts | 14 +- .../src/lib/generic-data-provider.ts | 33 +- .../src/lib/grid/cumulative-vol-cell.tsx | 84 +-- .../react-helpers/src/lib/grid/price-cell.tsx | 8 +- .../src/lib/grid/price-flash-cell.spec.tsx | 1 - libs/react-helpers/src/lib/grid/vol-cell.tsx | 45 +- libs/tailwindcss-config/src/theme.js | 1 + libs/ui-toolkit/.storybook/preview.js | 1 + package.json | 1 + yarn.lock | 536 ++++++++++++++++++ 46 files changed, 2158 insertions(+), 388 deletions(-) create mode 100644 apps/trading-e2e/declaration.d.ts create mode 100644 apps/trading-e2e/src/support/mocks/generate-order-book.ts create mode 100644 apps/trading-e2e/src/support/trading-windows/orderbook-list.ts create mode 100644 libs/market-depth/.storybook/main.js create mode 100644 libs/market-depth/.storybook/preview-head.html create mode 100644 libs/market-depth/.storybook/preview.js create mode 100644 libs/market-depth/.storybook/tsconfig.json create mode 100644 libs/market-depth/postcss.config.js delete mode 100644 libs/market-depth/src/lib/market-depth-mock.ts create mode 100644 libs/market-depth/src/lib/orderbook.module.scss create mode 100644 libs/market-depth/src/lib/orderbook.stories.tsx create mode 100644 libs/market-depth/src/styles.scss create mode 100644 libs/market-depth/tailwind.config.js diff --git a/apps/trading-e2e/declaration.d.ts b/apps/trading-e2e/declaration.d.ts new file mode 100644 index 000000000..d5cf927a7 --- /dev/null +++ b/apps/trading-e2e/declaration.d.ts @@ -0,0 +1 @@ +declare module '*.scss'; diff --git a/apps/trading-e2e/src/integration/trading-page.feature b/apps/trading-e2e/src/integration/trading-page.feature index 93799c109..0e4c2f3f1 100644 --- a/apps/trading-e2e/src/integration/trading-page.feature +++ b/apps/trading-e2e/src/integration/trading-page.feature @@ -76,15 +76,6 @@ Feature: Trading page And place a buy 'FOK' market order with amount of 0 Then Order rejected by wallet error shown containing text "must be positive" - @manual - Scenario: Deal ticket: GTT order failed because invalid date - - @manual - Scenario: Deal ticket: GTT order failed because date in the past - - @manual - Scenario: Deal ticket: GTT order failed because date over allowed period - Scenario: Positions: Displayed when connected to wallet Given I am on the trading page for an active market And I connect to Vega Wallet @@ -97,9 +88,40 @@ Feature: Trading page When I click on accounts tab Then accounts are displayed And I can see account for tEURO - + Scenario: Orders: Placed orders displayed Given I am on the trading page for an active market And I connect to Vega Wallet When I click on orders tab - Then placed orders are displayed \ No newline at end of file + Then placed orders are displayed + + Scenario: Orderbook displayed + Given I am on the trading page for an active market + When I click on order book tab + Then orderbook is displayed with expected orders + And orderbook can be reduced and expanded + + @todo + Scenario: Orderbook paginated with over 100 orders + Given I am on the trading page for an active market + When I click on order book tab + And a large amount is orders are received + Then a certain amount of orders are displayed + + @todo + Scenario: Orderbook uses non-static prices for market in auction + Given I am on the trading page for a market in auction + When I click on order book tab + Then order book is rendered using non-static offers + + @todo + Scenario: Orderbook updated when large order is made + Given I am on the trading page for an active market + When I place a large order + Then I should see my order have an effect on the order book + + @todo + Scenario: Able to place order by clicking on order from orderbook + Given I am on the trading page for an active market + When I place a large order + Then I should see my order have an effect on the order book diff --git a/apps/trading-e2e/src/support/mocks/generate-order-book.ts b/apps/trading-e2e/src/support/mocks/generate-order-book.ts new file mode 100644 index 000000000..2a4acbc49 --- /dev/null +++ b/apps/trading-e2e/src/support/mocks/generate-order-book.ts @@ -0,0 +1,125 @@ +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; +import type { + MarketDepth, + MarketDepth_market, +} from '@vegaprotocol/market-depth'; +import { MarketTradingMode } from '@vegaprotocol/types'; + +export const generateOrderBook = ( + override?: PartialDeep +): MarketDepth => { + const marketDepth: MarketDepth_market = { + id: 'b2426f67b085ba8fb429f1b529d49372b2d096c6fb6f509f76c5863abb6d969e', + decimalPlaces: 5, + data: { + staticMidPrice: '826337', + marketTradingMode: MarketTradingMode.Continuous, + indicativeVolume: '0', + indicativePrice: '0', + bestStaticBidPrice: '826336', + bestStaticOfferPrice: '826338', + market: { + id: 'b2426f67b085ba8fb429f1b529d49372b2d096c6fb6f509f76c5863abb6d969e', + __typename: 'Market', + }, + __typename: 'MarketData', + }, + depth: { + lastTrade: { + price: '826338', + __typename: 'Trade', + }, + sell: [ + { + price: '826338', + volume: '303', + numberOfOrders: '8', + __typename: 'PriceLevel', + }, + { + price: '826339', + volume: '193', + numberOfOrders: '4', + __typename: 'PriceLevel', + }, + { + price: '826340', + volume: '316', + numberOfOrders: '7', + __typename: 'PriceLevel', + }, + { + price: '826341', + volume: '412', + numberOfOrders: '9', + __typename: 'PriceLevel', + }, + { + price: '826342', + volume: '264', + numberOfOrders: '6', + __typename: 'PriceLevel', + }, + ], + buy: [ + { + price: '826339', + volume: '200', + numberOfOrders: '5', + __typename: 'PriceLevel', + }, + { + price: '826336', + volume: '1475', + numberOfOrders: '28', + __typename: 'PriceLevel', + }, + { + price: '826335', + volume: '193', + numberOfOrders: '3', + __typename: 'PriceLevel', + }, + { + price: '826334', + volume: '425', + numberOfOrders: '8', + __typename: 'PriceLevel', + }, + { + price: '826333', + volume: '845', + numberOfOrders: '17', + __typename: 'PriceLevel', + }, + { + price: '826332', + volume: '248', + numberOfOrders: '4', + __typename: 'PriceLevel', + }, + { + price: '826331', + volume: '162', + numberOfOrders: '3', + __typename: 'PriceLevel', + }, + { + price: '826328', + volume: '20', + numberOfOrders: '2', + __typename: 'PriceLevel', + }, + ], + sequenceNumber: '36109974', + __typename: 'MarketDepth', + }, + __typename: 'Market', + }; + const defaultResult = { + market: marketDepth, + }; + + return merge(defaultResult, override); +}; diff --git a/apps/trading-e2e/src/support/pages/trading-page.ts b/apps/trading-e2e/src/support/pages/trading-page.ts index 8a3c6639a..837415db8 100644 --- a/apps/trading-e2e/src/support/pages/trading-page.ts +++ b/apps/trading-e2e/src/support/pages/trading-page.ts @@ -10,7 +10,6 @@ export default class TradingPage extends BasePage { collateralTab = 'Collateral'; tradesTab = 'Trades'; completedTrades = 'Market-trades'; - orderBookTab = 'Prderbook'; clickOnOrdersTab() { cy.getByTestId(this.ordersTab).click(); @@ -37,6 +36,6 @@ export default class TradingPage extends BasePage { } clickOrderBookTab() { - cy.getByTestId(this.orderBookTab).click(); + cy.getByTestId(this.orderbookTab).click(); } } diff --git a/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts b/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts index c082115ad..de57f231f 100644 --- a/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts @@ -8,20 +8,23 @@ import { generateDealTicketQuery } from '../mocks/generate-deal-ticket-query'; import { generateMarket } from '../mocks/generate-market'; import { generateOrders } from '../mocks/generate-orders'; import { generatePositions } from '../mocks/generate-positions'; +import { generateOrderBook } from '../mocks/generate-order-book'; import { generateAccounts } from '../mocks/generate-accounts'; import PositionsList from '../trading-windows/positions-list'; import AccountsList from '../trading-windows/accounts-list'; import TradesList from '../trading-windows/trades-list'; import TradingPage from '../pages/trading-page'; import OrdersList from '../trading-windows/orders-list'; +import OrderBookList from '../trading-windows/orderbook-list'; import MarketPage from '../pages/markets-page'; const tradesList = new TradesList(); const tradingPage = new TradingPage(); -const marketPage = new MarketPage(); const positionsList = new PositionsList(); const accountList = new AccountsList(); const ordersList = new OrdersList(); +const orderBookList = new OrderBookList(); +const marketPage = new MarketPage(); const mockMarket = (state: MarketState) => { cy.mockGQL('Market', (req) => { @@ -75,6 +78,12 @@ const mockMarket = (state: MarketState) => { }); } + if (hasOperationName(req, 'MarketDepth')) { + req.reply({ + body: { data: generateOrderBook() }, + }); + } + if (hasOperationName(req, 'Candles')) { req.reply({ body: { data: generateCandles() }, @@ -159,3 +168,78 @@ When('I click on positions tab', () => { Then('positions are displayed', () => { positionsList.verifyPositionsDisplayed(); }); + +When('I click on order book tab', () => { + tradingPage.clickOrderBookTab(); +}); + +Then('orderbook is displayed with expected orders', () => { + orderBookList.verifyOrderBookRow('826342', '0', '8.26342', '264', '1488'); + orderBookList.verifyOrderBookRow('826336', '1475', '8.26336', '0', '1675'); + orderBookList.verifyDisplayedVolume( + '826342', + false, + '18%', + orderBookList.testingVolume.AskVolume + ); + orderBookList.verifyDisplayedVolume( + '826331', + true, + '100%', + orderBookList.testingVolume.CumulativeVolume + ); + // mid level price + orderBookList.verifyOrderBookRow('826337', '0', '8.26337', '0', '200'); + orderBookList.verifyDisplayedVolume( + '826337', + true, + '6%', + orderBookList.testingVolume.CumulativeVolume + ); + orderBookList.verifyTopMidPricePosition('129'); + orderBookList.verifyBottomMidPricePosition('151'); + + // autofilled order + orderBookList.verifyOrderBookRow('826330', '0', '8.26330', '0', '3548'); + orderBookList.verifyDisplayedVolume( + '826330', + true, + '0%', + orderBookList.testingVolume.BidVolume + ); + orderBookList.verifyDisplayedVolume( + '826330', + true, + '100%', + orderBookList.testingVolume.CumulativeVolume + ); +}); + +Then('orderbook can be reduced and expanded', () => { + orderBookList.changePrecision('10'); + orderBookList.verifyOrderBookRow( + '82634', + '1868', + '8.2634', + '1488', + '1488/1868' + ); + orderBookList.verifyCumulativeAskBarPercentage('42%'); + orderBookList.verifyCumulativeBidBarPercentage('53%'); + orderBookList.changePrecision('100'); + orderBookList.verifyOrderBookRow('8263', '3568', '8.263', '1488', ''); + orderBookList.verifyDisplayedVolume( + '8263', + true, + '100%', + orderBookList.testingVolume.BidVolume + ); + orderBookList.verifyDisplayedVolume( + '8263', + false, + '42%', + orderBookList.testingVolume.AskVolume + ); + orderBookList.changePrecision('1'); + orderBookList.verifyOrderBookRow('826342', '0', '8.26342', '264', '1488'); +}); diff --git a/apps/trading-e2e/src/support/trading-windows/orderbook-list.ts b/apps/trading-e2e/src/support/trading-windows/orderbook-list.ts new file mode 100644 index 000000000..17b0a2fda --- /dev/null +++ b/apps/trading-e2e/src/support/trading-windows/orderbook-list.ts @@ -0,0 +1,129 @@ +export default class OrderBookList { + cumulativeVolBidBar = 'bid-bar'; + cumulativeVolAskBar = 'ask-bar'; + precisionChange = 'resolution'; + bidColour = 'darkgreen'; + askColour = 'maroon'; + testingVolume = TestingVolumeType; + topMidPriceLine = 'best-static-offer-price'; + bottomMidPriceLine = 'best-static-bid-price'; + + bidVolTestId(price: string) { + return `bid-vol-${price}`; + } + priceTestId(price: string) { + return `price-${price}`; + } + askVolTestId(price: string) { + return `ask-vol-${price}`; + } + cumulativeVolTestId(price: string) { + return `cumulative-vol-${price}`; + } + + verifyOrderBookDisplayed(price: string) { + cy.getByTestId(this.bidVolTestId(price)).should('not.be.empty'); + cy.getByTestId(this.priceTestId(price)) + .invoke('text') + .then(($priceText) => { + $priceText = $priceText.replace('.', ''); + expect($priceText).to.equal(price); + }); + cy.getByTestId(this.askVolTestId(price)).should('not.be.empty'); + cy.getByTestId(this.cumulativeVolTestId(price)).should('not.be.empty'); + } + + verifyOrderBookRow( + price: string, + expectedBidVol: string, + expectedPrice: string, + expectedAskVol: string, + expectedCumulativeVol: string + ) { + cy.getByTestId(this.bidVolTestId(price)).should( + 'have.text', + expectedBidVol + ); + cy.getByTestId(this.priceTestId(price)).should('have.text', expectedPrice); + cy.getByTestId(this.askVolTestId(price)).should( + 'have.text', + expectedAskVol + ); + cy.getByTestId(this.cumulativeVolTestId(price)).should( + 'have.text', + expectedCumulativeVol + ); + } + + // Value should be 1, 10 or 100 + changePrecision(precisionValue: string) { + cy.getByTestId(this.precisionChange).select(precisionValue); + } + + verifyDisplayedVolume( + price: string, + isBuy: boolean, + expectedPercentage: string, + volumeType: TestingVolumeType + ) { + let expectedColour = ''; + let testId = ''; + + if (isBuy == true) { + expectedColour = this.bidColour; + } else { + expectedColour = this.askColour; + } + + switch (volumeType) { + case TestingVolumeType.BidVolume: + testId = `[data-testid=${this.bidVolTestId(price)}]`; + break; + + case TestingVolumeType.AskVolume: + testId = `[data-testid=${this.askVolTestId(price)}]`; + break; + + case TestingVolumeType.CumulativeVolume: + testId = `[data-testid=${this.cumulativeVolTestId(price)}]`; + break; + } + + cy.get(`${testId} > div`) + .invoke('attr', 'style') + .should('contain', `width: ${expectedPercentage}`) + .should('contain', `background-color: ${expectedColour}`); + } + + verifyCumulativeAskBarPercentage(expectedPercentage: string) { + cy.getByTestId(this.cumulativeVolAskBar) + .invoke('attr', 'style') + .should('contain', `width: ${expectedPercentage}`) + .should('contain', `background-color: ${this.askColour}`); + } + + verifyCumulativeBidBarPercentage(expectedPercentage: string) { + cy.getByTestId(this.cumulativeVolBidBar) + .invoke('attr', 'style') + .should('contain', `width: ${expectedPercentage}`) + .should('contain', `background-color: ${this.bidColour}`); + } + + verifyTopMidPricePosition(expectedPosition: string) { + cy.getByTestId(this.topMidPriceLine) + .invoke('attr', 'style') + .should('contain', `top: ${expectedPosition}px`); + } + + verifyBottomMidPricePosition(expectedPosition: string) { + cy.getByTestId(this.bottomMidPriceLine) + .invoke('attr', 'style') + .should('contain', `top: ${expectedPosition}px`); + } +} + +enum TestingVolumeType { + BidVolume = 'BidVolume', + AskVolume = 'AskVolume', + CumulativeVolume = 'CumulativeVolume', +} diff --git a/apps/trading-e2e/tsconfig.json b/apps/trading-e2e/tsconfig.json index 9f157b52e..117dbb681 100644 --- a/apps/trading-e2e/tsconfig.json +++ b/apps/trading-e2e/tsconfig.json @@ -15,5 +15,5 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true }, - "include": ["src/**/*.ts", "src/**/*.js"] + "include": ["src/**/*.ts", "src/**/*.js", "./declaration.d.ts"] } diff --git a/libs/market-depth/.storybook/main.js b/libs/market-depth/.storybook/main.js new file mode 100644 index 000000000..9997fd7a1 --- /dev/null +++ b/libs/market-depth/.storybook/main.js @@ -0,0 +1,28 @@ +const rootMain = require('../../../.storybook/main'); + +module.exports = { + ...rootMain, + + core: { ...rootMain.core, builder: 'webpack5' }, + + stories: [ + ...rootMain.stories, + '../src/lib/**/*.stories.mdx', + '../src/lib/**/*.stories.@(js|jsx|ts|tsx)', + ], + addons: [ + ...rootMain.addons, + '@nrwl/react/plugins/storybook', + 'storybook-addon-themes', + ], + webpackFinal: async (config, { configType }) => { + // apply any global webpack configs that might have been specified in .storybook/main.js + if (rootMain.webpackFinal) { + config = await rootMain.webpackFinal(config, { configType }); + } + + // add your own webpack tweaks if needed + + return config; + }, +}; diff --git a/libs/market-depth/.storybook/preview-head.html b/libs/market-depth/.storybook/preview-head.html new file mode 100644 index 000000000..dd2e70030 --- /dev/null +++ b/libs/market-depth/.storybook/preview-head.html @@ -0,0 +1 @@ + diff --git a/libs/market-depth/.storybook/preview.js b/libs/market-depth/.storybook/preview.js new file mode 100644 index 000000000..9c0191c4f --- /dev/null +++ b/libs/market-depth/.storybook/preview.js @@ -0,0 +1,12 @@ +import '../src/styles.scss'; +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, + backgrounds: { disable: true }, + themes: { + default: 'dark', + list: [ + { name: 'dark', class: ['dark', 'bg-black'], color: '#000' }, + { name: 'light', class: '', color: '#FFF' }, + ], + }, +}; diff --git a/libs/market-depth/.storybook/tsconfig.json b/libs/market-depth/.storybook/tsconfig.json new file mode 100644 index 000000000..7a1170995 --- /dev/null +++ b/libs/market-depth/.storybook/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "emitDecoratorMetadata": true, + "outDir": "" + }, + "files": [ + "../../../node_modules/@nrwl/react/typings/styled-jsx.d.ts", + "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "../**/*.spec.ts", + "../**/*.spec.js", + "../**/*.spec.tsx", + "../**/*.spec.jsx" + ], + "include": ["../src/**/*", "*.js"] +} diff --git a/libs/market-depth/README.md b/libs/market-depth/README.md index 8f50689b4..bebf556a0 100644 --- a/libs/market-depth/README.md +++ b/libs/market-depth/README.md @@ -4,4 +4,4 @@ This library was generated with [Nx](https://nx.dev). ## Running unit tests -Run `nx test orderbook` to execute the unit tests via [Jest](https://jestjs.io). +Run `nx test market-depth` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/market-depth/jest.config.js b/libs/market-depth/jest.config.js index 58d4af636..8410e843d 100644 --- a/libs/market-depth/jest.config.js +++ b/libs/market-depth/jest.config.js @@ -1,13 +1,8 @@ module.exports = { - displayName: 'orderbook', + displayName: 'market-depth', preset: '../../jest.preset.js', - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.spec.json', - }, - }, transform: { - '^.+\\.[tj]sx?$': 'ts-jest', + '^.+\\.[tj]sx?$': 'babel-jest', }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/libs/market-depth', diff --git a/libs/market-depth/postcss.config.js b/libs/market-depth/postcss.config.js new file mode 100644 index 000000000..cbdd9c22c --- /dev/null +++ b/libs/market-depth/postcss.config.js @@ -0,0 +1,10 @@ +const { join } = require('path'); + +module.exports = { + plugins: { + tailwindcss: { + config: join(__dirname, 'tailwind.config.js'), + }, + autoprefixer: {}, + }, +}; diff --git a/libs/market-depth/project.json b/libs/market-depth/project.json index b2bd26de5..b0efd3fa4 100644 --- a/libs/market-depth/project.json +++ b/libs/market-depth/project.json @@ -38,6 +38,37 @@ "jestConfig": "libs/market-depth/jest.config.js", "passWithNoTests": true } + }, + "storybook": { + "executor": "@nrwl/storybook:storybook", + "options": { + "uiFramework": "@storybook/react", + "port": 4400, + "config": { + "configFolder": "libs/market-depth/.storybook" + } + }, + "configurations": { + "ci": { + "quiet": true + } + } + }, + "build-storybook": { + "executor": "@nrwl/storybook:build", + "outputs": ["{options.outputPath}"], + "options": { + "uiFramework": "@storybook/react", + "outputPath": "dist/storybook/market-depth", + "config": { + "configFolder": "libs/market-depth/.storybook" + } + }, + "configurations": { + "ci": { + "quiet": true + } + } } } } diff --git a/libs/market-depth/src/index.ts b/libs/market-depth/src/index.ts index 841aad5fc..8b352b852 100644 --- a/libs/market-depth/src/index.ts +++ b/libs/market-depth/src/index.ts @@ -1,2 +1,3 @@ export * from './lib/depth-chart'; export * from './lib/orderbook-container'; +export * from './lib/__generated__/MarketDepth'; diff --git a/libs/market-depth/src/lib/__generated__/MarketDepth.ts b/libs/market-depth/src/lib/__generated__/MarketDepth.ts index ea9c37f29..f0d191b83 100644 --- a/libs/market-depth/src/lib/__generated__/MarketDepth.ts +++ b/libs/market-depth/src/lib/__generated__/MarketDepth.ts @@ -3,6 +3,8 @@ // @generated // This file was automatically generated and should not be edited. +import { MarketTradingMode } from "@vegaprotocol/types"; + // ==================================================== // GraphQL query operation: MarketDepth // ==================================================== @@ -18,9 +20,29 @@ export interface MarketDepth_market_data_market { export interface MarketDepth_market_data { __typename: "MarketData"; /** - * the arithmetic average of the best bid price and best offer price. + * the arithmetic average of the best static bid price and best static offer price */ - midPrice: string; + staticMidPrice: string; + /** + * what state the market is in (auction, continuous etc) + */ + marketTradingMode: MarketTradingMode; + /** + * indicative volume if the auction ended now, 0 if not in auction mode + */ + indicativeVolume: string; + /** + * indicative price if the auction ended now, 0 if not in auction mode + */ + indicativePrice: string; + /** + * the highest price level on an order book for buy orders not including pegged orders. + */ + bestStaticBidPrice: string; + /** + * the lowest price level on an order book for offer orders not including pegged orders. + */ + bestStaticOfferPrice: string; /** * market id of the associated mark price */ diff --git a/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts b/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts index 7a81dd677..481ad9251 100644 --- a/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts +++ b/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts @@ -3,6 +3,8 @@ // @generated // This file was automatically generated and should not be edited. +import { MarketTradingMode } from "@vegaprotocol/types"; + // ==================================================== // GraphQL subscription operation: MarketDepthSubscription // ==================================================== @@ -18,9 +20,29 @@ export interface MarketDepthSubscription_marketDepthUpdate_market_data_market { export interface MarketDepthSubscription_marketDepthUpdate_market_data { __typename: "MarketData"; /** - * the arithmetic average of the best bid price and best offer price. + * the arithmetic average of the best static bid price and best static offer price */ - midPrice: string; + staticMidPrice: string; + /** + * what state the market is in (auction, continuous etc) + */ + marketTradingMode: MarketTradingMode; + /** + * indicative volume if the auction ended now, 0 if not in auction mode + */ + indicativeVolume: string; + /** + * indicative price if the auction ended now, 0 if not in auction mode + */ + indicativePrice: string; + /** + * the highest price level on an order book for buy orders not including pegged orders. + */ + bestStaticBidPrice: string; + /** + * the lowest price level on an order book for offer orders not including pegged orders. + */ + bestStaticOfferPrice: string; /** * market id of the associated mark price */ diff --git a/libs/market-depth/src/lib/depth-chart.tsx b/libs/market-depth/src/lib/depth-chart.tsx index 46d453788..076f8e79f 100644 --- a/libs/market-depth/src/lib/depth-chart.tsx +++ b/libs/market-depth/src/lib/depth-chart.tsx @@ -107,9 +107,9 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => { decimalPlacesRef.current ); } - draft.midPrice = delta.market.data?.midPrice + draft.midPrice = delta.market.data?.staticMidPrice ? formatMidPrice( - delta.market.data?.midPrice, + delta.market.data?.staticMidPrice, decimalPlacesRef.current ) : undefined; @@ -133,8 +133,8 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => { return; } dataRef.current = { - midPrice: data.data?.midPrice - ? formatMidPrice(data.data?.midPrice, data.decimalPlaces) + midPrice: data.data?.staticMidPrice + ? formatMidPrice(data.data?.staticMidPrice, data.decimalPlaces) : undefined, data: { buy: diff --git a/libs/market-depth/src/lib/market-depth-data-provider.ts b/libs/market-depth/src/lib/market-depth-data-provider.ts index 7fc063495..6d32a6960 100644 --- a/libs/market-depth/src/lib/market-depth-data-provider.ts +++ b/libs/market-depth/src/lib/market-depth-data-provider.ts @@ -17,7 +17,12 @@ const MARKET_DEPTH_QUERY = gql` id decimalPlaces data { - midPrice + staticMidPrice + marketTradingMode + indicativeVolume + indicativePrice + bestStaticBidPrice + bestStaticOfferPrice market { id } @@ -48,7 +53,12 @@ export const MARKET_DEPTH_SUBSCRIPTION_QUERY = gql` market { id data { - midPrice + staticMidPrice + marketTradingMode + indicativeVolume + indicativePrice + bestStaticBidPrice + bestStaticOfferPrice market { id } @@ -74,7 +84,7 @@ const sequenceNumbers: Record = {}; const update: Update< MarketDepth_market, MarketDepthSubscription_marketDepthUpdate -> = (draft, delta, restart) => { +> = (draft, delta, reload) => { if (delta.market.id !== draft.id) { return; } @@ -84,10 +94,11 @@ const update: Update< } if (sequenceNumber - 1 !== sequenceNumbers[delta.market.id]) { sequenceNumbers[delta.market.id] = 0; - restart(true); + reload(); return; } sequenceNumbers[delta.market.id] = sequenceNumber; + Object.assign(draft.data, delta.market.data); if (delta.buy) { draft.depth.buy = updateLevels(draft.depth.buy ?? [], delta.buy); } diff --git a/libs/market-depth/src/lib/market-depth-mock.ts b/libs/market-depth/src/lib/market-depth-mock.ts deleted file mode 100644 index 4c6cbbcce..000000000 --- a/libs/market-depth/src/lib/market-depth-mock.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { - MarketDepth_market, - MarketDepth_market_depth_sell, - MarketDepth_market_depth_buy, -} from './__generated__/MarketDepth'; - -const depthRow = ( - price: number -): MarketDepth_market_depth_sell | MarketDepth_market_depth_buy => { - return { - __typename: 'PriceLevel', - price: price.toString(), - volume: Math.round(Math.random() * 100).toString(), - numberOfOrders: Math.round(Math.random() * 20).toString(), - }; -}; - -const sell = ( - price: number, - numberOfRecords: number -): MarketDepth_market_depth_sell[] => { - const distance = Math.random() * price * 0.1; - return new Array(numberOfRecords) - .fill(null) - .map(() => depthRow(price + Math.round(Math.random() * distance))); -}; - -const buy = ( - price: number, - numberOfRecords: number -): MarketDepth_market_depth_buy[] => { - const distance = Math.random() * price * 0.1; - return new Array(numberOfRecords) - .fill(null) - .map(() => depthRow(price - Math.round(Math.random() * distance))); -}; - -export const getMockedData = (id?: string): MarketDepth_market => ({ - __typename: 'Market', - id: id || '', - decimalPlaces: 2, - // "positionDecimalPlaces": 0, - data: { - __typename: 'MarketData', - midPrice: '0', - }, - depth: { - __typename: 'MarketDepth', - lastTrade: { - __typename: 'Trade', - price: '12350', - }, - sell: sell(12350 * 0.99, 100), - buy: buy(12350, 100), - sequenceNumber: '118118448', - }, -}); diff --git a/libs/market-depth/src/lib/orderbook-container.tsx b/libs/market-depth/src/lib/orderbook-container.tsx index 2fc086821..dbc447172 100644 --- a/libs/market-depth/src/lib/orderbook-container.tsx +++ b/libs/market-depth/src/lib/orderbook-container.tsx @@ -1,31 +1,5 @@ -import { useState } from 'react'; import { OrderbookManager } from './orderbook-manager'; -import { Button } from '@vegaprotocol/ui-toolkit'; -export const OrderbookContainer = ({ marketId }: { marketId: string }) => { - const [resolution, setResolution] = useState(1); - - return ( - <> -
    - - -
    - - - ); -}; +export const OrderbookContainer = ({ marketId }: { marketId: string }) => ( + +); diff --git a/libs/market-depth/src/lib/orderbook-data.spec.ts b/libs/market-depth/src/lib/orderbook-data.spec.ts index f5cebb580..a94544cbc 100644 --- a/libs/market-depth/src/lib/orderbook-data.spec.ts +++ b/libs/market-depth/src/lib/orderbook-data.spec.ts @@ -1,16 +1,16 @@ import { - compactData, + compactRows, updateLevels, - updateCompactedData, + updateCompactedRows, } from './orderbook-data'; -import type { OrderbookData } from './orderbook-data'; +import type { OrderbookRowData } from './orderbook-data'; import type { MarketDepth_market_depth_sell } from './__generated__/MarketDepth'; import type { MarketDepthSubscription_marketDepthUpdate_sell, MarketDepthSubscription_marketDepthUpdate_buy, } from './__generated__/MarketDepthSubscription'; -describe('compactData', () => { +describe('compactRows', () => { const numberOfRows = 100; const middle = 1000; const sell: MarketDepth_market_depth_sell[] = new Array(numberOfRows) @@ -30,26 +30,26 @@ describe('compactData', () => { numberOfOrders: (numberOfRows - i).toString(), })); it('groups data by price and resolution', () => { - expect(compactData(sell, buy, 1).length).toEqual(200); - expect(compactData(sell, buy, 5).length).toEqual(41); - expect(compactData(sell, buy, 10).length).toEqual(21); + expect(compactRows(sell, buy, 1).length).toEqual(200); + expect(compactRows(sell, buy, 5).length).toEqual(41); + expect(compactRows(sell, buy, 10).length).toEqual(21); }); it('counts cumulative vol', () => { - const orderbookData = compactData(sell, buy, 10); - expect(orderbookData[0].cumulativeVol.ask).toEqual(4950); - expect(orderbookData[0].cumulativeVol.bid).toEqual(0); - expect(orderbookData[10].cumulativeVol.ask).toEqual(390); - expect(orderbookData[10].cumulativeVol.bid).toEqual(579); - expect(orderbookData[orderbookData.length - 1].cumulativeVol.bid).toEqual( + const orderbookRows = compactRows(sell, buy, 10); + expect(orderbookRows[0].cumulativeVol.ask).toEqual(4950); + expect(orderbookRows[0].cumulativeVol.bid).toEqual(0); + expect(orderbookRows[10].cumulativeVol.ask).toEqual(390); + expect(orderbookRows[10].cumulativeVol.bid).toEqual(579); + expect(orderbookRows[orderbookRows.length - 1].cumulativeVol.bid).toEqual( 4950 ); - expect(orderbookData[orderbookData.length - 1].cumulativeVol.ask).toEqual( + expect(orderbookRows[orderbookRows.length - 1].cumulativeVol.ask).toEqual( 0 ); }); it('stores volume by level', () => { - const orderbookData = compactData(sell, buy, 10); - expect(orderbookData[0].askByLevel).toEqual({ + const orderbookRows = compactRows(sell, buy, 10); + expect(orderbookRows[0].askByLevel).toEqual({ '1095': 5, '1096': 4, '1097': 3, @@ -57,7 +57,7 @@ describe('compactData', () => { '1099': 1, '1100': 0, }); - expect(orderbookData[orderbookData.length - 1].bidByLevel).toEqual({ + expect(orderbookRows[orderbookRows.length - 1].bidByLevel).toEqual({ '901': 0, '902': 1, '903': 2, @@ -66,17 +66,17 @@ describe('compactData', () => { }); it('updates relative data', () => { - const orderbookData = compactData(sell, buy, 10); - expect(orderbookData[0].cumulativeVol.relativeAsk).toEqual(100); - expect(orderbookData[0].cumulativeVol.relativeBid).toEqual(0); - expect(orderbookData[0].relativeAskVol).toEqual(2); - expect(orderbookData[0].relativeBidVol).toEqual(0); - expect(orderbookData[10].cumulativeVol.relativeAsk).toEqual(8); - expect(orderbookData[10].cumulativeVol.relativeBid).toEqual(12); - expect(orderbookData[10].relativeAskVol).toEqual(44); - expect(orderbookData[10].relativeBidVol).toEqual(66); - expect(orderbookData[orderbookData.length - 1].relativeAskVol).toEqual(0); - expect(orderbookData[orderbookData.length - 1].relativeBidVol).toEqual(1); + const orderbookRows = compactRows(sell, buy, 10); + expect(orderbookRows[0].cumulativeVol.relativeAsk).toEqual(100); + expect(orderbookRows[0].cumulativeVol.relativeBid).toEqual(0); + expect(orderbookRows[0].relativeAsk).toEqual(2); + expect(orderbookRows[0].relativeBid).toEqual(0); + expect(orderbookRows[10].cumulativeVol.relativeAsk).toEqual(8); + expect(orderbookRows[10].cumulativeVol.relativeBid).toEqual(12); + expect(orderbookRows[10].relativeAsk).toEqual(44); + expect(orderbookRows[10].relativeBid).toEqual(64); + expect(orderbookRows[orderbookRows.length - 1].relativeAsk).toEqual(0); + expect(orderbookRows[orderbookRows.length - 1].relativeBid).toEqual(1); }); }); @@ -137,8 +137,8 @@ describe('updateLevels', () => { }); }); -describe('updateCompactedData', () => { - const orderbookData: OrderbookData[] = [ +describe('updateCompactedRows', () => { + const orderbookRows: OrderbookRowData[] = [ { price: '120', cumulativeVol: { @@ -153,8 +153,8 @@ describe('updateCompactedData', () => { bidByLevel: {}, ask: 10, bid: 0, - relativeAskVol: 25, - relativeBidVol: 0, + relativeAsk: 25, + relativeBid: 0, }, { price: '100', @@ -174,8 +174,8 @@ describe('updateCompactedData', () => { }, ask: 40, bid: 40, - relativeAskVol: 100, - relativeBidVol: 100, + relativeAsk: 100, + relativeBid: 100, }, { price: '80', @@ -191,8 +191,8 @@ describe('updateCompactedData', () => { }, ask: 0, bid: 10, - relativeAskVol: 0, - relativeBidVol: 25, + relativeAsk: 0, + relativeBid: 25, }, ]; const resolution = 10; @@ -210,18 +210,18 @@ describe('updateCompactedData', () => { volume: '10', numberOfOrders: '10', }; - const updatedData = updateCompactedData( - orderbookData, + const updatedRows = updateCompactedRows( + orderbookRows, [sell], [buy], resolution ); - expect(updatedData[0].ask).toEqual(20); - expect(updatedData[0].askByLevel?.[120]).toEqual(10); - expect(updatedData[0].cumulativeVol.ask).toEqual(60); - expect(updatedData[2].bid).toEqual(20); - expect(updatedData[2].bidByLevel?.[80]).toEqual(10); - expect(updatedData[2].cumulativeVol.bid).toEqual(60); + expect(updatedRows[0].ask).toEqual(20); + expect(updatedRows[0].askByLevel?.[120]).toEqual(10); + expect(updatedRows[0].cumulativeVol.ask).toEqual(60); + expect(updatedRows[2].bid).toEqual(20); + expect(updatedRows[2].bidByLevel?.[80]).toEqual(10); + expect(updatedRows[2].cumulativeVol.bid).toEqual(60); }); it('remove row', () => { @@ -237,13 +237,13 @@ describe('updateCompactedData', () => { volume: '0', numberOfOrders: '0', }; - const updatedData = updateCompactedData( - orderbookData, + const updatedRows = updateCompactedRows( + orderbookRows, [sell], [buy], resolution ); - expect(updatedData.length).toEqual(1); + expect(updatedRows.length).toEqual(1); }); it('add new row at the end', () => { @@ -259,17 +259,17 @@ describe('updateCompactedData', () => { volume: '5', numberOfOrders: '5', }; - const updatedData = updateCompactedData( - orderbookData, + const updatedRows = updateCompactedRows( + orderbookRows, [sell], [buy], resolution ); - expect(updatedData.length).toEqual(5); - expect(updatedData[0].price).toEqual('130'); - expect(updatedData[0].cumulativeVol.ask).toEqual(55); - expect(updatedData[4].price).toEqual('60'); - expect(updatedData[4].cumulativeVol.bid).toEqual(55); + expect(updatedRows.length).toEqual(5); + expect(updatedRows[0].price).toEqual('130'); + expect(updatedRows[0].cumulativeVol.ask).toEqual(55); + expect(updatedRows[4].price).toEqual('60'); + expect(updatedRows[4].cumulativeVol.bid).toEqual(55); }); it('add new row in the middle', () => { @@ -285,18 +285,18 @@ describe('updateCompactedData', () => { volume: '5', numberOfOrders: '5', }; - const updatedData = updateCompactedData( - orderbookData, + const updatedRows = updateCompactedRows( + orderbookRows, [sell], [buy], resolution ); - expect(updatedData.length).toEqual(5); - expect(updatedData[1].price).toEqual('110'); - expect(updatedData[1].cumulativeVol.ask).toEqual(45); - expect(updatedData[0].cumulativeVol.ask).toEqual(55); - expect(updatedData[3].price).toEqual('90'); - expect(updatedData[3].cumulativeVol.bid).toEqual(45); - expect(updatedData[4].cumulativeVol.bid).toEqual(55); + expect(updatedRows.length).toEqual(5); + expect(updatedRows[1].price).toEqual('110'); + expect(updatedRows[1].cumulativeVol.ask).toEqual(45); + expect(updatedRows[0].cumulativeVol.ask).toEqual(55); + expect(updatedRows[3].price).toEqual('90'); + expect(updatedRows[3].cumulativeVol.bid).toEqual(45); + expect(updatedRows[4].cumulativeVol.bid).toEqual(55); }); }); diff --git a/libs/market-depth/src/lib/orderbook-data.ts b/libs/market-depth/src/lib/orderbook-data.ts index edb117510..9ff332d97 100644 --- a/libs/market-depth/src/lib/orderbook-data.ts +++ b/libs/market-depth/src/lib/orderbook-data.ts @@ -1,12 +1,16 @@ import produce from 'immer'; import groupBy from 'lodash/groupBy'; +import { VolumeType } from '@vegaprotocol/react-helpers'; +import { MarketTradingMode } from '@vegaprotocol/types'; import type { MarketDepth_market_depth_sell, MarketDepth_market_depth_buy, + MarketDepth_market_data, } from './__generated__/MarketDepth'; import type { MarketDepthSubscription_marketDepthUpdate_sell, MarketDepthSubscription_marketDepthUpdate_buy, + MarketDepthSubscription_marketDepthUpdate_market_data, } from './__generated__/MarketDepthSubscription'; export interface CumulativeVol { @@ -16,28 +20,32 @@ export interface CumulativeVol { relativeAsk?: number; } -export interface OrderbookData { +export interface OrderbookRowData { price: string; bid: number; bidByLevel: Record; - relativeBidVol?: number; + relativeBid?: number; ask: number; askByLevel: Record; - relativeAskVol?: number; + relativeAsk?: number; cumulativeVol: CumulativeVol; } -const getGroupPrice = (price: string, resolution: number) => { +export type OrderbookData = Partial< + Omit +> & { rows: OrderbookRowData[] | null }; + +export const getPriceLevel = (price: string | bigint, resolution: number) => { const p = BigInt(price); const r = BigInt(resolution); - let groupPrice = (p / r) * r; - if (p - groupPrice >= resolution / 2) { - groupPrice += BigInt(resolution); + let priceLevel = (p / r) * r; + if (p - priceLevel >= resolution / 2) { + priceLevel += BigInt(resolution); } - return groupPrice.toString(); + return priceLevel.toString(); }; -const getMaxVolumes = (orderbookData: OrderbookData[]) => ({ +const getMaxVolumes = (orderbookData: OrderbookRowData[]) => ({ bid: Math.max(...orderbookData.map((data) => data.bid)), ask: Math.max(...orderbookData.map((data) => data.ask)), cumulativeVol: Math.max( @@ -50,13 +58,14 @@ const getMaxVolumes = (orderbookData: OrderbookData[]) => ({ const toPercentValue = (value?: number) => Math.ceil((value ?? 0) * 100); /** - * @summary Updates relativeAskVol, relativeBidVol, cumulativeVol.relativeAsk, cumulativeVol.relativeBid + * @summary Updates relativeAsk, relativeBid, cumulativeVol.relativeAsk, cumulativeVol.relativeBid */ -const updateRelativeData = (data: OrderbookData[]) => { +const updateRelativeData = (data: OrderbookRowData[]) => { const { bid, ask, cumulativeVol } = getMaxVolumes(data); + const maxBidAsk = Math.max(bid, ask); data.forEach((data) => { - data.relativeAskVol = toPercentValue(data.ask / ask); - data.relativeBidVol = toPercentValue(data.bid / bid); + data.relativeAsk = toPercentValue(data.ask / maxBidAsk); + data.relativeBid = toPercentValue(data.bid / maxBidAsk); data.cumulativeVol.relativeAsk = toPercentValue( data.cumulativeVol.ask / cumulativeVol ); @@ -66,37 +75,37 @@ const updateRelativeData = (data: OrderbookData[]) => { }); }; -const createData = ( +export const createRow = ( price: string, volume = 0, - dataType?: 'sell' | 'buy' -): OrderbookData => ({ + dataType?: VolumeType +): OrderbookRowData => ({ price, - ask: dataType === 'sell' ? volume : 0, - bid: dataType === 'buy' ? volume : 0, + ask: dataType === VolumeType.ask ? volume : 0, + bid: dataType === VolumeType.bid ? volume : 0, cumulativeVol: { - ask: dataType === 'sell' ? volume : 0, - bid: dataType === 'buy' ? volume : 0, + ask: dataType === VolumeType.ask ? volume : 0, + bid: dataType === VolumeType.bid ? volume : 0, }, - askByLevel: dataType === 'sell' ? { [price]: volume } : {}, - bidByLevel: dataType === 'buy' ? { [price]: volume } : {}, + askByLevel: dataType === VolumeType.ask ? { [price]: volume } : {}, + bidByLevel: dataType === VolumeType.bid ? { [price]: volume } : {}, }); const mapRawData = - (dataType: 'sell' | 'buy') => + (dataType: VolumeType.ask | VolumeType.bid) => ( data: | MarketDepth_market_depth_sell | MarketDepthSubscription_marketDepthUpdate_sell | MarketDepth_market_depth_buy | MarketDepthSubscription_marketDepthUpdate_buy - ): OrderbookData => - createData(data.price, Number(data.volume), dataType); + ): OrderbookRowData => + createRow(data.price, Number(data.volume), dataType); /** * @summary merges sell amd buy data, orders by price desc, group by price level, counts cumulative and relative values */ -export const compactData = ( +export const compactRows = ( sell: | ( | MarketDepth_market_depth_sell @@ -112,25 +121,25 @@ export const compactData = ( resolution: number ) => { // map raw sell data to OrderbookData - const askOrderbookData = [...(sell ?? [])].map( - mapRawData('sell') + const askOrderbookData = [...(sell ?? [])].map( + mapRawData(VolumeType.ask) ); // map raw buy data to OrderbookData - const bidOrderbookData = [...(buy ?? [])].map( - mapRawData('buy') + const bidOrderbookData = [...(buy ?? [])].map( + mapRawData(VolumeType.bid) ); // group by price level - const groupedByLevel = groupBy( + const groupedByLevel = groupBy( [...askOrderbookData, ...bidOrderbookData], - (row) => getGroupPrice(row.price, resolution) + (row) => getPriceLevel(row.price, resolution) ); // create single OrderbookData from grouped OrderbookData[], sum volumes and atore volume by level - const orderbookData = Object.keys(groupedByLevel).reduce( + const orderbookData = Object.keys(groupedByLevel).reduce( (rows, price) => rows.concat( - groupedByLevel[price].reduce( + groupedByLevel[price].reduce( (a, c) => ({ ...a, ask: a.ask + c.ask, @@ -138,7 +147,7 @@ export const compactData = ( bid: (a.bid ?? 0) + (c.bid ?? 0), bidByLevel: Object.assign(a.bidByLevel, c.bidByLevel), }), - createData(price) + createRow(price) ) ), [] @@ -175,9 +184,9 @@ export const compactData = ( * @param modifiedIndex * @returns max (sell) or min (buy) modified index in draft data, mutates draft */ -const partiallyUpdateCompactedData = ( - dataType: 'sell' | 'buy', - draft: OrderbookData[], +const partiallyUpdateCompactedRows = ( + dataType: VolumeType, + draft: OrderbookRowData[], delta: | MarketDepthSubscription_marketDepthUpdate_sell | MarketDepthSubscription_marketDepthUpdate_buy, @@ -186,26 +195,27 @@ const partiallyUpdateCompactedData = ( ) => { const { price } = delta; const volume = Number(delta.volume); - const groupPrice = getGroupPrice(price, resolution); - const volKey = dataType === 'sell' ? 'ask' : 'bid'; - const oppositeVolKey = dataType === 'sell' ? 'bid' : 'ask'; - const volByLevelKey = dataType === 'sell' ? 'askByLevel' : 'bidByLevel'; - const resolveModifiedIndex = dataType === 'sell' ? Math.max : Math.min; - let index = draft.findIndex((data) => data.price === groupPrice); + const priceLevel = getPriceLevel(price, resolution); + const isAskDataType = dataType === VolumeType.ask; + const volKey = isAskDataType ? 'ask' : 'bid'; + const oppositeVolKey = isAskDataType ? 'bid' : 'ask'; + const volByLevelKey = isAskDataType ? 'askByLevel' : 'bidByLevel'; + const resolveModifiedIndex = isAskDataType ? Math.max : Math.min; + let index = draft.findIndex((data) => data.price === priceLevel); if (index !== -1) { modifiedIndex = resolveModifiedIndex(modifiedIndex, index); draft[index][volKey] = draft[index][volKey] - (draft[index][volByLevelKey][price] || 0) + volume; draft[index][volByLevelKey][price] = volume; } else { - const newData: OrderbookData = createData(groupPrice, volume, dataType); - index = draft.findIndex((data) => BigInt(data.price) < BigInt(groupPrice)); + const newData: OrderbookRowData = createRow(priceLevel, volume, dataType); + index = draft.findIndex((data) => BigInt(data.price) < BigInt(priceLevel)); if (index !== -1) { draft.splice(index, 0, newData); newData.cumulativeVol[oppositeVolKey] = - draft[index + (groupPrice === 'sell' ? -1 : 1)].cumulativeVol[ + draft[index + (isAskDataType ? -1 : 1)]?.cumulativeVol[ oppositeVolKey - ]; + ] ?? 0; modifiedIndex = resolveModifiedIndex(modifiedIndex, index); } else { draft.push(newData); @@ -218,35 +228,35 @@ const partiallyUpdateCompactedData = ( /** * Updates OrderbookData[] with new data received from subscription - mutates input * - * @param orderbookData + * @param rows * @param sell * @param buy * @param resolution * @returns void */ -export const updateCompactedData = ( - orderbookData: OrderbookData[], +export const updateCompactedRows = ( + rows: OrderbookRowData[], sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null, buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null, resolution: number ) => - produce(orderbookData, (draft) => { + produce(rows, (draft) => { let sellModifiedIndex = -1; - sell?.forEach((buy) => { - sellModifiedIndex = partiallyUpdateCompactedData( - 'sell', + sell?.forEach((delta) => { + sellModifiedIndex = partiallyUpdateCompactedRows( + VolumeType.ask, draft, - buy, + delta, resolution, sellModifiedIndex ); }); let buyModifiedIndex = draft.length; - buy?.forEach((sell) => { - buyModifiedIndex = partiallyUpdateCompactedData( - 'buy', + buy?.forEach((delta) => { + buyModifiedIndex = partiallyUpdateCompactedRows( + VolumeType.bid, draft, - sell, + delta, resolution, buyModifiedIndex ); @@ -283,6 +293,25 @@ export const updateCompactedData = ( updateRelativeData(draft); }); +export const mapMarketData = ( + data: + | MarketDepth_market_data + | MarketDepthSubscription_marketDepthUpdate_market_data + | null, + resolution: number +) => ({ + staticMidPrice: + data?.staticMidPrice && getPriceLevel(data?.staticMidPrice, resolution), + bestStaticBidPrice: + data?.bestStaticBidPrice && + getPriceLevel(data?.bestStaticBidPrice, resolution), + bestStaticOfferPrice: + data?.bestStaticOfferPrice && + getPriceLevel(data?.bestStaticOfferPrice, resolution), + indicativePrice: + data?.indicativePrice && getPriceLevel(data?.indicativePrice, resolution), +}); + /** * Updates raw data with new data received from subscription - mutates input * @param levels @@ -317,3 +346,69 @@ export const updateLevels = ( }); return levels; }; + +export interface MockDataGeneratorParams { + numberOfSellRows: number; + numberOfBuyRows: number; + overlap: number; + midPrice: number; + bestStaticBidPrice: number; + bestStaticOfferPrice: number; + indicativePrice?: number; + indicativeVolume?: number; + resolution: number; +} + +export const generateMockData = ({ + numberOfSellRows, + numberOfBuyRows, + midPrice, + overlap, + bestStaticBidPrice, + bestStaticOfferPrice, + indicativePrice, + indicativeVolume, + resolution, +}: MockDataGeneratorParams) => { + let matrix = new Array(numberOfSellRows).fill(undefined); + let price = midPrice + (numberOfSellRows - Math.ceil(overlap / 2) + 1); + const sell: MarketDepth_market_depth_sell[] = matrix.map((row, i) => ({ + __typename: 'PriceLevel', + price: (price -= 1).toString(), + volume: (numberOfSellRows - i + 1).toString(), + numberOfOrders: '', + })); + price += overlap; + matrix = new Array(numberOfBuyRows).fill(undefined); + const buy: MarketDepth_market_depth_buy[] = matrix.map((row, i) => ({ + __typename: 'PriceLevel', + price: (price -= 1).toString(), + volume: (i + 2).toString(), + numberOfOrders: '', + })); + const rows = compactRows(sell, buy, resolution); + return { + rows, + resolution, + indicativeVolume: indicativeVolume?.toString(), + ...mapMarketData( + { + __typename: 'MarketData', + staticMidPrice: '', + marketTradingMode: + overlap > 0 + ? MarketTradingMode.BatchAuction + : MarketTradingMode.Continuous, + bestStaticBidPrice: bestStaticBidPrice.toString(), + bestStaticOfferPrice: bestStaticOfferPrice.toString(), + indicativePrice: indicativePrice?.toString() ?? '', + indicativeVolume: indicativeVolume?.toString() ?? '', + market: { + __typename: 'Market', + id: '', + }, + }, + resolution + ), + }; +}; diff --git a/libs/market-depth/src/lib/orderbook-manager.tsx b/libs/market-depth/src/lib/orderbook-manager.tsx index 50288e8a5..3b9aca216 100644 --- a/libs/market-depth/src/lib/orderbook-manager.tsx +++ b/libs/market-depth/src/lib/orderbook-manager.tsx @@ -1,44 +1,54 @@ import throttle from 'lodash/throttle'; +import produce from 'immer'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { Orderbook } from './orderbook'; import { useDataProvider } from '@vegaprotocol/react-helpers'; import { marketDepthDataProvider } from './market-depth-data-provider'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription'; -import { compactData, updateCompactedData } from './orderbook-data'; +import { + compactRows, + updateCompactedRows, + mapMarketData, +} from './orderbook-data'; import type { OrderbookData } from './orderbook-data'; interface OrderbookManagerProps { marketId: string; - resolution: number; } -export const OrderbookManager = ({ - marketId, - resolution, -}: OrderbookManagerProps) => { +export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => { + const [resolution, setResolution] = useState(1); const variables = useMemo(() => ({ marketId }), [marketId]); const resolutionRef = useRef(resolution); - const [orderbookData, setOrderbookData] = useState( - null - ); - const dataRef = useRef(null); + const [orderbookData, setOrderbookData] = useState({ + rows: null, + }); + const dataRef = useRef({ rows: null }); const setOrderbookDataThrottled = useRef(throttle(setOrderbookData, 1000)); const update = useCallback( (delta: MarketDepthSubscription_marketDepthUpdate) => { - if (!dataRef.current) { + if (!dataRef.current.rows) { return false; } - dataRef.current = updateCompactedData( - dataRef.current, - delta.sell, - delta.buy, - resolutionRef.current - ); + dataRef.current = produce(dataRef.current, (draft) => { + Object.assign(draft, delta.market.data); + draft.rows = updateCompactedRows( + draft.rows ?? [], + delta.sell, + delta.buy, + resolutionRef.current + ); + Object.assign( + draft, + mapMarketData(delta.market.data, resolutionRef.current) + ); + }); setOrderbookDataThrottled.current(dataRef.current); return true; }, + // using resolutionRef.current to avoid using resolution as a dependency - it will cause data provider restart on resolution change [] ); @@ -50,11 +60,15 @@ export const OrderbookManager = ({ useEffect(() => { if (!data) { - dataRef.current = null; + dataRef.current = { rows: null }; setOrderbookData(dataRef.current); return; } - dataRef.current = compactData(data.depth.sell, data.depth.buy, resolution); + dataRef.current = { + ...data.data, + rows: compactRows(data.depth.sell, data.depth.buy, resolution), + ...mapMarketData(data.data, resolution), + }; setOrderbookData(dataRef.current); }, [data, resolution]); @@ -66,8 +80,10 @@ export const OrderbookManager = ({ return ( setResolution(resolution)} /> ); diff --git a/libs/market-depth/src/lib/orderbook-row.tsx b/libs/market-depth/src/lib/orderbook-row.tsx index a0b4b4051..9fdc2a73c 100644 --- a/libs/market-depth/src/lib/orderbook-row.tsx +++ b/libs/market-depth/src/lib/orderbook-row.tsx @@ -4,41 +4,64 @@ import { Vol, CumulativeVol, addDecimalsFormatNumber, + VolumeType, } from '@vegaprotocol/react-helpers'; interface OrderbookRowProps { - bid: number; - relativeBidVol?: number; - price: string; ask: number; - relativeAskVol?: number; + bid: number; + cumulativeAsk?: number; + cumulativeBid?: number; cumulativeRelativeAsk?: number; cumulativeRelativeBid?: number; decimalPlaces: number; + indicativeVolume?: string; + price: string; + relativeAsk?: number; + relativeBid?: number; } export const OrderbookRow = React.memo( ({ - bid, - relativeBidVol, - price, ask, - relativeAskVol, - decimalPlaces, + bid, + cumulativeAsk, + cumulativeBid, cumulativeRelativeAsk, cumulativeRelativeBid, + decimalPlaces, + indicativeVolume, + price, + relativeAsk, + relativeBid, }: OrderbookRowProps) => { return ( <> - + - + ); diff --git a/libs/market-depth/src/lib/orderbook.module.scss b/libs/market-depth/src/lib/orderbook.module.scss new file mode 100644 index 000000000..922498e6e --- /dev/null +++ b/libs/market-depth/src/lib/orderbook.module.scss @@ -0,0 +1,22 @@ +$scrollbar-width: 6px; + +/* Works on Firefox */ +.scroll { + scrollbar-width: thin; + scrollbar-color: #999 #333; +} + +/* Works on Chrome, Edge, and Safari */ +.scroll::-webkit-scrollbar { + width: $scrollbar-width; + background-color: #999; +} + +.scroll::-webkit-scrollbar-thumb { + width: $scrollbar-width; + background-color: #333; +} +.scroll::-webkit-scrollbar-track { + box-shadow: inset 0 0 #{$scrollbar-width} rgb(0 0 0 / 30%); + background-color: #999; +} diff --git a/libs/market-depth/src/lib/orderbook.spec.tsx b/libs/market-depth/src/lib/orderbook.spec.tsx index 7fbabad7d..e767873d5 100644 --- a/libs/market-depth/src/lib/orderbook.spec.tsx +++ b/libs/market-depth/src/lib/orderbook.spec.tsx @@ -1,10 +1,165 @@ -import { render } from '@testing-library/react'; - -import Orderbook from './orderbook'; +import { render, fireEvent, waitFor, screen } from '@testing-library/react'; +import { generateMockData } from './orderbook-data'; +import { Orderbook, rowHeight } from './orderbook'; describe('Orderbook', () => { - it('should render successfully', () => { - const { baseElement } = render(); - expect(baseElement).toBeTruthy(); + const params = { + numberOfSellRows: 100, + numberOfBuyRows: 100, + midPrice: 122900, + bestStaticBidPrice: 122905, + bestStaticOfferPrice: 122895, + decimalPlaces: 3, + overlap: 10, + indicativePrice: 122900, + indicativeVolume: 11, + resolution: 1, + }; + const onResolutionChange = jest.fn(); + const decimalPlaces = 3; + it('should scroll to mid price on init', async () => { + window.innerHeight = 11 * rowHeight; + const result = render( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight); + }); + + it('should keep mid price row in the middle', async () => { + window.innerHeight = 11 * rowHeight; + const result = render( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight); + result.rerender( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + expect(result.getByTestId('scroll').scrollTop).toBe(90 * rowHeight); + }); + + it('should scroll to mid price when it will change', async () => { + window.innerHeight = 11 * rowHeight; + const result = render( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight); + result.rerender( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + expect(result.getByTestId('scroll').scrollTop).toBe(90 * rowHeight); + }); + + it('should should keep price it the middle', async () => { + window.innerHeight = 11 * rowHeight; + const result = render( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + const scrollElement = result.getByTestId('scroll'); + expect(scrollElement.scrollTop).toBe(91 * rowHeight); + scrollElement.scrollTop = 92 * rowHeight; + fireEvent.scroll(scrollElement); + result.rerender( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight); + }); + + it('should should get back to mid price on click', async () => { + window.innerHeight = 11 * rowHeight; + const result = render( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + const scrollElement = result.getByTestId('scroll'); + expect(scrollElement.scrollTop).toBe(91 * rowHeight); + scrollElement.scrollTop = 0; + fireEvent.scroll(scrollElement); + expect(result.getByTestId('scroll').scrollTop).toBe(0); + const scrollToMidPriceButton = result.getByTestId('scroll-to-midprice'); + fireEvent.click(scrollToMidPriceButton); + expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight); + }); + + it('should should get back to mid price on resolution change', async () => { + window.innerHeight = 11 * rowHeight; + const result = render( + + ); + await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`)); + const scrollElement = result.getByTestId('scroll'); + expect(scrollElement.scrollTop).toBe(91 * rowHeight); + scrollElement.scrollTop = 0; + fireEvent.scroll(scrollElement); + expect(result.getByTestId('scroll').scrollTop).toBe(0); + const resolutionSelect = result.getByTestId( + 'resolution' + ) as HTMLSelectElement; + fireEvent.change(resolutionSelect, { target: { value: '10' } }); + expect(onResolutionChange.mock.calls.length).toBe(1); + expect(onResolutionChange.mock.calls[0][0]).toBe(10); + result.rerender( + + ); + expect(result.getByTestId('scroll').scrollTop).toBe(5 * rowHeight); }); }); diff --git a/libs/market-depth/src/lib/orderbook.stories.tsx b/libs/market-depth/src/lib/orderbook.stories.tsx new file mode 100644 index 000000000..7d782fdb5 --- /dev/null +++ b/libs/market-depth/src/lib/orderbook.stories.tsx @@ -0,0 +1,71 @@ +import type { Story, Meta } from '@storybook/react'; +import { generateMockData } from './orderbook-data'; +import type { MockDataGeneratorParams } from './orderbook-data'; +import { Orderbook } from './orderbook'; +import { useState } from 'react'; + +type Props = Omit & { + decimalPlaces: number; +}; + +const OrderbokMockDataProvider = ({ decimalPlaces, ...props }: Props) => { + const [resolution, setResolution] = useState(1); + return ( +
    +
    + +
    +
    + ); +}; + +export default { + component: OrderbokMockDataProvider, + title: 'Orderbook', +} as Meta; + +const Template: Story = (args) => ; + +export const Continuous = Template.bind({}); +Continuous.args = { + numberOfSellRows: 100, + numberOfBuyRows: 100, + midPrice: 1000, + bestStaticBidPrice: 1000, + bestStaticOfferPrice: 1000, + decimalPlaces: 3, + overlap: -1, +}; + +export const Auction = Template.bind({}); +Auction.args = { + numberOfSellRows: 100, + numberOfBuyRows: 100, + midPrice: 122900, + bestStaticBidPrice: 122905, + bestStaticOfferPrice: 122895, + decimalPlaces: 3, + overlap: 10, + indicativePrice: 122900, + indicativeVolume: 11, +}; + +export const Empty = Template.bind({}); +Empty.args = { + numberOfSellRows: 0, + numberOfBuyRows: 0, + midPrice: 0, + bestStaticBidPrice: 0, + bestStaticOfferPrice: 0, + decimalPlaces: 3, + overlap: 0, + indicativePrice: 0, + indicativeVolume: 0, +}; diff --git a/libs/market-depth/src/lib/orderbook.tsx b/libs/market-depth/src/lib/orderbook.tsx index 3da406c63..38f71347b 100644 --- a/libs/market-depth/src/lib/orderbook.tsx +++ b/libs/market-depth/src/lib/orderbook.tsx @@ -1,36 +1,395 @@ -import { t } from '@vegaprotocol/react-helpers'; -import { OrderbookRow } from './orderbook-row'; -import type { OrderbookData } from './orderbook-data'; +import styles from './orderbook.module.scss'; -interface OrderbookProps { - data: OrderbookData[] | null; +import { + Fragment, + useEffect, + useLayoutEffect, + useRef, + useState, + useMemo, + useCallback, +} from 'react'; +import classNames from 'classnames'; + +import { formatNumber, t } from '@vegaprotocol/react-helpers'; +import { MarketTradingMode } from '@vegaprotocol/types'; +import { OrderbookRow } from './orderbook-row'; +import { createRow, getPriceLevel } from './orderbook-data'; +import { Icon, Splash } from '@vegaprotocol/ui-toolkit'; +import type { OrderbookData, OrderbookRowData } from './orderbook-data'; +interface OrderbookProps extends OrderbookData { decimalPlaces: number; + resolution: number; + onResolutionChange: (resolution: number) => void; } -export const Orderbook = ({ data, decimalPlaces }: OrderbookProps) => ( - <> -
    -
    {t('Bid Vol')}
    -
    {t('Price')}
    -
    {t('Ask Vol')}
    -
    {t('Cumulative Vol')}
    -
    -
    - {data?.map((data) => ( - - ))} -
    - +const horizontalLine = (top: string, testId: string) => ( +
    ); +const getNumberOfRows = ( + rows: OrderbookRowData[] | null, + resolution: number +) => { + if (!rows || !rows.length) { + return 0; + } + if (rows.length === 1) { + return 1; + } + return ( + Number(BigInt(rows[0].price) - BigInt(rows[rows.length - 1].price)) / + resolution + + 1 + ); +}; + +const getRowsToRender = ( + rows: OrderbookRowData[] | null, + resolution: number, + offset: number, + limit: number +): OrderbookRowData[] | null => { + if (!rows || !rows.length) { + return rows; + } + if (rows.length === 1) { + return rows; + } + const selectedRows: OrderbookRowData[] = []; + let price = BigInt(rows[0].price) - BigInt(offset * resolution); + let index = Math.max( + rows.findIndex((row) => BigInt(row.price) <= price) - 1, + -1 + ); + while (selectedRows.length < limit && index + 1 < rows.length) { + if (rows[index + 1].price === price.toString()) { + selectedRows.push(rows[index + 1]); + index += 1; + } else { + const row = createRow(price.toString()); + row.cumulativeVol = { + bid: rows[index].cumulativeVol.bid, + relativeBid: rows[index].cumulativeVol.relativeBid, + ask: rows[index + 1].cumulativeVol.ask, + relativeAsk: rows[index + 1].cumulativeVol.relativeAsk, + }; + selectedRows.push(row); + } + price -= BigInt(resolution); + } + return selectedRows; +}; + +// 17px of row height plus 5px gap +export const rowHeight = 22; +// buffer size in rows +const bufferSize = 30; +// margin size in px, when reached scrollOffset will be updated +const marginSize = bufferSize * 0.9 * rowHeight; + +export const Orderbook = ({ + rows, + bestStaticBidPrice, + bestStaticOfferPrice, + marketTradingMode, + indicativeVolume, + indicativePrice, + decimalPlaces, + resolution, + onResolutionChange, +}: OrderbookProps) => { + const scrollElement = useRef(null); + // scroll offset for which rendered rows are selected, will change after user will scroll to margin of rendered data + const [scrollOffset, setScrollOffset] = useState(0); + // actual scrollTop of scrollElement current element + const scrollTopRef = useRef(0); + // price level which is rendered in center of viewport, need to preserve price level when rows will be added or removed + // if undefined then we render mid price in center + const priceInCenter = useRef(); + const [lockOnMidPrice, setLockOnMidPrice] = useState(true); + const resolutionRef = useRef(resolution); + // stores rows[0].price value + const [maxPriceLevel, setMaxPriceLevel] = useState(''); + const [viewportHeight, setViewportHeight] = useState(window.innerHeight); + const numberOfRows = useMemo( + () => getNumberOfRows(rows, resolution), + [rows, resolution] + ); + + const updateScrollOffset = useCallback( + (scrollTop: number) => { + if (Math.abs(scrollOffset - scrollTop) > marginSize) { + setScrollOffset(scrollTop); + } + }, + [scrollOffset] + ); + + const onScroll = useCallback( + (event: React.UIEvent) => { + const { scrollTop } = event.currentTarget; + updateScrollOffset(scrollTop); + if (scrollTop === scrollTopRef.current) { + return; + } + priceInCenter.current = ( + BigInt(resolution) + // extra row on very top - sticky header + BigInt(maxPriceLevel) - + BigInt( + Math.floor((scrollTop + Math.floor(viewportHeight / 2)) / rowHeight) + ) * + BigInt(resolution) + ).toString(); + if (lockOnMidPrice) { + setLockOnMidPrice(false); + } + scrollTopRef.current = scrollTop; + }, + [ + resolution, + lockOnMidPrice, + maxPriceLevel, + viewportHeight, + updateScrollOffset, + ] + ); + + const scrollToPrice = useCallback( + (price: string) => { + if (scrollElement.current && maxPriceLevel) { + let scrollTop = + // distance in rows between midPrice and price from first row * row Height + (Number( + (BigInt(maxPriceLevel) - BigInt(price)) / BigInt(resolution) + ) + + 1) * // add one row for sticky header + rowHeight; + // minus half height of viewport plus half of row + scrollTop -= Math.ceil((viewportHeight - rowHeight) / 2); + // adjust to current rows position + scrollTop += + (scrollTopRef.current % rowHeight) - (scrollTop % rowHeight); + const priceCenterScrollOffset = Math.max(0, Math.min(scrollTop)); + if (scrollTopRef.current !== priceCenterScrollOffset) { + updateScrollOffset(priceCenterScrollOffset); + scrollTopRef.current = priceCenterScrollOffset; + scrollElement.current.scrollTop = priceCenterScrollOffset; + } + } + }, + [maxPriceLevel, resolution, viewportHeight, updateScrollOffset] + ); + + useEffect(() => { + const newMaxPriceLevel = rows?.[0]?.price ?? ''; + if (newMaxPriceLevel !== maxPriceLevel) { + setMaxPriceLevel(newMaxPriceLevel); + } + }, [rows, maxPriceLevel]); + + const scrollToMidPrice = useCallback(() => { + if (!bestStaticOfferPrice || !bestStaticBidPrice) { + return; + } + priceInCenter.current = undefined; + setLockOnMidPrice(true); + scrollToPrice( + getPriceLevel( + BigInt(bestStaticOfferPrice) + + (BigInt(bestStaticBidPrice) - BigInt(bestStaticOfferPrice)) / + BigInt(2), + resolution + ) + ); + }, [bestStaticOfferPrice, bestStaticBidPrice, scrollToPrice, resolution]); + + // adjust scroll position to keep selected price in center + useLayoutEffect(() => { + if (resolutionRef.current !== resolution) { + priceInCenter.current = undefined; + resolutionRef.current = resolution; + setLockOnMidPrice(true); + } + if (priceInCenter.current) { + scrollToPrice(priceInCenter.current); + } else { + scrollToMidPrice(); + } + }, [scrollToMidPrice, scrollToPrice, resolution]); + + // handles viewport resize + useEffect(() => { + function handleResize() { + if (scrollElement.current) { + setViewportHeight( + scrollElement.current.clientHeight || window.innerHeight + ); + } + } + window.addEventListener('resize', handleResize); + handleResize(); + return () => window.removeEventListener('resize', handleResize); + }, []); + + const renderedRows = useMemo(() => { + let offset = Math.max(0, Math.round(scrollOffset / rowHeight)); + const prependingBufferSize = Math.min(bufferSize, offset); + offset -= prependingBufferSize; + const viewportSize = Math.round(viewportHeight / rowHeight); + const limit = Math.min( + prependingBufferSize + viewportSize + bufferSize, + numberOfRows - offset + ); + return { + offset, + limit, + data: getRowsToRender(rows, resolution, offset, limit), + }; + }, [rows, scrollOffset, resolution, viewportHeight, numberOfRows]); + + const paddingTop = renderedRows.offset * rowHeight; + const paddingBottom = + (numberOfRows - renderedRows.offset - renderedRows.limit) * rowHeight; + const minPriceLevel = + BigInt(maxPriceLevel) - BigInt(Math.floor(numberOfRows * resolution)); + const hasData = renderedRows.data && renderedRows.data.length !== 0; + return ( +
    +
    +
    {t('Bid Vol')}
    +
    {t('Price')}
    +
    {t('Ask Vol')}
    +
    {t('Cumulative Vol')}
    +
    + +
    + {hasData ? ( +
    + {renderedRows.data?.map((data) => { + return ( + + + + ); + })} +
    + ) : ( +
    + {t('No data')} +
    + )} +
    +
    +
    + +
    +
    + +
    +
    + {maxPriceLevel && + bestStaticBidPrice && + BigInt(bestStaticBidPrice) < BigInt(maxPriceLevel) && + BigInt(bestStaticBidPrice) > minPriceLevel && + horizontalLine( + `${( + ((BigInt(maxPriceLevel) - BigInt(bestStaticBidPrice)) / + BigInt(resolution) + + BigInt(1)) * + BigInt(rowHeight) - + BigInt(3) + ).toString()}px`, + 'best-static-bid-price' + )} + {maxPriceLevel && + bestStaticOfferPrice && + BigInt(bestStaticOfferPrice) <= BigInt(maxPriceLevel) && + BigInt(bestStaticOfferPrice) > minPriceLevel && + horizontalLine( + `${( + ((BigInt(maxPriceLevel) - BigInt(bestStaticOfferPrice)) / + BigInt(resolution) + + BigInt(2)) * + BigInt(rowHeight) - + BigInt(3) + ).toString()}px`, + 'best-static-offer-price' + )} +
    + ); +}; + export default Orderbook; diff --git a/libs/market-depth/src/setup-tests.ts b/libs/market-depth/src/setup-tests.ts index 7b0828bfa..068c53d36 100644 --- a/libs/market-depth/src/setup-tests.ts +++ b/libs/market-depth/src/setup-tests.ts @@ -1 +1,2 @@ import '@testing-library/jest-dom'; +import 'jest-canvas-mock'; diff --git a/libs/market-depth/src/styles.scss b/libs/market-depth/src/styles.scss new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/libs/market-depth/src/styles.scss @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/libs/market-depth/tailwind.config.js b/libs/market-depth/tailwind.config.js new file mode 100644 index 000000000..1deb8143d --- /dev/null +++ b/libs/market-depth/tailwind.config.js @@ -0,0 +1,17 @@ +const { join } = require('path'); +const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind'); +const theme = require('../tailwindcss-config/src/theme'); +const vegaCustomClasses = require('../tailwindcss-config/src/vega-custom-classes'); + +module.exports = { + content: [ + join(__dirname, 'src/**/*.{ts,tsx,html,mdx}'), + join(__dirname, '.storybook/preview.js'), + ...createGlobPatternsForDependencies(__dirname), + ], + darkMode: 'class', + theme: { + extend: theme, + }, + plugins: [vegaCustomClasses], +}; diff --git a/libs/market-depth/tsconfig.json b/libs/market-depth/tsconfig.json index 1eabf319c..b9096c1ed 100644 --- a/libs/market-depth/tsconfig.json +++ b/libs/market-depth/tsconfig.json @@ -20,6 +20,9 @@ }, { "path": "./tsconfig.spec.json" + }, + { + "path": "./.storybook/tsconfig.json" } ] } diff --git a/libs/market-depth/tsconfig.lib.json b/libs/market-depth/tsconfig.lib.json index 252904bb7..ad9c3d024 100644 --- a/libs/market-depth/tsconfig.lib.json +++ b/libs/market-depth/tsconfig.lib.json @@ -16,7 +16,11 @@ "**/*.spec.js", "**/*.test.js", "**/*.spec.jsx", - "**/*.test.jsx" + "**/*.test.jsx", + "**/*.stories.ts", + "**/*.stories.js", + "**/*.stories.jsx", + "**/*.stories.tsx" ], "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] } diff --git a/libs/market-depth/tsconfig.spec.json b/libs/market-depth/tsconfig.spec.json index 67f149c4c..86a9fa994 100644 --- a/libs/market-depth/tsconfig.spec.json +++ b/libs/market-depth/tsconfig.spec.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", - "types": ["jest", "node"] + "types": ["jest", "node", "@testing-library/jest-dom"] }, "include": [ "**/*.test.ts", diff --git a/libs/react-helpers/src/hooks/use-data-provider.ts b/libs/react-helpers/src/hooks/use-data-provider.ts index 11867af1f..c7dade909 100644 --- a/libs/react-helpers/src/hooks/use-data-provider.ts +++ b/libs/react-helpers/src/hooks/use-data-provider.ts @@ -20,16 +20,16 @@ export function useDataProvider( const [loading, setLoading] = useState(true); const [error, setError] = useState(undefined); const flushRef = useRef<(() => void) | undefined>(undefined); - const restartRef = useRef<((force?: boolean) => void) | undefined>(undefined); + const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined); const initialized = useRef(false); const flush = useCallback(() => { if (flushRef.current) { flushRef.current(); } }, []); - const restart = useCallback((force = false) => { - if (restartRef.current) { - restartRef.current(force); + const reload = useCallback((force = false) => { + if (reloadRef.current) { + reloadRef.current(force); } }, []); const callback = useCallback( @@ -48,14 +48,14 @@ export function useDataProvider( [update] ); useEffect(() => { - const { unsubscribe, flush, restart } = dataProvider( + const { unsubscribe, flush, reload } = dataProvider( callback, client, variables ); flushRef.current = flush; - restartRef.current = restart; + reloadRef.current = reload; return unsubscribe; }, [client, initialized, dataProvider, callback, variables]); - return { data, loading, error, flush, restart }; + return { data, loading, error, flush, reload }; } diff --git a/libs/react-helpers/src/lib/generic-data-provider.ts b/libs/react-helpers/src/lib/generic-data-provider.ts index 70d44c8eb..101fc9a04 100644 --- a/libs/react-helpers/src/lib/generic-data-provider.ts +++ b/libs/react-helpers/src/lib/generic-data-provider.ts @@ -25,7 +25,7 @@ export interface Subscribe { variables?: OperationVariables ): { unsubscribe: () => void; - restart: (force?: boolean) => void; + reload: (forceReset?: boolean) => void; flush: () => void; }; } @@ -34,7 +34,11 @@ export interface Subscribe { type Query = DocumentNode | TypedDocumentNode; export interface Update { - (draft: Draft, delta: Delta, restart: (force?: boolean) => void): void; + ( + draft: Draft, + delta: Delta, + reload: (forceReset?: boolean) => void + ): void; } interface GetData { @@ -47,7 +51,7 @@ interface GetDelta { /** * @param subscriptionQuery query that will be used for subscription - * @param update function that will be executed on each onNext, it should update data base on delta, it can restart data provider + * @param update function that will be execued on each onNext, it should update data base on delta, it can reload data provider * @param getData transforms received query data to format that will be stored in data provider * @param getDelta transforms delta data to format that will be stored in data provider * @param fetchPolicy @@ -105,7 +109,7 @@ function makeDataProviderInternal( while (updateQueue.length) { const delta = updateQueue.shift(); if (delta) { - update(draft, delta, restart); + update(draft, delta, reload); } } }); @@ -123,13 +127,13 @@ function makeDataProviderInternal( } }; - // restart function is passed to update and as a returned by subscribe function - const restart = (hard = false) => { + // reload function is passed to update and as a returned by subscribe function + const reload = (forceReset = false) => { if (loading) { return; } // hard reset on demand or when there is no apollo subscription yet - if (hard || !subscription) { + if (forceReset || !subscription) { reset(); initialize(); } else { @@ -165,7 +169,7 @@ function makeDataProviderInternal( updateQueue.push(delta); } else { const newData = produce(data, (draft) => { - update(draft, delta, restart); + update(draft, delta, reload); }); if (newData === data) { return; @@ -174,12 +178,12 @@ function makeDataProviderInternal( notifyAll(delta); } }, - () => restart() + () => reload() ); await initialFetch(); }; - const reset = () => { + const reset = (clean = true) => { if (subscription) { subscription.unsubscribe(); subscription = undefined; @@ -198,7 +202,6 @@ function makeDataProviderInternal( } }; - // return (callback, c, v) => { callbacks.push(callback); if (callbacks.length === 1) { @@ -210,7 +213,7 @@ function makeDataProviderInternal( } return { unsubscribe: () => unsubscribe(callback), - restart, + reload, flush: () => notify(callback), }; }; @@ -243,7 +246,7 @@ const memoize = ( /** * @param query Query * @param subscriptionQuery Query query that will be used for subscription - * @param update Update function that will be executed on each onNext, it should update data base on delta, it can restart data provider + * @param update Update function that will be executed on each onNext, it should update data base on delta, it can reload data provider * @param getData transforms received query data to format that will be stored in data provider * @param getDelta transforms delta data to format that will be stored in data provider * @param fetchPolicy @@ -252,12 +255,12 @@ const memoize = ( * const marketMidPriceProvider = makeDataProvider( * gql`query MarketMidPrice($marketId: ID!) { market(id: $marketId) { data { midPrice } } }`, * gql`subscription MarketMidPriceSubscription($marketId: ID!) { marketDepthUpdate(marketId: $marketId) { market { data { midPrice } } } }`, - * (draft: Draft, delta: Delta, restart: (force?: boolean) => void) => { draft.midPrice = delta.midPrice } + * (draft: Draft, delta: Delta, reload: (forceReset?: boolean) => void) => { draft.midPrice = delta.midPrice } * (data:QueryData) => data.market.data.midPrice * (delta:SubscriptionData) => delta.marketData.market * ) * - * const { unsubscribe, flush, restart } = marketMidPriceProvider( + * const { unsubscribe, flush, reload } = marketMidPriceProvider( * ({ data, error, loading, delta }) => { ... }, * apolloClient, * { id: '1fd726454fa1220038acbf6ff9ac701d8b8bf3f2d77c93a4998544471dc58747' } diff --git a/libs/react-helpers/src/lib/grid/cumulative-vol-cell.tsx b/libs/react-helpers/src/lib/grid/cumulative-vol-cell.tsx index ea8717ff7..e106d8ec3 100644 --- a/libs/react-helpers/src/lib/grid/cumulative-vol-cell.tsx +++ b/libs/react-helpers/src/lib/grid/cumulative-vol-cell.tsx @@ -1,13 +1,16 @@ import React from 'react'; import type { ICellRendererParams } from 'ag-grid-community'; - +import classNames from 'classnames'; import { BID_COLOR, ASK_COLOR } from './vol-cell'; -const INTERSECT_COLOR = 'darkgray'; - export interface CumulativeVolProps { + ask?: number; + bid?: number; relativeAsk?: number; relativeBid?: number; + indicativeVolume?: string; + testId?: string; + className?: string; } export interface ICumulativeVolCellProps extends ICellRendererParams { @@ -15,44 +18,57 @@ export interface ICumulativeVolCellProps extends ICellRendererParams { } export const CumulativeVol = React.memo( - ({ relativeAsk, relativeBid }: CumulativeVolProps) => { - const bid = relativeBid ? ( + ({ + relativeAsk, + relativeBid, + ask, + bid, + indicativeVolume, + testId, + className, + }: CumulativeVolProps) => { + const askBar = relativeAsk ? (
    relativeBid - ? INTERSECT_COLOR - : BID_COLOR, - }} - >
    - ) : null; - const ask = relativeAsk ? ( -
    relativeAsk - ? INTERSECT_COLOR - : ASK_COLOR, + backgroundColor: ASK_COLOR, }} >
    ) : null; + const bidBar = relativeBid ? ( +
    + ) : null; + + const volume = indicativeVolume ? ( + ({indicativeVolume}) + ) : ( + + {ask ? ask : null} + {ask && bid ? '/' : null} + {bid ? bid : null} + + ); + return ( -
    - {relativeBid && relativeAsk && relativeBid > relativeAsk ? ( - <> - {ask} - {bid} - - ) : ( - <> - {bid} - {ask} - - )} +
    + {askBar} + {bidBar} + {volume}
    ); } diff --git a/libs/react-helpers/src/lib/grid/price-cell.tsx b/libs/react-helpers/src/lib/grid/price-cell.tsx index e8200de9c..6a7e22acc 100644 --- a/libs/react-helpers/src/lib/grid/price-cell.tsx +++ b/libs/react-helpers/src/lib/grid/price-cell.tsx @@ -2,10 +2,11 @@ import React from 'react'; export interface IPriceCellProps { value: number | bigint | null | undefined; valueFormatted: string; + testId?: string; } export const PriceCell = React.memo( - ({ value, valueFormatted }: IPriceCellProps) => { + ({ value, valueFormatted, testId }: IPriceCellProps) => { if ( (!value && value !== 0) || (typeof value === 'number' && isNaN(Number(value))) @@ -13,7 +14,10 @@ export const PriceCell = React.memo( return -; } return ( - + {valueFormatted} ); diff --git a/libs/react-helpers/src/lib/grid/price-flash-cell.spec.tsx b/libs/react-helpers/src/lib/grid/price-flash-cell.spec.tsx index ef5738c3c..58619348d 100644 --- a/libs/react-helpers/src/lib/grid/price-flash-cell.spec.tsx +++ b/libs/react-helpers/src/lib/grid/price-flash-cell.spec.tsx @@ -1,6 +1,5 @@ import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import * as React from 'react'; import { PriceFlashCell } from './price-flash-cell'; diff --git a/libs/react-helpers/src/lib/grid/vol-cell.tsx b/libs/react-helpers/src/lib/grid/vol-cell.tsx index 6096eaad4..42e8a4b20 100644 --- a/libs/react-helpers/src/lib/grid/vol-cell.tsx +++ b/libs/react-helpers/src/lib/grid/vol-cell.tsx @@ -1,11 +1,17 @@ import React from 'react'; import type { ICellRendererParams } from 'ag-grid-community'; import { PriceCell } from './price-cell'; +import classNames from 'classnames'; +export enum VolumeType { + bid, + ask, +} export interface VolProps { value: number | bigint | null | undefined; relativeValue?: number; - type: 'bid' | 'ask'; + type: VolumeType; + testId?: string; } export interface IVolCellProps extends ICellRendererParams { value: number | bigint | null | undefined; @@ -15,23 +21,28 @@ export interface IVolCellProps extends ICellRendererParams { export const BID_COLOR = 'darkgreen'; export const ASK_COLOR = 'maroon'; -export const Vol = React.memo(({ value, relativeValue, type }: VolProps) => { - if ((!value && value !== 0) || isNaN(Number(value))) { - return
    -
    ; +export const Vol = React.memo( + ({ value, relativeValue, type, testId }: VolProps) => { + if ((!value && value !== 0) || isNaN(Number(value))) { + return
    -
    ; + } + return ( +
    +
    + +
    + ); } - return ( -
    -
    - -
    - ); -}); +); Vol.displayName = 'Vol'; diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js index 7c3981142..47da20c59 100644 --- a/libs/tailwindcss-config/src/theme.js +++ b/libs/tailwindcss-config/src/theme.js @@ -79,6 +79,7 @@ module.exports = { 0: '0px', 2: '0.125rem', 4: '0.25rem', + 5: '0.3125rem', 8: '0.5rem', 12: '0.75rem', 16: '1rem', diff --git a/libs/ui-toolkit/.storybook/preview.js b/libs/ui-toolkit/.storybook/preview.js index 6d8a1840c..c4a617a09 100644 --- a/libs/ui-toolkit/.storybook/preview.js +++ b/libs/ui-toolkit/.storybook/preview.js @@ -1,6 +1,7 @@ import '../src/styles.scss'; export const parameters = { actions: { argTypesRegex: '^on[A-Z].*' }, + backgrounds: { disable: true }, /*themes: { default: 'dark', list: [ diff --git a/package.json b/package.json index 8791933f7..09e031e50 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "@storybook/addon-a11y": "^6.4.19", "@storybook/addon-essentials": "~6.4.12", "@storybook/builder-webpack5": "~6.4.12", + "@storybook/core-server": "~6.4.12", "@storybook/manager-webpack5": "~6.4.12", "@storybook/react": "~6.4.12", "@svgr/webpack": "^5.4.0", diff --git a/yarn.lock b/yarn.lock index c2da3727e..389e0df8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4158,6 +4158,23 @@ global "^4.4.0" regenerator-runtime "^0.13.7" +"@storybook/addons@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.4.22.tgz#e165407ca132c2182de2d466b7ff7c5644b6ad7b" + integrity sha512-P/R+Jsxh7pawKLYo8MtE3QU/ilRFKbtCewV/T1o5U/gm8v7hKQdFz3YdRMAra4QuCY8bQIp7MKd2HrB5aH5a1A== + dependencies: + "@storybook/api" "6.4.22" + "@storybook/channels" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/router" "6.4.22" + "@storybook/theming" "6.4.22" + "@types/webpack-env" "^1.16.0" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + "@storybook/api@6.4.21", "@storybook/api@^6.0.0": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.21.tgz#efee41ae7bde37f6fe43ee960fef1a261b1b1dd6" @@ -4181,6 +4198,29 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/api@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.22.tgz#d63f7ad3ffdd74af01ae35099bff4c39702cf793" + integrity sha512-lAVI3o2hKupYHXFTt+1nqFct942up5dHH6YD7SZZJGyW21dwKC3HK1IzCsTawq3fZAKkgWFgmOO649hKk60yKg== + dependencies: + "@storybook/channels" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/router" "6.4.22" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.4.22" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^5.3.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/builder-webpack4@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.4.21.tgz#5355ab1bfe7ee153e907d8e64c6088fdb7a95676" @@ -4256,6 +4296,81 @@ webpack-hot-middleware "^2.25.1" webpack-virtual-modules "^0.2.2" +"@storybook/builder-webpack4@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.4.22.tgz#d3384b146e97a2b3a6357c6eb8279ff0f1c7f8f5" + integrity sha512-A+GgGtKGnBneRFSFkDarUIgUTI8pYFdLmUVKEAGdh2hL+vLXAz9A46sEY7C8LQ85XWa8TKy3OTDxqR4+4iWj3A== + dependencies: + "@babel/core" "^7.12.10" + "@babel/plugin-proposal-class-properties" "^7.12.1" + "@babel/plugin-proposal-decorators" "^7.12.12" + "@babel/plugin-proposal-export-default-from" "^7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" + "@babel/plugin-proposal-object-rest-spread" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.12.7" + "@babel/plugin-proposal-private-methods" "^7.12.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.12.1" + "@babel/plugin-transform-block-scoping" "^7.12.12" + "@babel/plugin-transform-classes" "^7.12.1" + "@babel/plugin-transform-destructuring" "^7.12.1" + "@babel/plugin-transform-for-of" "^7.12.1" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-transform-shorthand-properties" "^7.12.1" + "@babel/plugin-transform-spread" "^7.12.1" + "@babel/plugin-transform-template-literals" "^7.12.1" + "@babel/preset-env" "^7.12.11" + "@babel/preset-react" "^7.12.10" + "@babel/preset-typescript" "^7.12.7" + "@storybook/addons" "6.4.22" + "@storybook/api" "6.4.22" + "@storybook/channel-postmessage" "6.4.22" + "@storybook/channels" "6.4.22" + "@storybook/client-api" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/components" "6.4.22" + "@storybook/core-common" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/node-logger" "6.4.22" + "@storybook/preview-web" "6.4.22" + "@storybook/router" "6.4.22" + "@storybook/semver" "^7.3.2" + "@storybook/store" "6.4.22" + "@storybook/theming" "6.4.22" + "@storybook/ui" "6.4.22" + "@types/node" "^14.0.10" + "@types/webpack" "^4.41.26" + autoprefixer "^9.8.6" + babel-loader "^8.0.0" + babel-plugin-macros "^2.8.0" + babel-plugin-polyfill-corejs3 "^0.1.0" + case-sensitive-paths-webpack-plugin "^2.3.0" + core-js "^3.8.2" + css-loader "^3.6.0" + file-loader "^6.2.0" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^4.1.6" + glob "^7.1.6" + glob-promise "^3.4.0" + global "^4.4.0" + html-webpack-plugin "^4.0.0" + pnp-webpack-plugin "1.6.4" + postcss "^7.0.36" + postcss-flexbugs-fixes "^4.2.1" + postcss-loader "^4.2.0" + raw-loader "^4.0.2" + stable "^0.1.8" + style-loader "^1.3.0" + terser-webpack-plugin "^4.2.3" + ts-dedent "^2.0.0" + url-loader "^4.1.1" + util-deprecate "^1.0.2" + webpack "4" + webpack-dev-middleware "^3.7.3" + webpack-filter-warnings-plugin "^1.2.1" + webpack-hot-middleware "^2.25.1" + webpack-virtual-modules "^0.2.2" + "@storybook/builder-webpack5@~6.4.12": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-6.4.21.tgz#d601676083a263a1f03847b12fe2ad1ecd3865bb" @@ -4332,6 +4447,19 @@ qs "^6.10.0" telejson "^5.3.2" +"@storybook/channel-postmessage@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.4.22.tgz#8be0be1ea1e667a49fb0f09cdfdeeb4a45829637" + integrity sha512-gt+0VZLszt2XZyQMh8E94TqjHZ8ZFXZ+Lv/Mmzl0Yogsc2H+6VzTTQO4sv0IIx6xLbpgG72g5cr8VHsxW5kuDQ== + dependencies: + "@storybook/channels" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/core-events" "6.4.22" + core-js "^3.8.2" + global "^4.4.0" + qs "^6.10.0" + telejson "^5.3.2" + "@storybook/channel-websocket@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.4.21.tgz#46db7dbfb9a37907ab12ba2632c46070557b5a97" @@ -4343,6 +4471,17 @@ global "^4.4.0" telejson "^5.3.2" +"@storybook/channel-websocket@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.4.22.tgz#d541f69125873123c453757e2b879a75a9266c65" + integrity sha512-Bm/FcZ4Su4SAK5DmhyKKfHkr7HiHBui6PNutmFkASJInrL9wBduBfN8YQYaV7ztr8ezoHqnYRx8sj28jpwa6NA== + dependencies: + "@storybook/channels" "6.4.22" + "@storybook/client-logger" "6.4.22" + core-js "^3.8.2" + global "^4.4.0" + telejson "^5.3.2" + "@storybook/channels@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.21.tgz#0f1924963f77ec0c3d82aa643a246824ca9f5fca" @@ -4352,6 +4491,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/channels@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.22.tgz#710f732763d63f063f615898ab1afbe74e309596" + integrity sha512-cfR74tu7MLah1A8Rru5sak71I+kH2e/sY6gkpVmlvBj4hEmdZp4Puj9PTeaKcMXh9DgIDPNA5mb8yvQH6VcyxQ== + dependencies: + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/client-api@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.21.tgz#6dcf41a9e55b5e38638cd4d032f1ceaec305e0eb" @@ -4378,6 +4526,32 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/client-api@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.22.tgz#df14f85e7900b94354c26c584bab53a67c47eae9" + integrity sha512-sO6HJNtrrdit7dNXQcZMdlmmZG1k6TswH3gAyP/DoYajycrTwSJ6ovkarzkO+0QcJ+etgra4TEdTIXiGHBMe/A== + dependencies: + "@storybook/addons" "6.4.22" + "@storybook/channel-postmessage" "6.4.22" + "@storybook/channels" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/store" "6.4.22" + "@types/qs" "^6.9.5" + "@types/webpack-env" "^1.16.0" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + synchronous-promise "^2.0.15" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/client-logger@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.21.tgz#7df21cec4d5426669e828af59232ec44ea19c81a" @@ -4386,6 +4560,14 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/client-logger@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.22.tgz#51abedb7d3c9bc21921aeb153ac8a19abc625cd6" + integrity sha512-LXhxh/lcDsdGnK8kimqfhu3C0+D2ylCSPPQNbU0IsLRmTfbpQYMdyl0XBjPdHiRVwlL7Gkw5OMjYemQgJ02zlw== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + "@storybook/components@6.4.21", "@storybook/components@^6.0.0": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.4.21.tgz#77483ef429f96d94cf7d2d8c1af8441ef855a77d" @@ -4416,6 +4598,36 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/components@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.4.22.tgz#4d425280240702883225b6a1f1abde7dc1a0e945" + integrity sha512-dCbXIJF9orMvH72VtAfCQsYbe57OP7fAADtR6YTwfCw9Sm1jFuZr8JbblQ1HcrXEoJG21nOyad3Hm5EYVb/sBw== + dependencies: + "@popperjs/core" "^2.6.0" + "@storybook/client-logger" "6.4.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/theming" "6.4.22" + "@types/color-convert" "^2.0.0" + "@types/overlayscrollbars" "^1.12.0" + "@types/react-syntax-highlighter" "11.0.5" + color-convert "^2.0.1" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + markdown-to-jsx "^7.1.3" + memoizerific "^1.11.3" + overlayscrollbars "^1.13.1" + polished "^4.0.5" + prop-types "^15.7.2" + react-colorful "^5.1.2" + react-popper-tooltip "^3.1.1" + react-syntax-highlighter "^13.5.3" + react-textarea-autosize "^8.3.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/core-client@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.4.21.tgz#4882092315c884dca6118202c83a5e6758b7de57" @@ -4442,6 +4654,32 @@ unfetch "^4.2.0" util-deprecate "^1.0.2" +"@storybook/core-client@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.4.22.tgz#9079eda8a9c8e6ba24b84962a749b1c99668cb2a" + integrity sha512-uHg4yfCBeM6eASSVxStWRVTZrAnb4FT6X6v/xDqr4uXCpCttZLlBzrSDwPBLNNLtCa7ntRicHM8eGKIOD5lMYQ== + dependencies: + "@storybook/addons" "6.4.22" + "@storybook/channel-postmessage" "6.4.22" + "@storybook/channel-websocket" "6.4.22" + "@storybook/client-api" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/preview-web" "6.4.22" + "@storybook/store" "6.4.22" + "@storybook/ui" "6.4.22" + airbnb-js-shims "^2.2.1" + ansi-to-html "^0.6.11" + core-js "^3.8.2" + global "^4.4.0" + lodash "^4.17.21" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + unfetch "^4.2.0" + util-deprecate "^1.0.2" + "@storybook/core-common@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.4.21.tgz#7151eeb5f628bec1dc1461df2de4c51fec15ac4c" @@ -4497,6 +4735,61 @@ util-deprecate "^1.0.2" webpack "4" +"@storybook/core-common@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.4.22.tgz#b00fa3c0625e074222a50be3196cb8052dd7f3bf" + integrity sha512-PD3N/FJXPNRHeQS2zdgzYFtqPLdi3MLwAicbnw+U3SokcsspfsAuyYHZOYZgwO8IAEKy6iCc7TpBdiSJZ/vAKQ== + dependencies: + "@babel/core" "^7.12.10" + "@babel/plugin-proposal-class-properties" "^7.12.1" + "@babel/plugin-proposal-decorators" "^7.12.12" + "@babel/plugin-proposal-export-default-from" "^7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" + "@babel/plugin-proposal-object-rest-spread" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.12.7" + "@babel/plugin-proposal-private-methods" "^7.12.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.12.1" + "@babel/plugin-transform-block-scoping" "^7.12.12" + "@babel/plugin-transform-classes" "^7.12.1" + "@babel/plugin-transform-destructuring" "^7.12.1" + "@babel/plugin-transform-for-of" "^7.12.1" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-transform-shorthand-properties" "^7.12.1" + "@babel/plugin-transform-spread" "^7.12.1" + "@babel/preset-env" "^7.12.11" + "@babel/preset-react" "^7.12.10" + "@babel/preset-typescript" "^7.12.7" + "@babel/register" "^7.12.1" + "@storybook/node-logger" "6.4.22" + "@storybook/semver" "^7.3.2" + "@types/node" "^14.0.10" + "@types/pretty-hrtime" "^1.0.0" + babel-loader "^8.0.0" + babel-plugin-macros "^3.0.1" + babel-plugin-polyfill-corejs3 "^0.1.0" + chalk "^4.1.0" + core-js "^3.8.2" + express "^4.17.1" + file-system-cache "^1.0.5" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.0.4" + fs-extra "^9.0.1" + glob "^7.1.6" + handlebars "^4.7.7" + interpret "^2.2.0" + json5 "^2.1.3" + lazy-universal-dotenv "^3.0.1" + picomatch "^2.3.0" + pkg-dir "^5.0.0" + pretty-hrtime "^1.0.3" + resolve-from "^5.0.0" + slash "^3.0.0" + telejson "^5.3.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + webpack "4" + "@storybook/core-events@6.4.21", "@storybook/core-events@^6.0.0": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.21.tgz#28fff8b10c0d564259edf4439ff8677615ce59c0" @@ -4504,6 +4797,13 @@ dependencies: core-js "^3.8.2" +"@storybook/core-events@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.22.tgz#c09b0571951affd4254028b8958a4d8652700989" + integrity sha512-5GYY5+1gd58Gxjqex27RVaX6qbfIQmJxcbzbNpXGNSqwqAuIIepcV1rdCVm6I4C3Yb7/AQ3cN5dVbf33QxRIwA== + dependencies: + core-js "^3.8.2" + "@storybook/core-server@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.4.21.tgz#3f60c68bb21fd1b07113b2bbaefd6e0498bdbd68" @@ -4552,6 +4852,54 @@ webpack "4" ws "^8.2.3" +"@storybook/core-server@~6.4.12": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.4.22.tgz#254409ec2ba49a78b23f5e4a4c0faea5a570a32b" + integrity sha512-wFh3e2fa0un1d4+BJP+nd3FVWUO7uHTqv3OGBfOmzQMKp4NU1zaBNdSQG7Hz6mw0fYPBPZgBjPfsJRwIYLLZyw== + dependencies: + "@discoveryjs/json-ext" "^0.5.3" + "@storybook/builder-webpack4" "6.4.22" + "@storybook/core-client" "6.4.22" + "@storybook/core-common" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/csf-tools" "6.4.22" + "@storybook/manager-webpack4" "6.4.22" + "@storybook/node-logger" "6.4.22" + "@storybook/semver" "^7.3.2" + "@storybook/store" "6.4.22" + "@types/node" "^14.0.10" + "@types/node-fetch" "^2.5.7" + "@types/pretty-hrtime" "^1.0.0" + "@types/webpack" "^4.41.26" + better-opn "^2.1.1" + boxen "^5.1.2" + chalk "^4.1.0" + cli-table3 "^0.6.1" + commander "^6.2.1" + compression "^1.7.4" + core-js "^3.8.2" + cpy "^8.1.2" + detect-port "^1.3.0" + express "^4.17.1" + file-system-cache "^1.0.5" + fs-extra "^9.0.1" + globby "^11.0.2" + ip "^1.1.5" + lodash "^4.17.21" + node-fetch "^2.6.1" + pretty-hrtime "^1.0.3" + prompts "^2.4.0" + regenerator-runtime "^0.13.7" + serve-favicon "^2.5.0" + slash "^3.0.0" + telejson "^5.3.3" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + watchpack "^2.2.0" + webpack "4" + ws "^8.2.3" + "@storybook/core@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.4.21.tgz#d92a60a6014df5f88902edfe4fadf1cbdd9ba238" @@ -4583,6 +4931,29 @@ regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" +"@storybook/csf-tools@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.4.22.tgz#f6d64bcea1b36114555972acae66a1dbe9e34b5c" + integrity sha512-LMu8MZAiQspJAtMBLU2zitsIkqQv7jOwX7ih5JrXlyaDticH7l2j6Q+1mCZNWUOiMTizj0ivulmUsSaYbpToSw== + dependencies: + "@babel/core" "^7.12.10" + "@babel/generator" "^7.12.11" + "@babel/parser" "^7.12.11" + "@babel/plugin-transform-react-jsx" "^7.12.12" + "@babel/preset-env" "^7.12.11" + "@babel/traverse" "^7.12.11" + "@babel/types" "^7.12.11" + "@mdx-js/mdx" "^1.6.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + core-js "^3.8.2" + fs-extra "^9.0.1" + global "^4.4.0" + js-string-escape "^1.0.1" + lodash "^4.17.21" + prettier ">=2.2.1 <=2.3.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + "@storybook/csf@0.0.2--canary.87bc651.0": version "0.0.2--canary.87bc651.0" resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.2--canary.87bc651.0.tgz#c7b99b3a344117ef67b10137b6477a3d2750cf44" @@ -4632,6 +5003,48 @@ webpack-dev-middleware "^3.7.3" webpack-virtual-modules "^0.2.2" +"@storybook/manager-webpack4@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.4.22.tgz#eabd674beee901c7f755d9b679e9f969cbab636d" + integrity sha512-nzhDMJYg0vXdcG0ctwE6YFZBX71+5NYaTGkxg3xT7gbgnP1YFXn9gVODvgq3tPb3gcRapjyOIxUa20rV+r8edA== + dependencies: + "@babel/core" "^7.12.10" + "@babel/plugin-transform-template-literals" "^7.12.1" + "@babel/preset-react" "^7.12.10" + "@storybook/addons" "6.4.22" + "@storybook/core-client" "6.4.22" + "@storybook/core-common" "6.4.22" + "@storybook/node-logger" "6.4.22" + "@storybook/theming" "6.4.22" + "@storybook/ui" "6.4.22" + "@types/node" "^14.0.10" + "@types/webpack" "^4.41.26" + babel-loader "^8.0.0" + case-sensitive-paths-webpack-plugin "^2.3.0" + chalk "^4.1.0" + core-js "^3.8.2" + css-loader "^3.6.0" + express "^4.17.1" + file-loader "^6.2.0" + file-system-cache "^1.0.5" + find-up "^5.0.0" + fs-extra "^9.0.1" + html-webpack-plugin "^4.0.0" + node-fetch "^2.6.1" + pnp-webpack-plugin "1.6.4" + read-pkg-up "^7.0.1" + regenerator-runtime "^0.13.7" + resolve-from "^5.0.0" + style-loader "^1.3.0" + telejson "^5.3.2" + terser-webpack-plugin "^4.2.3" + ts-dedent "^2.0.0" + url-loader "^4.1.1" + util-deprecate "^1.0.2" + webpack "4" + webpack-dev-middleware "^3.7.3" + webpack-virtual-modules "^0.2.2" + "@storybook/manager-webpack5@~6.4.12": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/manager-webpack5/-/manager-webpack5-6.4.21.tgz#f8f20c03bed8c3911a3678e637feef1d36bb45f5" @@ -4693,6 +5106,17 @@ npmlog "^5.0.1" pretty-hrtime "^1.0.3" +"@storybook/node-logger@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.4.22.tgz#c4ec00f8714505f44eda7671bc88bb44abf7ae59" + integrity sha512-sUXYFqPxiqM7gGH7gBXvO89YEO42nA4gBicJKZjj9e+W4QQLrftjF9l+mAw2K0mVE10Bn7r4pfs5oEZ0aruyyA== + dependencies: + "@types/npmlog" "^4.1.2" + chalk "^4.1.0" + core-js "^3.8.2" + npmlog "^5.0.1" + pretty-hrtime "^1.0.3" + "@storybook/postinstall@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.4.21.tgz#1a0dc4ae0c8bf73fcda3d2abf6f22477dce0a908" @@ -4722,6 +5146,28 @@ unfetch "^4.2.0" util-deprecate "^1.0.2" +"@storybook/preview-web@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/preview-web/-/preview-web-6.4.22.tgz#58bfc6492503ff4265b50f42a27ea8b0bfcf738a" + integrity sha512-sWS+sgvwSvcNY83hDtWUUL75O2l2LY/GTAS0Zp2dh3WkObhtuJ/UehftzPZlZmmv7PCwhb4Q3+tZDKzMlFxnKQ== + dependencies: + "@storybook/addons" "6.4.22" + "@storybook/channel-postmessage" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/store" "6.4.22" + ansi-to-html "^0.6.11" + core-js "^3.8.2" + global "^4.4.0" + lodash "^4.17.21" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + synchronous-promise "^2.0.15" + ts-dedent "^2.0.0" + unfetch "^4.2.0" + util-deprecate "^1.0.2" + "@storybook/react-docgen-typescript-plugin@1.0.2-canary.253f8c1.0": version "1.0.2-canary.253f8c1.0" resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.253f8c1.0.tgz#f2da40e6aae4aa586c2fb284a4a1744602c3c7fa" @@ -4782,6 +5228,23 @@ react-router-dom "^6.0.0" ts-dedent "^2.0.0" +"@storybook/router@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.4.22.tgz#e3cc5cd8595668a367e971efb9695bbc122ed95e" + integrity sha512-zeuE8ZgFhNerQX8sICQYNYL65QEi3okyzw7ynF58Ud6nRw4fMxSOHcj2T+nZCIU5ufozRL4QWD/Rg9P2s/HtLw== + dependencies: + "@storybook/client-logger" "6.4.22" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + history "5.0.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + qs "^6.10.0" + react-router "^6.0.0" + react-router-dom "^6.0.0" + ts-dedent "^2.0.0" + "@storybook/semver@^7.3.2": version "7.3.2" resolved "https://registry.yarnpkg.com/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0" @@ -4827,6 +5290,27 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/store@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/store/-/store-6.4.22.tgz#f291fbe3639f14d25f875cac86abb209a97d4e2a" + integrity sha512-lrmcZtYJLc2emO+1l6AG4Txm9445K6Pyv9cGAuhOJ9Kks0aYe0YtvMkZVVry0RNNAIv6Ypz72zyKc/QK+tZLAQ== + dependencies: + "@storybook/addons" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/csf" "0.0.2--canary.87bc651.0" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + slash "^3.0.0" + stable "^0.1.8" + synchronous-promise "^2.0.15" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/theming@6.4.21", "@storybook/theming@^6.0.0": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.21.tgz#ea1a33be70c654cb31e5b38fae93f72171e88ef8" @@ -4845,6 +5329,24 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" +"@storybook/theming@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.22.tgz#19097eec0366447ddd0d6917b0e0f81d0ec5e51e" + integrity sha512-NVMKH/jxSPtnMTO4VCN1k47uztq+u9fWv4GSnzq/eezxdGg9ceGL4/lCrNGoNajht9xbrsZ4QvsJ/V2sVGM8wA== + dependencies: + "@emotion/core" "^10.1.1" + "@emotion/is-prop-valid" "^0.8.6" + "@emotion/styled" "^10.0.27" + "@storybook/client-logger" "6.4.22" + core-js "^3.8.2" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.27" + global "^4.4.0" + memoizerific "^1.11.3" + polished "^4.0.5" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + "@storybook/ui@6.4.21": version "6.4.21" resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.4.21.tgz#03b0ba66663f70b706ca29481bedf08a468dad3d" @@ -4879,6 +5381,40 @@ resolve-from "^5.0.0" store2 "^2.12.0" +"@storybook/ui@6.4.22": + version "6.4.22" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.4.22.tgz#49badd7994465d78d984ca4c42533c1c22201c46" + integrity sha512-UVjMoyVsqPr+mkS1L7m30O/xrdIEgZ5SCWsvqhmyMUok3F3tRB+6M+OA5Yy+cIVfvObpA7MhxirUT1elCGXsWQ== + dependencies: + "@emotion/core" "^10.1.1" + "@storybook/addons" "6.4.22" + "@storybook/api" "6.4.22" + "@storybook/channels" "6.4.22" + "@storybook/client-logger" "6.4.22" + "@storybook/components" "6.4.22" + "@storybook/core-events" "6.4.22" + "@storybook/router" "6.4.22" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.4.22" + copy-to-clipboard "^3.3.1" + core-js "^3.8.2" + core-js-pure "^3.8.2" + downshift "^6.0.15" + emotion-theming "^10.0.27" + fuse.js "^3.6.1" + global "^4.4.0" + lodash "^4.17.21" + markdown-to-jsx "^7.1.3" + memoizerific "^1.11.3" + polished "^4.0.5" + qs "^6.10.0" + react-draggable "^4.4.3" + react-helmet-async "^1.0.7" + react-sizeme "^3.0.1" + regenerator-runtime "^0.13.7" + resolve-from "^5.0.0" + store2 "^2.12.0" + "@svgr/babel-plugin-add-jsx-attribute@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" From fde5149912b4e456fbe939d8d65b32ea2a8c4a1a Mon Sep 17 00:00:00 2001 From: Elmar <102954831+elmar-vega@users.noreply.github.com> Date: Fri, 10 Jun 2022 14:55:14 +0100 Subject: [PATCH 20/27] feat(console-lite): add trade balance component and hook (#516) * feat(console-lite): add trade balance component and hook * feat(console-lite): review fixes Co-authored-by: Dexter --- .../__generated__/PartyBalanceQuery.ts | 59 ++++++++++++++++++ .../deal-ticket/deal-ticket-balance.tsx | 55 +++++++++++++++++ .../deal-ticket/deal-ticket-container.tsx | 43 +++++++++++++ .../app/hooks/use-settlement-account.spec.tsx | 61 +++++++++++++++++++ .../src/app/hooks/use-settlement-account.ts | 12 ++++ .../mocks/generate-deal-ticket-query.ts | 6 ++ .../src/__generated__/DealTicketQuery.ts | 20 ++++++ .../src/components/deal-ticket-container.tsx | 5 ++ 8 files changed, 261 insertions(+) create mode 100644 apps/simple-trading-app/src/app/components/deal-ticket/__generated__/PartyBalanceQuery.ts create mode 100644 apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-balance.tsx create mode 100644 apps/simple-trading-app/src/app/hooks/use-settlement-account.spec.tsx create mode 100644 apps/simple-trading-app/src/app/hooks/use-settlement-account.ts diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/__generated__/PartyBalanceQuery.ts b/apps/simple-trading-app/src/app/components/deal-ticket/__generated__/PartyBalanceQuery.ts new file mode 100644 index 000000000..b7f15b3c2 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/deal-ticket/__generated__/PartyBalanceQuery.ts @@ -0,0 +1,59 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: PartyBalanceQuery +// ==================================================== + +export interface PartyBalanceQuery_party_accounts_asset { + __typename: "Asset"; + /** + * The id of the asset + */ + id: string; + /** + * The symbol of the asset (e.g: GBP) + */ + symbol: string; + /** + * The full name of the asset (e.g: Great British Pound) + */ + name: string; + /** + * The precision of the asset + */ + decimals: number; +} + +export interface PartyBalanceQuery_party_accounts { + __typename: "Account"; + /** + * Balance as string - current account balance (approx. as balances can be updated several times per second) + */ + balance: string; + /** + * Asset, the 'currency' + */ + asset: PartyBalanceQuery_party_accounts_asset; +} + +export interface PartyBalanceQuery_party { + __typename: "Party"; + /** + * Collateral accounts relating to a party + */ + accounts: PartyBalanceQuery_party_accounts[] | null; +} + +export interface PartyBalanceQuery { + /** + * An entity that is trading on the VEGA network + */ + party: PartyBalanceQuery_party | null; +} + +export interface PartyBalanceQueryVariables { + partyId: string; +} diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-balance.tsx b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-balance.tsx new file mode 100644 index 000000000..e0bd91ef8 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-balance.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import type { DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset } from '@vegaprotocol/deal-ticket'; +import type { PartyBalanceQuery_party_accounts } from './__generated__/PartyBalanceQuery'; +import { useSettlementAccount } from '../../hooks/use-settlement-account'; +import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers'; + +interface DealTicketBalanceProps { + settlementAsset: DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset; + accounts: PartyBalanceQuery_party_accounts[]; + isWalletConnected: boolean; +} + +export const DealTicketBalance = ({ + settlementAsset, + accounts, + isWalletConnected, +}: DealTicketBalanceProps) => { + const settlementAssetId = settlementAsset?.id; + const settlementAssetSymbol = settlementAsset?.symbol; + const settlementAccount = useSettlementAccount(settlementAssetId, accounts); + const formatedNumber = + settlementAccount?.balance && + settlementAccount.asset.decimals && + addDecimalsFormatNumber( + settlementAccount.balance, + settlementAccount.asset.decimals + ); + + const balance = settlementAccount ? ( +

    + {t( + `${formatedNumber} ${settlementAccount.asset.symbol} available to trade` + )} +

    + ) : ( +

    {t(`You have no ${settlementAssetSymbol} available to trade`)}

    + ); + + const connectWallet = ( +

    + {t( + "Please connect your Vega wallet to see your balance for this market's settlement asset" + )} +

    + ); + + return ( +
    +
    + {t('Balance')} + {isWalletConnected ? balance : connectWallet} +
    +
    + ); +}; diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx index aa23ce6d0..0889af306 100644 --- a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx +++ b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx @@ -4,15 +4,58 @@ import { DealTicketContainer as Container, } from '@vegaprotocol/deal-ticket'; import { DealTicketSteps } from './deal-ticket-steps'; +import { useVegaWallet } from '@vegaprotocol/wallet'; +import { gql, useQuery } from '@apollo/client'; +import { DealTicketBalance } from './deal-ticket-balance'; +import * as React from 'react'; +import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery'; const tempEmptyText =

    Please select a market from the markets page

    ; +const PARTY_BALANCE_QUERY = gql` + query PartyBalanceQuery($partyId: ID!) { + party(id: $partyId) { + accounts { + balance + asset { + id + symbol + name + decimals + } + } + } + } +`; + export const DealTicketContainer = () => { const { marketId } = useParams<{ marketId: string }>(); + const { keypair } = useVegaWallet(); + + const { data: partyData, loading } = useQuery( + PARTY_BALANCE_QUERY, + { + variables: { partyId: keypair?.pub }, + skip: !keypair?.pub, + } + ); + return marketId ? ( {(data) => ( + {loading ? ( + 'Loading...' + ) : ( + + )} )} diff --git a/apps/simple-trading-app/src/app/hooks/use-settlement-account.spec.tsx b/apps/simple-trading-app/src/app/hooks/use-settlement-account.spec.tsx new file mode 100644 index 000000000..fceb46475 --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/use-settlement-account.spec.tsx @@ -0,0 +1,61 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useSettlementAccount } from './use-settlement-account'; +import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery'; + +describe('useSettlementAccount Hook', () => { + it('should filter accounts by settlementAssetId', () => { + const accounts: PartyBalanceQuery_party_accounts[] = [ + { + __typename: 'Account', + balance: '2000000000000000000000', + asset: { + __typename: 'Asset', + id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c', + symbol: 'tBTC', + name: 'tBTC TEST', + decimals: 5, + }, + }, + { + __typename: 'Account', + balance: '1000000000', + asset: { + __typename: 'Asset', + id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', + symbol: 'tDAI', + name: 'tDAI TEST', + decimals: 5, + }, + }, + { + __typename: 'Account', + balance: '5000000000000000000', + asset: { + __typename: 'Asset', + id: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55', + symbol: 'VEGA', + name: 'Vega (testnet)', + decimals: 18, + }, + }, + ]; + const settlementAssetId = + '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61'; + + const { result } = renderHook(() => + useSettlementAccount(settlementAssetId, accounts) + ); + expect(result.current?.balance).toBe(accounts[1].balance); + expect(result.current?.asset).toEqual(accounts[1].asset); + }); + + it('should return null if no accounts', () => { + const accounts: PartyBalanceQuery_party_accounts[] = []; + const settlementAssetId = + '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61'; + const { result } = renderHook(() => + useSettlementAccount(settlementAssetId, accounts) + ); + expect(result.current).toBe(undefined); + }); +}); diff --git a/apps/simple-trading-app/src/app/hooks/use-settlement-account.ts b/apps/simple-trading-app/src/app/hooks/use-settlement-account.ts new file mode 100644 index 000000000..4acb04cbb --- /dev/null +++ b/apps/simple-trading-app/src/app/hooks/use-settlement-account.ts @@ -0,0 +1,12 @@ +import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery'; +import { useMemo } from 'react'; + +export const useSettlementAccount = ( + settlementAssetId: string, + accounts: PartyBalanceQuery_party_accounts[] +): PartyBalanceQuery_party_accounts | null => { + const callback = () => + accounts.find((account) => account.asset.id === settlementAssetId); + const account = useMemo(callback, [accounts, settlementAssetId]); + return account as PartyBalanceQuery_party_accounts; +}; diff --git a/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts b/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts index b7ff0b6c9..58f6854d9 100644 --- a/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts +++ b/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts @@ -18,6 +18,12 @@ export const generateDealTicketQuery = ( instrument: { product: { quoteName: 'BTC', + settlementAsset: { + __typename: 'Asset', + id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c', + symbol: 'tBTC', + name: 'tBTC TEST', + }, __typename: 'Future', }, __typename: 'Instrument', diff --git a/libs/deal-ticket/src/__generated__/DealTicketQuery.ts b/libs/deal-ticket/src/__generated__/DealTicketQuery.ts index 5bb0c3d36..610d413c1 100644 --- a/libs/deal-ticket/src/__generated__/DealTicketQuery.ts +++ b/libs/deal-ticket/src/__generated__/DealTicketQuery.ts @@ -9,12 +9,32 @@ import { MarketState, MarketTradingMode } from "@vegaprotocol/types"; // GraphQL query operation: DealTicketQuery // ==================================================== +export interface DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset { + __typename: "Asset"; + /** + * The id of the asset + */ + id: string; + /** + * The symbol of the asset (e.g: GBP) + */ + symbol: string; + /** + * The full name of the asset (e.g: Great British Pound) + */ + name: string; +} + export interface DealTicketQuery_market_tradableInstrument_instrument_product { __typename: "Future"; /** * String representing the quote (e.g. BTCUSD -> USD is quote) */ quoteName: string; + /** + * The name of the asset (string) + */ + settlementAsset: DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset; } export interface DealTicketQuery_market_tradableInstrument_instrument { diff --git a/libs/deal-ticket/src/components/deal-ticket-container.tsx b/libs/deal-ticket/src/components/deal-ticket-container.tsx index 03d7bfc9a..b7c09ff1b 100644 --- a/libs/deal-ticket/src/components/deal-ticket-container.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-container.tsx @@ -21,6 +21,11 @@ const DEAL_TICKET_QUERY = gql` product { ... on Future { quoteName + settlementAsset { + id + symbol + name + } } } } From dbae9623a7ae4c4a3c591d218cc6f4a8f740ef5a Mon Sep 17 00:00:00 2001 From: AndyWhiteVega <106072061+AndyWhiteVega@users.noreply.github.com> Date: Fri, 10 Jun 2022 14:59:26 +0100 Subject: [PATCH 21/27] Task/305 add consolev2 first screen test (#515) * test: extract data via graphql in prep for date order checks * test: linting changes * test: overlay tests expanded * test: linting follow up * test: extra tests and increase timeouts waiting on pr to improve scoping locators to the dialog and checking columns for content * test: linting * test: tweaks required to aid in initial wait * test: linting * test: removed a couple of imports not being used * test: addressing typescript issues * test: linting * chore: type errors * test: tidy up tests after typescript changes * test: quick fix due to multiple links - using first going forward it would be better to have ids for wrappers Co-authored-by: Dexter --- .../src/integration/home-page.feature | 55 ++++++- .../src/support/pages/base-page.ts | 6 +- .../src/support/pages/home-page.ts | 80 ++++++++++ .../support/step_definitions/common.step.ts | 9 +- .../step_definitions/home-page.step.ts | 141 ++++++++++++++++++ .../step_definitions/markets-page.step.ts | 4 + .../src/support/vega-wallet/index.ts | 4 +- 7 files changed, 289 insertions(+), 10 deletions(-) create mode 100644 apps/trading-e2e/src/support/pages/home-page.ts diff --git a/apps/trading-e2e/src/integration/home-page.feature b/apps/trading-e2e/src/integration/home-page.feature index 450ce92b8..940f6b1ff 100644 --- a/apps/trading-e2e/src/integration/home-page.feature +++ b/apps/trading-e2e/src/integration/home-page.feature @@ -2,23 +2,66 @@ Feature: Home page Background: Given I am on the homepage + And I query the server for Open Markets + When the server contains at least 6 open markets - Scenario: Visit Portfolio page + Scenario: Choose market overlay: prompted to choose market + Then I am prompted to select a market + + Scenario: Choose market overlay: oldest currently trading market shown in background by default + Then the oldest current trading market is loaded on the trading tab + + Scenario: Choose market overlay: contains at least 6 listed markets + Then I expect the market overlay table to contain at least 6 rows + + Scenario: Choose market overlay: each listed item reflects an open market + Then each market shown in overlay table exists as open market on server + + Scenario: Choose market overlay: each listed item contains values for last price and change + Then each market shown in overlay table contains content under the last price and change fields + + Scenario: Choose market overlay: oldest trading market appears at top of list + Then the oldest market trading in continous mode shown at top of overlay table + + Scenario: Choose market overlay: can be closed without choosing an option + When I close the dialog form + And the choose market overlay is no longer showing + Then the oldest current trading market is loaded on the trading tab + + Scenario: Choose market overlay: clicking a market name will load that market + When I click the most recent trading market + And the choose market overlay is no longer showing + Then the most recent current trading market is loaded on the trading tab + + Scenario: Choose market overlay: full market list: clicking the full market list shows all open markets + When I click the view full market list + And the choose market overlay is no longer showing + Then each market shown in the full list exists as open market on server + + Scenario: Choose market overlay: full market list: clicking a market name will load that market + When I click the view full market list + And the choose market overlay is no longer showing + When I click the most recent trading market + Then the most recent current trading market is loaded on the trading tab + + Scenario: Navigation: Visit Portfolio page + When I close the dialog form And I navigate to portfolio page - Scenario: Visit Markets page + Scenario: Navigation: Visit Markets page + When I close the dialog form And I navigate to markets page - Scenario: Able to switch public key for connected Vega wallet + Scenario: Vega Wallett Overlay: Able to switch public key for connected Vega wallet Given I connect to Vega Wallet When I open wallet dialog And select a different public key Then public key is switched - Scenario: Unable to connect Vega wallet with incorrect credentials + Scenario: Vega Wallett Overlay: Unable to connect Vega wallet with incorrect credentials When I try to connect Vega wallet with incorrect details Then wallet not running error message is displayed - Scenario: Unable to connect Vega wallet with blank fields + Scenario: Vega Wallett Overlay: Unable to connect Vega wallet with blank fields When I try to connect Vega wallet with blank fields - Then wallet field validation errors are shown + Then wallet field validation errors are shown \ No newline at end of file diff --git a/apps/trading-e2e/src/support/pages/base-page.ts b/apps/trading-e2e/src/support/pages/base-page.ts index 6f62a0d70..c7e0a0b3c 100644 --- a/apps/trading-e2e/src/support/pages/base-page.ts +++ b/apps/trading-e2e/src/support/pages/base-page.ts @@ -17,13 +17,17 @@ export default class BasePage { navigateToPortfolio() { cy.get(`a[href='${this.portfolioUrl}']`) + .first() .should('be.visible') .click({ force: true }); cy.url().should('include', '/portfolio'); } navigateToMarkets() { - cy.get(`a[href='${this.marketsUrl}']`).should('be.visible').click(); + cy.get(`a[href='${this.marketsUrl}']`) + .first() + .should('be.visible') + .click({ force: true }); cy.url().should('include', '/markets'); } diff --git a/apps/trading-e2e/src/support/pages/home-page.ts b/apps/trading-e2e/src/support/pages/home-page.ts new file mode 100644 index 000000000..7c8594e0f --- /dev/null +++ b/apps/trading-e2e/src/support/pages/home-page.ts @@ -0,0 +1,80 @@ +import BasePage from './base-page'; +import type { OpenMarketType } from '../step_definitions/home-page.step'; + +export default class HomePage extends BasePage { + validateStringIsDisplayedAtTopOfTable(value: string) { + // Ignore header row + cy.get('table tr') + .eq(1) + .within(() => cy.contains(value).should('be.visible')); + } + + getOpenMarketsFromServer() { + const query = `{markets{marketTimestamps{open},tradableInstrument{instrument{code,name}}}}`; + return cy + .request({ + method: 'POST', + url: `https://lb.testnet.vega.xyz/query`, + body: { query }, + headers: { 'content-type': 'application/json' }, + }) + .its('body.data.markets'); + } + + getOpenMarketCodes(openMarkets: OpenMarketType[]) { + const openMarketCodes: string[] = []; + openMarkets.forEach((market: OpenMarketType) => { + openMarketCodes.push(market.tradableInstrument.instrument.code); + }); + return openMarketCodes; + } + + getOldestOpenMarket(openMarkets: OpenMarketType[]) { + const [oldestMarket] = openMarkets.sort( + (a, b) => + new Date(a.marketTimestamps.open).getTime() - + new Date(b.marketTimestamps.open).getTime() + ); + if (!oldestMarket) { + throw new Error('Could not find oldest market'); + } + return oldestMarket; + } + + getMostRecentOpenMarket(openMarkets: OpenMarketType[]) { + const [recentMarket] = openMarkets.sort( + (b, a) => + new Date(a.marketTimestamps.open).getTime() - + new Date(b.marketTimestamps.open).getTime() + ); + if (!recentMarket) { + throw new Error('Could not find most recent market'); + } + return recentMarket; + } + + validateTableCodesExistOnServer(openMarketCodes: string[]) { + cy.get('table tr', { timeout: 12000 }).each(($element, index) => { + if (index > 0) { + // skip header row + const openMarketCodeText: string = $element.children().first().text(); + assert.include( + openMarketCodes, + openMarketCodeText, + `Checking ${openMarketCodeText} is shown within server open markets response` + ); + } + }); + } + + validateTableContainsLastPriceAndChange() { + cy.get('table tr').each(($element, index) => { + if (index > 0) { + // skip header row + cy.root().within(() => { + cy.getByTestId('price').should('not.be.empty'); + }); + } + }); + } +} diff --git a/apps/trading-e2e/src/support/step_definitions/common.step.ts b/apps/trading-e2e/src/support/step_definitions/common.step.ts index 7a8318233..19a21d3b9 100644 --- a/apps/trading-e2e/src/support/step_definitions/common.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/common.step.ts @@ -1,4 +1,4 @@ -import { Given } from 'cypress-cucumber-preprocessor/steps'; +import { Given, When } from 'cypress-cucumber-preprocessor/steps'; import { hasOperationName } from '..'; import { generateMarketList } from '../mocks/generate-market-list'; import BasePage from '../pages/base-page'; @@ -14,6 +14,13 @@ Given('I am on the homepage', () => { } }); cy.visit('/'); + cy.getByTestId('market', { timeout: 60000 }).should('be.visible', { + timeout: 20000, + }); + cy.contains('Loading...', { timeout: 20000 }).should('not.exist'); +}); + +When('I close the dialog form', () => { basePage.closeDialog(); }); diff --git a/apps/trading-e2e/src/support/step_definitions/home-page.step.ts b/apps/trading-e2e/src/support/step_definitions/home-page.step.ts index 245683373..c6ec79470 100644 --- a/apps/trading-e2e/src/support/step_definitions/home-page.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/home-page.step.ts @@ -1,7 +1,148 @@ import { Then, When } from 'cypress-cucumber-preprocessor/steps'; import VegaWallet from '../vega-wallet'; +import HomePage from '../pages/home-page'; const vegaWallet = new VegaWallet(); +const homePage = new HomePage(); + +export interface OpenMarketType { + marketTimestamps: { + open: string; + }; + tradableInstrument: { + instrument: { + code: string; + name: string; + }; + }; +} + +When('I query the server for Open Markets', function () { + homePage.getOpenMarketsFromServer().then((openMarkets: OpenMarketType[]) => { + cy.wrap(openMarkets).as('openMarketData'); + }); +}); + +Then('I am prompted to select a market', () => { + cy.contains('Select a market to get started', { timeout: 20000 }).should( + 'be.visible' + ); +}); + +Then('the choose market overlay is no longer showing', () => { + cy.contains('Select a market to get started').should('not.exist'); + cy.contains('Loading...', { timeout: 20000 }).should('not.exist'); +}); + +Then( + 'the server contains at least {int} open markets', + (expectedNumber: number) => { + cy.get('@openMarketData') + .its('length') + .should('be.at.least', expectedNumber); + } +); + +Then( + 'I expect the market overlay table to contain at least {int} rows', + (expectedNumber: number) => { + cy.get('table tr').then((row) => { + expect(row.length).to.be.at.least(expectedNumber); + }); + } +); + +Then( + 'each market shown in overlay table exists as open market on server', + () => { + const arrayOfOpenMarketCodes: string[] = []; + cy.get('@openMarketData') + .each((openMarket: OpenMarketType) => { + arrayOfOpenMarketCodes.push( + openMarket.tradableInstrument.instrument.code + ); + }) + .then(() => { + homePage.validateTableCodesExistOnServer(arrayOfOpenMarketCodes); + }); + } +); + +Then( + 'each market shown in overlay table contains content under the last price and change fields', + () => { + homePage.validateTableContainsLastPriceAndChange(); + } +); + +Then( + 'each market shown in the full list exists as open market on server', + () => { + cy.get('@openMarketData').each((openMarket: OpenMarketType) => { + cy.contains(openMarket.tradableInstrument.instrument.code).should( + 'be.visible' + ); + }); + } +); + +Then( + 'the oldest market trading in continous mode shown at top of overlay table', + () => { + cy.get('@openMarketData').then( + (openMarkets: OpenMarketType[]) => { + const oldestMarket = homePage.getOldestOpenMarket(openMarkets); + homePage.validateStringIsDisplayedAtTopOfTable( + oldestMarket.tradableInstrument.instrument.code + ); + } + ); + } +); + +Then('the oldest current trading market is loaded on the trading tab', () => { + cy.get('@openMarketData').then( + (openMarkets: OpenMarketType[]) => { + const oldestMarket = homePage.getOldestOpenMarket(openMarkets); + cy.getByTestId('market', { timeout: 12000 }).within(() => { + cy.get('button') + .contains(oldestMarket.tradableInstrument.instrument.name) + .should('be.visible'); + }); + } + ); +}); + +Then( + 'the most recent current trading market is loaded on the trading tab', + () => { + cy.get('@openMarketData').then( + (openMarkets: OpenMarketType[]) => { + const latestMarket = homePage.getMostRecentOpenMarket(openMarkets); + cy.getByTestId('market', { timeout: 12000 }).within(() => { + cy.get('button') + .contains(latestMarket.tradableInstrument.instrument.name) + .should('be.visible'); + }); + } + ); + } +); + +When('I click the most recent trading market', () => { + cy.get('@openMarketData').then( + (openMarkets: OpenMarketType[]) => { + const latestMarket = homePage.getMostRecentOpenMarket(openMarkets); + cy.contains(latestMarket.tradableInstrument.instrument.code).click(); + } + ); +}); + +When('I click the view full market list', () => { + cy.contains('Or view full market list').click(); + cy.contains('Loading...').should('be.visible'); + cy.contains('Loading...').should('not.exist'); +}); When('I try to connect Vega wallet with incorrect details', () => { vegaWallet.openVegaWalletConnectDialog(); diff --git a/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts b/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts index 73d3952d2..d691fe63c 100644 --- a/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts @@ -22,6 +22,10 @@ Then('I navigate to markets page', () => { cy.wait('@Markets'); }); +Then('I can view markets', () => { + marketsPage.validateMarketsAreDisplayed(); +}); + Given('I am on the markets page', () => { mockMarkets(); cy.visit('/markets'); diff --git a/apps/trading-e2e/src/support/vega-wallet/index.ts b/apps/trading-e2e/src/support/vega-wallet/index.ts index 3c7d6a4ad..cad94c17c 100644 --- a/apps/trading-e2e/src/support/vega-wallet/index.ts +++ b/apps/trading-e2e/src/support/vega-wallet/index.ts @@ -56,11 +56,11 @@ export default class VegaWallet { } selectPublicKey() { - cy.getByTestId(this.selectPublicKeyBtn).click(); + cy.getByTestId(this.selectPublicKeyBtn).first().click(); } clickOnWalletConnectDialog() { - cy.getByTestId(this.connectVegaBtn).click(); + cy.getByTestId(this.connectVegaBtn).click({ force: true }); } clickDisconnectAllKeys() { From 72f94d2e6dd8e9eba1251987f77b5589f3411c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Fri, 10 Jun 2022 17:07:44 +0200 Subject: [PATCH 22/27] feat(#500): colors fixes (#512) * feat(#500): mute price decimal places * feat(colors): remove alpha chanel from shades of gray * feat(colors): simplify intent colors * feat(colors): fix intent mapping * feat(colors): fix lint issues * feat(colors): fix lint issues --- .../simple-market-list/constants.ts | 24 +++---- .../simple-market-percent-change.tsx | 2 +- .../src/components/app-banner/app-banner.tsx | 2 +- .../locked-progress/locked-progress.tsx | 2 +- .../transaction-requested.tsx | 2 +- apps/token/src/hooks/use-animate-value.ts | 8 +-- apps/token/src/routes/claim/code-used.tsx | 2 +- .../current-proposal-state.tsx | 2 +- .../current-proposal-status.tsx | 2 +- .../components/vote-details/vote-buttons.tsx | 4 +- .../components/vote-details/vote-details.tsx | 6 +- .../home/redemption-information.tsx | 2 +- .../associate/associate-transaction.tsx | 14 +--- .../staking/associate/contract-associate.tsx | 4 +- .../staking/associate/wallet-associate.tsx | 4 +- .../src/routes/tranches/tranche-progress.tsx | 2 +- .../src/routes/tranches/vesting-chart.tsx | 8 +-- apps/token/src/routes/withdraw/index.tsx | 2 +- .../src/components/deal-ticket-manager.tsx | 2 +- libs/market-list/jest.config.js | 1 + .../lib/components/landing/landing-dialog.tsx | 2 +- .../landing/select-market-dialog.tsx | 2 +- .../landing/select-market-list.spec.tsx | 4 +- libs/market-list/src/setup-tests.ts | 1 + libs/market-list/tsconfig.spec.json | 2 +- .../promoted-stats-item.tsx | 8 +-- .../src/components/table-row/table-row.tsx | 8 +-- libs/react-helpers/src/lib/format/number.ts | 7 ++ .../react-helpers/src/lib/grid/flash-cell.tsx | 8 +-- .../react-helpers/src/lib/grid/price-cell.tsx | 13 +++- libs/tailwindcss-config/src/theme.js | 71 ++++++++++--------- .../src/vega-custom-classes.js | 6 +- .../src/components/callout/callout.spec.tsx | 26 +++++-- .../components/callout/callout.stories.tsx | 30 +++----- .../src/components/callout/callout.tsx | 2 +- .../src/components/dialog/dialog.stories.tsx | 2 +- .../indicator/indicator.stories.tsx | 13 ++-- .../src/components/indicator/indicator.tsx | 8 ++- .../components/input-error/input-error.tsx | 8 +-- .../components/lozenge/lozenge.stories.tsx | 13 ++-- .../src/components/lozenge/lozenge.tsx | 6 +- .../price-change/price-change-cell.tsx | 29 ++------ .../components/radio-group/radio-group.tsx | 2 +- .../src/primitives/colors.stories.mdx | 13 ++-- libs/ui-toolkit/src/utils/intent.tsx | 45 +++++------- libs/wallet/src/manage-dialog.tsx | 2 +- libs/wallet/src/rest-connector-form.tsx | 2 +- .../transaction-dialog/transaction-dialog.tsx | 4 +- libs/web3/src/lib/web3-connect-dialog.tsx | 2 +- libs/withdraws/src/lib/withdraw-dialog.tsx | 8 +-- 50 files changed, 215 insertions(+), 227 deletions(-) create mode 100644 libs/market-list/src/setup-tests.ts diff --git a/apps/simple-trading-app/src/app/components/simple-market-list/constants.ts b/apps/simple-trading-app/src/app/components/simple-market-list/constants.ts index f0de9e3fc..dd24e47fb 100644 --- a/apps/simple-trading-app/src/app/components/simple-market-list/constants.ts +++ b/apps/simple-trading-app/src/app/components/simple-market-list/constants.ts @@ -1,15 +1,15 @@ -import { TailwindIntents } from '@vegaprotocol/ui-toolkit'; +import { Intent } from '@vegaprotocol/ui-toolkit'; import { MarketState } from '@vegaprotocol/types'; -export const MARKET_STATUS: Record = { - [MarketState.Active]: TailwindIntents.Success, - [MarketState.Cancelled]: TailwindIntents.Highlight, - [MarketState.Closed]: TailwindIntents.Help, - [MarketState.Pending]: TailwindIntents.Warning, - [MarketState.Proposed]: TailwindIntents.Prompt, - [MarketState.Rejected]: TailwindIntents.Danger, - [MarketState.Settled]: TailwindIntents.Highlight, - [MarketState.Suspended]: TailwindIntents.Warning, - [MarketState.TradingTerminated]: TailwindIntents.Danger, - '': TailwindIntents.Highlight, +export const MARKET_STATUS: Record = { + [MarketState.Active]: Intent.Success, + [MarketState.Cancelled]: Intent.Primary, + [MarketState.Closed]: Intent.None, + [MarketState.Pending]: Intent.Warning, + [MarketState.Proposed]: Intent.Warning, + [MarketState.Rejected]: Intent.Danger, + [MarketState.Settled]: Intent.Primary, + [MarketState.Suspended]: Intent.Warning, + [MarketState.TradingTerminated]: Intent.Danger, + '': Intent.Primary, }; diff --git a/apps/simple-trading-app/src/app/components/simple-market-list/simple-market-percent-change.tsx b/apps/simple-trading-app/src/app/components/simple-market-list/simple-market-percent-change.tsx index 9d1cd2086..ac8efb5f5 100644 --- a/apps/simple-trading-app/src/app/components/simple-market-list/simple-market-percent-change.tsx +++ b/apps/simple-trading-app/src/app/components/simple-market-list/simple-market-percent-change.tsx @@ -43,7 +43,7 @@ const getColor = (change: number | string) => { if (parseFloat(change as string) < 0) { return theme.colors.vega.pink; } - return theme.colors.intent.highlight; + return theme.colors.black[10]; }; const SimpleMarketPercentChangeWrapper = (props: Props) => { diff --git a/apps/token/src/components/app-banner/app-banner.tsx b/apps/token/src/components/app-banner/app-banner.tsx index 25df77d88..f8a24cafd 100644 --- a/apps/token/src/components/app-banner/app-banner.tsx +++ b/apps/token/src/components/app-banner/app-banner.tsx @@ -12,7 +12,7 @@ export const AppBanner = () => { return (

    - + {bannerMessage} diff --git a/apps/token/src/components/locked-progress/locked-progress.tsx b/apps/token/src/components/locked-progress/locked-progress.tsx index 45ffa1d23..3c12cb031 100644 --- a/apps/token/src/components/locked-progress/locked-progress.tsx +++ b/apps/token/src/components/locked-progress/locked-progress.tsx @@ -72,7 +72,7 @@ export const LockedProgress = ({ unlocked, leftLabel, rightLabel, - leftColor = Colors.pink, + leftColor = Colors.vega.pink, rightColor = Colors.green.DEFAULT, light = false, }: LockedProgressProps) => { diff --git a/apps/token/src/components/transaction-callout/transaction-requested.tsx b/apps/token/src/components/transaction-callout/transaction-requested.tsx index f8146559d..8bc58a833 100644 --- a/apps/token/src/components/transaction-callout/transaction-requested.tsx +++ b/apps/token/src/components/transaction-callout/transaction-requested.tsx @@ -6,7 +6,7 @@ export const TransactionRequested = () => { return ( ); diff --git a/apps/token/src/hooks/use-animate-value.ts b/apps/token/src/hooks/use-animate-value.ts index b100dd67e..fd376663c 100644 --- a/apps/token/src/hooks/use-animate-value.ts +++ b/apps/token/src/hooks/use-animate-value.ts @@ -31,9 +31,9 @@ export function useAnimateValue( ) { elRef.current?.animate( [ - { backgroundColor: Colors.red.vega, color: Colors.white.DEFAULT }, + { backgroundColor: Colors.vega.red, color: Colors.white.DEFAULT }, { - backgroundColor: Colors.red.vega, + backgroundColor: Colors.vega.red, color: Colors.white.DEFAULT, offset: 0.8, }, @@ -55,11 +55,11 @@ export function useAnimateValue( elRef.current?.animate( [ { - backgroundColor: Colors.green.vega, + backgroundColor: Colors.vega.green, color: Colors.white.DEFAULT, }, { - backgroundColor: Colors.green.vega, + backgroundColor: Colors.vega.green, color: Colors.white.DEFAULT, offset: 0.8, }, diff --git a/apps/token/src/routes/claim/code-used.tsx b/apps/token/src/routes/claim/code-used.tsx index bf8600581..dad797441 100644 --- a/apps/token/src/routes/claim/code-used.tsx +++ b/apps/token/src/routes/claim/code-used.tsx @@ -6,7 +6,7 @@ import { AddLockedTokenAddress } from '../../components/add-locked-token'; export const CodeUsed = () => { const { t } = useTranslation(); return ( - +

    {t('codeUsedText')}

    diff --git a/apps/token/src/routes/governance/components/current-proposal-state/current-proposal-state.tsx b/apps/token/src/routes/governance/components/current-proposal-state/current-proposal-state.tsx index 3877a591f..93dbaf4cc 100644 --- a/apps/token/src/routes/governance/components/current-proposal-state/current-proposal-state.tsx +++ b/apps/token/src/routes/governance/components/current-proposal-state/current-proposal-state.tsx @@ -14,7 +14,7 @@ export const CurrentProposalState = ({ state === ProposalState.Failed || state === ProposalState.Rejected ) { - className = 'text-intent-danger'; + className = 'text-danger'; } else if ( state === ProposalState.Enacted || state === ProposalState.Passed diff --git a/apps/token/src/routes/governance/components/current-proposal-status/current-proposal-status.tsx b/apps/token/src/routes/governance/components/current-proposal-status/current-proposal-status.tsx index 2936c4d44..1000a146b 100644 --- a/apps/token/src/routes/governance/components/current-proposal-status/current-proposal-status.tsx +++ b/apps/token/src/routes/governance/components/current-proposal-status/current-proposal-status.tsx @@ -11,7 +11,7 @@ const StatusPass = ({ children }: { children: React.ReactNode }) => ( ); const StatusFail = ({ children }: { children: React.ReactNode }) => ( - {children} + {children} ); export const CurrentProposalStatus = ({ diff --git a/apps/token/src/routes/governance/components/vote-details/vote-buttons.tsx b/apps/token/src/routes/governance/components/vote-details/vote-buttons.tsx index 817abf256..7e9f54682 100644 --- a/apps/token/src/routes/governance/components/vote-details/vote-buttons.tsx +++ b/apps/token/src/routes/governance/components/vote-details/vote-buttons.tsx @@ -134,9 +134,7 @@ export const VoteButtons = ({ (voteState === VoteState.Yes || voteState === VoteState.No) ) { const className = - voteState === VoteState.Yes - ? 'text-intent-success' - : 'text-intent-danger'; + voteState === VoteState.Yes ? 'text-success' : 'text-danger'; return (

    {t('youVoted')}:{' '} diff --git a/apps/token/src/routes/governance/components/vote-details/vote-details.tsx b/apps/token/src/routes/governance/components/vote-details/vote-details.tsx index 248ca3ebc..287644684 100644 --- a/apps/token/src/routes/governance/components/vote-details/vote-details.tsx +++ b/apps/token/src/routes/governance/components/vote-details/vote-details.tsx @@ -62,9 +62,7 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => { progress={yesPercentage} /> -

    + @@ -98,7 +96,7 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => { {participationMet ? ( {t('met')} ) : ( - {t('notMet')} + {t('notMet')} )}{' '} {formatNumber(totalTokensVoted, defaultDecimals)}{' '} {formatNumber(totalTokensPercentage, defaultDecimals)}% diff --git a/apps/token/src/routes/redemption/home/redemption-information.tsx b/apps/token/src/routes/redemption/home/redemption-information.tsx index e9029d421..fcaa4931e 100644 --- a/apps/token/src/routes/redemption/home/redemption-information.tsx +++ b/apps/token/src/routes/redemption/home/redemption-information.tsx @@ -131,7 +131,7 @@ export const RedemptionInformation = () => {

    {t('Find out more about Staking.')}

    diff --git a/apps/token/src/routes/staking/associate/associate-transaction.tsx b/apps/token/src/routes/staking/associate/associate-transaction.tsx index a5e4021bd..7ba242296 100644 --- a/apps/token/src/routes/staking/associate/associate-transaction.tsx +++ b/apps/token/src/routes/staking/associate/associate-transaction.tsx @@ -1,10 +1,4 @@ -import { - Button, - Callout, - Link, - Intent, - Loader, -} from '@vegaprotocol/ui-toolkit'; +import { Button, Callout, Link, Loader } from '@vegaprotocol/ui-toolkit'; import { useEnvironment } from '@vegaprotocol/network-switcher'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -67,11 +61,7 @@ export const AssociateTransaction = ({ if (derivedTxState === TxState.Pending) { return ( - } - intent={Intent.Progress} - title={title} - > + } title={title}>

    {t('Associating {{amount}} VEGA tokens with Vega key {{vegaKey}}', { amount, diff --git a/apps/token/src/routes/staking/associate/contract-associate.tsx b/apps/token/src/routes/staking/associate/contract-associate.tsx index 9dcf165fd..9986ae6bc 100644 --- a/apps/token/src/routes/staking/associate/contract-associate.tsx +++ b/apps/token/src/routes/staking/associate/contract-associate.tsx @@ -31,13 +31,13 @@ export const ContractAssociate = ({ let pageContent = null; if (new BigNumber(balanceFormatted).isEqualTo('0')) { pageContent = ( -

    +
    {t('You have no VEGA tokens currently vesting.')}
    ); } else if (new BigNumber(balanceFormatted).minus(lien).isEqualTo('0')) { pageContent = ( -
    +
    {t( 'All VEGA tokens vesting in the connected wallet have already been associated.' )} diff --git a/apps/token/src/routes/staking/associate/wallet-associate.tsx b/apps/token/src/routes/staking/associate/wallet-associate.tsx index bc6a4f974..47f7cb69e 100644 --- a/apps/token/src/routes/staking/associate/wallet-associate.tsx +++ b/apps/token/src/routes/staking/associate/wallet-associate.tsx @@ -81,7 +81,7 @@ export const WalletAssociate = ({ new BigNumber(walletAssociatedBalance || 0).isEqualTo('0') ) { pageContent = ( -
    +
    {t( 'You have no VEGA tokens in your connected wallet. You will need to buy some VEGA tokens from an exchange in order to stake using this method.' )} @@ -92,7 +92,7 @@ export const WalletAssociate = ({ !new BigNumber(walletAssociatedBalance || 0).isEqualTo('0') ) { pageContent = ( -
    +
    {t( 'All VEGA tokens in the connected wallet is already associated with a Vega wallet/key' )} diff --git a/apps/token/src/routes/tranches/tranche-progress.tsx b/apps/token/src/routes/tranches/tranche-progress.tsx index 095e1279e..213f5b66c 100644 --- a/apps/token/src/routes/tranches/tranche-progress.tsx +++ b/apps/token/src/routes/tranches/tranche-progress.tsx @@ -29,7 +29,7 @@ export const TrancheProgress = ({ {t('Locked')} diff --git a/apps/token/src/routes/tranches/vesting-chart.tsx b/apps/token/src/routes/tranches/vesting-chart.tsx index 666919fba..767d38ce5 100644 --- a/apps/token/src/routes/tranches/vesting-chart.tsx +++ b/apps/token/src/routes/tranches/vesting-chart.tsx @@ -33,8 +33,8 @@ export const VestingChart = () => { {[ - ['pink', Colors.pink], - ['green', Colors.green.vega], + ['pink', Colors.vega.pink], + ['green', Colors.vega.green], ['orange', Colors.orange], ['yellow', Colors.yellow.DEFAULT], ].map(([key, color]) => ( @@ -95,7 +95,7 @@ export const VestingChart = () => { dot={false} type="linear" dataKey="team" - stroke={Colors.pink} + stroke={Colors.vega.pink} fill="url(#pink)" yAxisId={0} strokeWidth={2} @@ -107,7 +107,7 @@ export const VestingChart = () => { dot={false} type="monotone" dataKey="earlyInvestors" - stroke={Colors.green.vega} + stroke={Colors.vega.green} fill="url(#green)" yAxisId={0} strokeWidth={2} diff --git a/apps/token/src/routes/withdraw/index.tsx b/apps/token/src/routes/withdraw/index.tsx index 0f063d324..00be00cd4 100644 --- a/apps/token/src/routes/withdraw/index.tsx +++ b/apps/token/src/routes/withdraw/index.tsx @@ -139,7 +139,7 @@ export const WithdrawContainer = ({ currVegaKey }: WithdrawContainerProps) => {

    {t('pendingWithdrawalsCalloutText')}

    diff --git a/libs/deal-ticket/src/components/deal-ticket-manager.tsx b/libs/deal-ticket/src/components/deal-ticket-manager.tsx index 9796acd54..7a4bdc8bd 100644 --- a/libs/deal-ticket/src/components/deal-ticket-manager.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-manager.tsx @@ -41,7 +41,7 @@ export const DealTicketManager = ({ return Intent.Danger; } - return Intent.Progress; + return Intent.None; }; useEffect(() => { diff --git a/libs/market-list/jest.config.js b/libs/market-list/jest.config.js index a5c40cc3b..58b7f31b6 100644 --- a/libs/market-list/jest.config.js +++ b/libs/market-list/jest.config.js @@ -6,4 +6,5 @@ module.exports = { }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/libs/market-list', + setupFilesAfterEnv: ['./src/setup-tests.ts'], }; diff --git a/libs/market-list/src/lib/components/landing/landing-dialog.tsx b/libs/market-list/src/lib/components/landing/landing-dialog.tsx index 1ce64093d..0f870f39e 100644 --- a/libs/market-list/src/lib/components/landing/landing-dialog.tsx +++ b/libs/market-list/src/lib/components/landing/landing-dialog.tsx @@ -26,7 +26,7 @@ export const LandingDialog = ({ open, setOpen }: LandingDialogProps) => {

    setDialogOpen(false)} titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8" diff --git a/libs/market-list/src/lib/components/landing/select-market-list.spec.tsx b/libs/market-list/src/lib/components/landing/select-market-list.spec.tsx index 51482c65a..0290c76a8 100644 --- a/libs/market-list/src/lib/components/landing/select-market-list.spec.tsx +++ b/libs/market-list/src/lib/components/landing/select-market-list.spec.tsx @@ -12,7 +12,7 @@ jest.mock( describe('SelectMarketList', () => { it('should render', () => { - render( + const { container } = render( { ); expect(screen.getByText('AAPL.MF21')).toBeTruthy(); expect(screen.getByText('-3.14%')).toBeTruthy(); - expect(screen.getByText('141.75')).toBeTruthy(); + expect(container).toHaveTextContent(/141\.75/); expect(screen.getByText('Or view full market list')).toBeTruthy(); }); diff --git a/libs/market-list/src/setup-tests.ts b/libs/market-list/src/setup-tests.ts new file mode 100644 index 000000000..7b0828bfa --- /dev/null +++ b/libs/market-list/src/setup-tests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/libs/market-list/tsconfig.spec.json b/libs/market-list/tsconfig.spec.json index 67f149c4c..86a9fa994 100644 --- a/libs/market-list/tsconfig.spec.json +++ b/libs/market-list/tsconfig.spec.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", - "types": ["jest", "node"] + "types": ["jest", "node", "@testing-library/jest-dom"] }, "include": [ "**/*.test.ts", diff --git a/libs/network-stats/src/components/promoted-stats-item/promoted-stats-item.tsx b/libs/network-stats/src/components/promoted-stats-item/promoted-stats-item.tsx index e03eb695d..b8e9f721d 100644 --- a/libs/network-stats/src/components/promoted-stats-item/promoted-stats-item.tsx +++ b/libs/network-stats/src/components/promoted-stats-item/promoted-stats-item.tsx @@ -1,7 +1,7 @@ import { Tooltip } from '@vegaprotocol/ui-toolkit'; import type { StatFields } from '../../config/types'; import { defaultFieldFormatter } from '../table-row'; -import { Card, Indicator, TailwindIntents } from '@vegaprotocol/ui-toolkit'; +import { Card, Indicator, Intent } from '@vegaprotocol/ui-toolkit'; import { useMemo } from 'react'; export const PromotedStatsItem = ({ @@ -16,9 +16,9 @@ export const PromotedStatsItem = ({ () => goodThreshold ? goodThreshold(value) - ? TailwindIntents.Success - : TailwindIntents.Danger - : TailwindIntents.Highlight, + ? Intent.Success + : Intent.Danger + : Intent.Primary, [goodThreshold, value] ); return ( diff --git a/libs/network-stats/src/components/table-row/table-row.tsx b/libs/network-stats/src/components/table-row/table-row.tsx index 13ede5e17..7e5e44af5 100644 --- a/libs/network-stats/src/components/table-row/table-row.tsx +++ b/libs/network-stats/src/components/table-row/table-row.tsx @@ -1,7 +1,7 @@ import { Tooltip } from '@vegaprotocol/ui-toolkit'; import type { StatFields } from '../../config/types'; import { useMemo } from 'react'; -import { Indicator, TailwindIntents } from '@vegaprotocol/ui-toolkit'; +import { Indicator, Intent } from '@vegaprotocol/ui-toolkit'; export const defaultFieldFormatter = (field: unknown) => field === undefined ? 'no data' : field; @@ -18,9 +18,9 @@ export const TableRow = ({ () => goodThreshold ? goodThreshold(value) - ? TailwindIntents.Success - : TailwindIntents.Danger - : TailwindIntents.Highlight, + ? Intent.Success + : Intent.Danger + : Intent.None, [goodThreshold, value] ); return ( diff --git a/libs/react-helpers/src/lib/format/number.ts b/libs/react-helpers/src/lib/format/number.ts index 1a5b5559b..d8be73488 100644 --- a/libs/react-helpers/src/lib/format/number.ts +++ b/libs/react-helpers/src/lib/format/number.ts @@ -39,6 +39,13 @@ export const getNumberFormat = memoize( }) ); +export const getDecimalSeparator = memoize( + () => + getNumberFormat(1) + .formatToParts(1.1) + .find((part) => part.type === 'decimal')?.value +); + export const formatNumber = (rawValue: string | number, formatDecimals = 0) => { return getNumberFormat(formatDecimals).format(Number(rawValue)); }; diff --git a/libs/react-helpers/src/lib/grid/flash-cell.tsx b/libs/react-helpers/src/lib/grid/flash-cell.tsx index 490552f6d..ce7db8c6b 100644 --- a/libs/react-helpers/src/lib/grid/flash-cell.tsx +++ b/libs/react-helpers/src/lib/grid/flash-cell.tsx @@ -71,8 +71,8 @@ export const FlashCell = memo(({ children, value }: FlashCellProps) => { if (value < previousValue) { ref.current?.animate( [ - { color: theme.colors.pink }, - { color: theme.colors.pink, offset: 0.8 }, + { color: theme.colors.vega.pink }, + { color: theme.colors.vega.pink, offset: 0.8 }, { color: 'inherit' }, ], FLASH_DURATION @@ -80,8 +80,8 @@ export const FlashCell = memo(({ children, value }: FlashCellProps) => { } else if (value > previousValue) { ref.current?.animate( [ - { color: theme.colors.green.vega }, - { color: theme.colors.green.vega, offset: 0.8 }, + { color: theme.colors.vega.green }, + { color: theme.colors.vega.green, offset: 0.8 }, { color: 'inherit' }, ], FLASH_DURATION diff --git a/libs/react-helpers/src/lib/grid/price-cell.tsx b/libs/react-helpers/src/lib/grid/price-cell.tsx index 6a7e22acc..8f6f97125 100644 --- a/libs/react-helpers/src/lib/grid/price-cell.tsx +++ b/libs/react-helpers/src/lib/grid/price-cell.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { getDecimalSeparator } from '../format'; export interface IPriceCellProps { value: number | bigint | null | undefined; valueFormatted: string; @@ -13,12 +14,22 @@ export const PriceCell = React.memo( ) { return -; } + const decimalSeparator = getDecimalSeparator(); + const valueSplit = decimalSeparator + ? valueFormatted.split(decimalSeparator) + : [value]; return ( - {valueFormatted} + {valueSplit[0]} + {valueSplit[1] ? decimalSeparator : null} + {valueSplit[1] ? ( + + {valueSplit[1]} + + ) : null} ); } diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js index 47da20c59..92c872bc2 100644 --- a/libs/tailwindcss-config/src/theme.js +++ b/libs/tailwindcss-config/src/theme.js @@ -1,5 +1,11 @@ const defaultTheme = require('tailwindcss/defaultTheme'); +const shadeOfGray = (shade) => { + const decValue = Math.round((255 * shade) / 100); + const hexValue = decValue.toString(16).padStart(2, '0'); + return `#${hexValue}${hexValue}${hexValue}`; +}; + module.exports = { screens: { xs: '500px', @@ -16,64 +22,61 @@ module.exports = { yellow: '#EDFF22', pink: '#FF2D5E', green: '#00F780', + red: '#FF261A', }, red: { DEFAULT: '#ED1515', - transparent: 'rgba(255, 38, 65, 0.3)', - vega: '#FF261A', dark: '#EB001B', + bar: 'rgba(47, 246, 139, 0.45)', // #2FF68B 45% }, green: { DEFAULT: '#26FF8A', - transparent: 'rgba(38, 255, 138, 0.3)', dark: '#008545', - vega: '#00F780', + bar: 'rgba(47, 246, 139, 0.45)', // #2FF68B 45% }, text: '#C7C7C7', deemphasise: '#8A9BA8', white: { DEFAULT: '#FFF', - '02': 'rgba(255, 255, 255, 0.02)', - '05': 'rgba(255, 255, 255, 0.05)', - 10: 'rgba(255, 255, 255, 0.10)', - 25: 'rgba(255, 255, 255, 0.25)', - 40: 'rgba(255, 255, 255, 0.40)', - 60: 'rgba(255, 255, 255, 0.60)', - 80: 'rgba(255, 255, 255, 0.80)', - 95: 'rgba(255, 255, 255, 0.95)', - 100: 'rgba(255, 255, 255, 1.00)', + strong: '#FFF', + normal: '#F5F8FA', + muted: '#676767', + '02': shadeOfGray(2), + '05': shadeOfGray(5), + 10: shadeOfGray(10), + 25: shadeOfGray(25), + 40: shadeOfGray(40), + 60: shadeOfGray(60), + 80: shadeOfGray(80), + 95: shadeOfGray(95), + 100: shadeOfGray(100), }, black: { DEFAULT: '#000', - '02': 'rgba(0, 0, 0, 0.02)', - '05': 'rgba(0, 0, 0, 0.05)', - 10: 'rgba(0, 0, 0, 0.10)', - 25: 'rgba(0, 0, 0, 0.25)', - 40: 'rgba(0, 0, 0, 0.40)', - 60: 'rgba(0, 0, 0, 0.60)', - 80: 'rgba(0, 0, 0, 0.80)', - 95: 'rgba(0, 0, 0, 0.95)', - 100: 'rgba(0, 0, 0, 1)', + strong: '#000', + normal: '#000', + muted: '#BFCCD6', + '02': shadeOfGray(100 - 2), + '05': shadeOfGray(100 - 5), + 10: shadeOfGray(100 - 10), + 25: shadeOfGray(100 - 25), + 40: shadeOfGray(100 - 40), + 60: shadeOfGray(100 - 60), + 80: shadeOfGray(100 - 80), + 95: shadeOfGray(100 - 95), + 100: shadeOfGray(100 - 100), }, blue: '#1DA2FB', coral: '#FF6057', - pink: '#FF2D5E', orange: '#D9822B', yellow: { DEFAULT: '#EDFF22', dark: '#474B0A', // yellow 0.3 opacity on black }, - intent: { - danger: '#FF261A', - warning: '#FF7A1A', - prompt: '#EDFF22', - success: '#26FF8A', - help: '#494949', - highlight: '#E5E5E5', - }, - 'intent-background': { - danger: '#9E0025', // for white text - }, + danger: '#FF261A', + warning: '#FF7A1A', + success: '#26FF8A', + 'danger-bg': '#9E0025', // for white text }, spacing: { 0: '0px', diff --git a/libs/tailwindcss-config/src/vega-custom-classes.js b/libs/tailwindcss-config/src/vega-custom-classes.js index f4f0096dc..b2704dd5e 100644 --- a/libs/tailwindcss-config/src/vega-custom-classes.js +++ b/libs/tailwindcss-config/src/vega-custom-classes.js @@ -13,8 +13,8 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) { overflowX: 'auto', padding: '1em', background: theme.colors.white.DEFAULT, - color: theme.colors.intent.help, - border: '1px solid #696969', + color: theme.colors.black[25], + border: `1px solid #${theme.colors.black[40]}`, }, '.dark .syntax-highlighter-wrapper .hljs': { background: '#2C2C2C', @@ -24,7 +24,7 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) { color: theme.colors.vega.pink, }, '.syntax-highlighter-wrapper .hljs-number': { - color: theme.colors.intent.warning, + color: theme.colors.warning, }, '.syntax-highlighter-wrapper .hljs-string': { color: theme.colors.blue, diff --git a/libs/ui-toolkit/src/components/callout/callout.spec.tsx b/libs/ui-toolkit/src/components/callout/callout.spec.tsx index 39e5594d5..2ac6761c0 100644 --- a/libs/ui-toolkit/src/components/callout/callout.spec.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.spec.tsx @@ -15,17 +15,31 @@ it('renders title and icon', () => { expect(screen.getByText('title')).toBeInTheDocument(); }); -const intents = Object.values(Intent).filter((i) => i !== Intent.Progress); +it(`Applies class for success intent`, () => { + render(); + expect(screen.getByTestId('callout')).toHaveClass('shadow-danger'); +}); -it.each(intents)('Applies class for %s', (intent) => { - render(); +it(`Applies class for warning intent`, () => { + render(); + expect(screen.getByTestId('callout')).toHaveClass('shadow-warning'); +}); + +it(`Applies class for danger intent`, () => { + render(); + expect(screen.getByTestId('callout')).toHaveClass('shadow-danger'); +}); + +it(`Applies class for primary intent`, () => { + render(); expect(screen.getByTestId('callout')).toHaveClass( - `shadow-intent-${intent.toLowerCase()}` + 'shadow-vega-pink', + 'dark:shadow-vega-yellow' ); }); -it(`Applies class for progress`, () => { - render(); +it(`Applies class for none intent`, () => { + render(); expect(screen.getByTestId('callout')).toHaveClass( 'shadow-black', 'dark:shadow-white' diff --git a/libs/ui-toolkit/src/components/callout/callout.stories.tsx b/libs/ui-toolkit/src/components/callout/callout.stories.tsx index b99a6ece9..aaace79dc 100644 --- a/libs/ui-toolkit/src/components/callout/callout.stories.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.stories.tsx @@ -24,6 +24,12 @@ Default.args = { children: 'Content', }; +export const Primary = Template.bind({}); +Primary.args = { + intent: Intent.Primary, + children: 'Content', +}; + export const Danger = Template.bind({}); Danger.args = { intent: Intent.Danger, @@ -36,33 +42,15 @@ Warning.args = { children: 'Content', }; -export const Prompt = Template.bind({}); -Prompt.args = { - intent: Intent.Prompt, - children: 'Content', -}; - -export const Progress = Template.bind({}); -Progress.args = { - intent: Intent.Progress, - children: 'Content', -}; - export const Success = Template.bind({}); Success.args = { intent: Intent.Success, children: 'Content', }; -export const Help = Template.bind({}); -Help.args = { - intent: Intent.Help, - children: 'Content', -}; - export const IconAndContent = Template.bind({}); IconAndContent.args = { - intent: Intent.Help, + intent: Intent.None, title: 'This is what this thing does', iconName: 'endorsed', children: ( @@ -77,7 +65,7 @@ IconAndContent.args = { export const CustomIconAndContent = Template.bind({}); CustomIconAndContent.args = { - intent: Intent.Help, + intent: Intent.None, title: 'This is what this thing does', icon: ( @@ -96,7 +84,7 @@ CustomIconAndContent.args = { export const Loading = Template.bind({}); Loading.args = { - intent: Intent.Help, + intent: Intent.None, title: 'This is what this thing does', isLoading: true, children: ( diff --git a/libs/ui-toolkit/src/components/callout/callout.tsx b/libs/ui-toolkit/src/components/callout/callout.tsx index 218e3082c..a226cb941 100644 --- a/libs/ui-toolkit/src/components/callout/callout.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.tsx @@ -77,7 +77,7 @@ export function Callout({ iconName, iconDescription, isLoading, - intent = Intent.Help, + intent = Intent.None, headingLevel, }: CalloutProps) { const iconElement = getIconElement({ diff --git a/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx b/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx index 2a288c534..968e276ba 100644 --- a/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx +++ b/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx @@ -56,5 +56,5 @@ Modal.args = { open: false, title: 'Modal (Prompt)', children:

    Some content

    , - intent: Intent.Prompt, + intent: Intent.Warning, }; diff --git a/libs/ui-toolkit/src/components/indicator/indicator.stories.tsx b/libs/ui-toolkit/src/components/indicator/indicator.stories.tsx index 248fedaa5..9482bfc02 100644 --- a/libs/ui-toolkit/src/components/indicator/indicator.stories.tsx +++ b/libs/ui-toolkit/src/components/indicator/indicator.stories.tsx @@ -1,5 +1,6 @@ import type { Story, Meta } from '@storybook/react'; import { Indicator } from './indicator'; +import { Intent } from '../../utils/intent'; export default { component: Indicator, @@ -10,22 +11,22 @@ const Template: Story = (args) => ; export const Default = Template.bind({}); -export const Highlight = Template.bind({}); -Highlight.args = { - variant: 'highlight', +export const Primary = Template.bind({}); +Primary.args = { + variant: Intent.Primary, }; export const Success = Template.bind({}); Success.args = { - variant: 'success', + variant: Intent.Success, }; export const Warning = Template.bind({}); Warning.args = { - variant: 'warning', + variant: Intent.Warning, }; export const Danger = Template.bind({}); Danger.args = { - variant: 'danger', + variant: Intent.Danger, }; diff --git a/libs/ui-toolkit/src/components/indicator/indicator.tsx b/libs/ui-toolkit/src/components/indicator/indicator.tsx index 11354686e..6b3221cdf 100644 --- a/libs/ui-toolkit/src/components/indicator/indicator.tsx +++ b/libs/ui-toolkit/src/components/indicator/indicator.tsx @@ -1,8 +1,12 @@ import classNames from 'classnames'; -import type { TailwindIntents } from '../../utils/intent'; +import { Intent } from '../../utils/intent'; import { getVariantBackground } from '../../utils/intent'; -export const Indicator = ({ variant }: { variant?: TailwindIntents }) => { +interface IndicatorProps { + variant?: Intent; +} + +export const Indicator = ({ variant = Intent.None }: IndicatorProps) => { const names = classNames( 'inline-block w-8 h-8 mb-2 mr-8 rounded', getVariantBackground(variant) diff --git a/libs/ui-toolkit/src/components/input-error/input-error.tsx b/libs/ui-toolkit/src/components/input-error/input-error.tsx index 44f70d078..d691b3d12 100644 --- a/libs/ui-toolkit/src/components/input-error/input-error.tsx +++ b/libs/ui-toolkit/src/components/input-error/input-error.tsx @@ -27,14 +27,14 @@ export const InputError = ({ 'text-ui', ], { - 'border-intent-danger': intent === 'danger', - 'border-intent-warning': intent === 'warning', + 'border-danger': intent === 'danger', + 'border-warning': intent === 'warning', }, className ); const iconClassName = classNames(['mx-8'], { - 'fill-intent-danger': intent === 'danger', - 'fill-intent-warning': intent === 'warning', + 'fill-danger': intent === 'danger', + 'fill-warning': intent === 'warning', }); return (
    lozenge; export const Default = Template.bind({}); -export const Highlight = Template.bind({}); -Highlight.args = { - variant: 'highlight', +export const Primary = Template.bind({}); +Primary.args = { + variant: Intent.Primary, }; export const Success = Template.bind({}); Success.args = { - variant: 'success', + variant: Intent.Success, }; export const Warning = Template.bind({}); Warning.args = { - variant: 'warning', + variant: Intent.Warning, }; export const Danger = Template.bind({}); Danger.args = { - variant: 'danger', + variant: Intent.Danger, }; diff --git a/libs/ui-toolkit/src/components/lozenge/lozenge.tsx b/libs/ui-toolkit/src/components/lozenge/lozenge.tsx index 4a697cbc7..ec487ac07 100644 --- a/libs/ui-toolkit/src/components/lozenge/lozenge.tsx +++ b/libs/ui-toolkit/src/components/lozenge/lozenge.tsx @@ -1,11 +1,11 @@ import type { ReactNode } from 'react'; import classNames from 'classnames'; import { getVariantBackground } from '../../utils/intent'; -import { TailwindIntents } from '../../utils/intent'; +import { Intent } from '../../utils/intent'; interface LozengeProps { children: ReactNode; - variant?: TailwindIntents; + variant?: Intent; className?: string; } @@ -22,7 +22,7 @@ const getLozengeClasses = ( export const Lozenge = ({ children, - variant = TailwindIntents.Highlight, + variant = Intent.None, className, }: LozengeProps) => { return ( diff --git a/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx b/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx index 91aa77027..152a75115 100644 --- a/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx +++ b/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx @@ -1,7 +1,6 @@ import { addDecimalsFormatNumber, formatNumberPercentage, - PriceCell, } from '@vegaprotocol/react-helpers'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -55,32 +54,16 @@ export const PriceCellChange = React.memo( )} flex items-center gap-4 justify-end`} > - + - { - - } + {formatNumberPercentage( + new BigNumber(changePercentage.toString()), + 2 + )}   - ( - { - - } - ) + {addDecimalsFormatNumber(change.toString(), decimalPlaces ?? 0, 3)} diff --git a/libs/ui-toolkit/src/components/radio-group/radio-group.tsx b/libs/ui-toolkit/src/components/radio-group/radio-group.tsx index 9cd1818cd..85329e31a 100644 --- a/libs/ui-toolkit/src/components/radio-group/radio-group.tsx +++ b/libs/ui-toolkit/src/components/radio-group/radio-group.tsx @@ -41,7 +41,7 @@ export const Radio = ({ id, value, label, disabled, hasError }: RadioProps) => { 'dark:bg-white-25', { 'border-black-60 dark:border-white-60': !hasError, - 'border-intent-danger dark:border-intent-danger': hasError, + 'border-danger dark:border-danger': hasError, } ); return ( diff --git a/libs/ui-toolkit/src/primitives/colors.stories.mdx b/libs/ui-toolkit/src/primitives/colors.stories.mdx index d8d59770e..ebf2275ca 100644 --- a/libs/ui-toolkit/src/primitives/colors.stories.mdx +++ b/libs/ui-toolkit/src/primitives/colors.stories.mdx @@ -53,12 +53,11 @@ full colour palette [here](https://tailwindcss.com/docs/customizing-colors/#defa - @@ -71,7 +70,7 @@ full colour palette [here](https://tailwindcss.com/docs/customizing-colors/#defa colors={{ black: theme.colors.black.DEFAULT, white: theme.colors.white.DEFAULT, - danger: theme.colors['intent-background'].danger, + danger: theme.colors['danger-bg'], }} /> diff --git a/libs/ui-toolkit/src/utils/intent.tsx b/libs/ui-toolkit/src/utils/intent.tsx index 6eda6777b..4a9845eaa 100644 --- a/libs/ui-toolkit/src/utils/intent.tsx +++ b/libs/ui-toolkit/src/utils/intent.tsx @@ -1,40 +1,29 @@ export enum Intent { - Danger = 'danger', - Warning = 'warning', - Prompt = 'prompt', - Progress = 'progress', - Success = 'success', - Help = 'help', -} - -export enum TailwindIntents { - Danger = 'danger', - Warning = 'warning', - Prompt = 'prompt', - Success = 'success', - Help = 'help', - Highlight = 'highlight', + None, + Primary, + Success, + Warning, + Danger, } export const getIntentShadow = (intent?: Intent) => { return { 'shadow-callout': true, - 'shadow-intent-danger': intent === Intent.Danger, - 'shadow-intent-warning': intent === Intent.Warning, - 'shadow-intent-prompt': intent === Intent.Prompt, - 'shadow-black dark:shadow-white': intent === Intent.Progress, - 'shadow-intent-success': intent === Intent.Success, - 'shadow-intent-help': intent === Intent.Help, + 'shadow-danger': intent === Intent.Danger, + 'shadow-warning': intent === Intent.Warning, + 'shadow-success': intent === Intent.Success, + 'shadow-black dark:shadow-white': intent === Intent.None, + 'shadow-vega-pink dark:shadow-vega-yellow': intent === Intent.Primary, }; }; -export const getVariantBackground = (variant?: TailwindIntents) => { +export const getVariantBackground = (variant?: Intent) => { return { - 'bg-intent-danger text-white': variant === TailwindIntents.Danger, - 'bg-intent-warning text-black': variant === TailwindIntents.Warning, - 'bg-intent-prompt text-black': variant === TailwindIntents.Prompt, - 'bg-intent-success text-black': variant === TailwindIntents.Success, - 'bg-intent-help text-white': variant === TailwindIntents.Help, - 'bg-intent-highlight text-black': variant === TailwindIntents.Highlight, + 'bg-black dark:bg-white': variant === Intent.None, + 'bg-vega-pink text-black dark:bg-vega-yellow dark:text-black-normal': + variant === Intent.Primary, + 'bg-danger text-white': variant === Intent.Danger, + 'bg-warning text-black': variant === Intent.Warning, + 'bg-success text-black': variant === Intent.Success, }; }; diff --git a/libs/wallet/src/manage-dialog.tsx b/libs/wallet/src/manage-dialog.tsx index ed6b8c3fa..edd2e183d 100644 --- a/libs/wallet/src/manage-dialog.tsx +++ b/libs/wallet/src/manage-dialog.tsx @@ -23,7 +23,7 @@ export const VegaManageDialog = ({ title={t('SELECT A VEGA KEY')} open={dialogOpen} onChange={setDialogOpen} - intent={Intent.Prompt} + intent={Intent.Warning} >
    {keypairs ? ( diff --git a/libs/wallet/src/rest-connector-form.tsx b/libs/wallet/src/rest-connector-form.tsx index c51887d0f..097c045fd 100644 --- a/libs/wallet/src/rest-connector-form.tsx +++ b/libs/wallet/src/rest-connector-form.tsx @@ -82,7 +82,7 @@ export function RestConnectorForm({ )} {error && ( -

    +

    {error}

    )} diff --git a/libs/web3/src/lib/transaction-dialog/transaction-dialog.tsx b/libs/web3/src/lib/transaction-dialog/transaction-dialog.tsx index 08f6ba833..83c397d75 100644 --- a/libs/web3/src/lib/transaction-dialog/transaction-dialog.tsx +++ b/libs/web3/src/lib/transaction-dialog/transaction-dialog.tsx @@ -66,12 +66,12 @@ export const TransactionDialog = ({ [EthTxStatus.Requested]: { title: t('Confirm transaction'), icon: , - intent: Intent.Prompt, + intent: Intent.Warning, }, [EthTxStatus.Pending]: { title: t(`${name} pending`), icon: , - intent: Intent.Progress, + intent: Intent.None, }, [EthTxStatus.Complete]: { title: t(`${name} complete`), diff --git a/libs/web3/src/lib/web3-connect-dialog.tsx b/libs/web3/src/lib/web3-connect-dialog.tsx index 4343d8b37..d79977838 100644 --- a/libs/web3/src/lib/web3-connect-dialog.tsx +++ b/libs/web3/src/lib/web3-connect-dialog.tsx @@ -22,7 +22,7 @@ export const Web3ConnectDialog = ({
      diff --git a/libs/withdraws/src/lib/withdraw-dialog.tsx b/libs/withdraws/src/lib/withdraw-dialog.tsx index bf79e27e0..f7e1fc4fd 100644 --- a/libs/withdraws/src/lib/withdraw-dialog.tsx +++ b/libs/withdraws/src/lib/withdraw-dialog.tsx @@ -108,13 +108,13 @@ const getProps = ( [VegaTxStatus.Requested]: { title: t('Confirm withdrawal'), icon: , - intent: Intent.Prompt, + intent: Intent.Warning, children: Confirm withdrawal in Vega wallet, }, [VegaTxStatus.Pending]: { title: t('Withdrawal transaction pending'), icon: , - intent: Intent.Progress, + intent: Intent.None, children: Awaiting transaction, }, }; @@ -139,13 +139,13 @@ const getProps = ( [EthTxStatus.Requested]: { title: t('Confirm transaction'), icon: , - intent: Intent.Prompt, + intent: Intent.Warning, children: {t('Confirm transaction in wallet')}, }, [EthTxStatus.Pending]: { title: t('Ethereum transaction pending'), icon: , - intent: Intent.Progress, + intent: Intent.None, children: ( From 9941c9bfaa3566f077fe358e87cb334d6a915156 Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Fri, 10 Jun 2022 12:00:02 -0700 Subject: [PATCH 23/27] Fix/trading app tests (#539) * fix: deposits tests, also convert to basic cypress * add new home tests which test redirect to trading page and markets page * chore: replace portfolio page feature with raw cypress * chore: replace market page feature with raw cypress tests * chore: replace home page tests with global.ts for wallet connections * chore: add raw cypress withdrawals tests with mocks * fix: complete withdrawals prompt and add assertion for it * chore: remove unnecessary cypress envs now that we are mocking assets * chore: ignore lint errors temporarily * chore: add mock for deposit page query, add wait for mocked queries to resolve * fix: order of waiting for withdraw page query * fix: validate vega wallet connection * chore: remove rest of page objects and convert trading page feature to regular cypress * fix: assertion on transaction dialog after withdrawal * chore: split withdraw and withdrawals pages into separate files * chore: split trading tests into own files, connect wallet once for deal ticket * feat: convert home page tests to raw cypress --- apps/trading-e2e/cypress.json | 2 - apps/trading-e2e/src/integration/deposit.ts | 60 +++++ .../src/integration/deposits.feature | 154 ----------- apps/trading-e2e/src/integration/global.ts | 78 ++++++ .../src/integration/home-page.feature | 67 ----- apps/trading-e2e/src/integration/home.ts | 171 ++++++++++++ .../src/integration/markets-page.feature | 19 -- apps/trading-e2e/src/integration/markets.ts | 92 +++++++ .../src/integration/portfolio-page.feature | 5 - apps/trading-e2e/src/integration/portfolio.ts | 6 + .../src/integration/trading-accounts.ts | 38 +++ .../src/integration/trading-deal-ticket.ts | 166 ++++++++++++ .../src/integration/trading-orders.ts | 69 +++++ .../src/integration/trading-page.feature | 127 --------- .../src/integration/trading-positions.ts | 35 +++ .../src/integration/trading-trades.ts | 34 +++ apps/trading-e2e/src/integration/withdraw.ts | 97 +++++++ .../src/integration/withdrawals.feature | 68 ----- .../src/integration/withdrawals.ts | 82 ++++++ .../src/support/ethereum-wallet.ts | 5 + .../ethereum-wallet/ethereum-wallet.ts | 22 -- .../src/support/ethereum-wallet/index.ts | 1 - .../src/support/mocks/generate-candles.ts | 2 +- .../mocks/generate-deal-ticket-query.ts | 4 +- .../support/mocks/generate-deposit-page.ts | 36 +++ .../src/support/mocks/generate-market-list.ts | 4 +- .../src/support/mocks/generate-market.ts | 2 +- .../support/mocks/generate-markets-landing.ts | 51 ++++ .../src/support/mocks/generate-markets.ts | 4 +- .../src/support/mocks/generate-trades.ts | 2 +- .../mocks/generate-withdraw-page-query.ts | 60 +++++ .../src/support/mocks/generate-withdrawals.ts | 58 +++++ .../src/support/pages/base-page.ts | 57 ---- .../src/support/pages/deposits-page.ts | 55 ---- .../src/support/pages/home-page.ts | 80 ------ .../src/support/pages/markets-page.ts | 60 ----- .../src/support/pages/portfolio-page.ts | 44 ---- .../src/support/pages/trading-page.ts | 41 --- .../src/support/pages/withdrawals-page.ts | 94 ------- .../support/step_definitions/common.step.ts | 29 --- .../step_definitions/deal-ticket.step.ts | 52 ---- .../support/step_definitions/deposits.step.ts | 71 ----- .../step_definitions/home-page.step.ts | 164 ------------ .../step_definitions/markets-page.step.ts | 45 ---- .../step_definitions/portfolio-page.step.ts | 8 - .../step_definitions/trading-page.step.ts | 245 ------------------ .../step_definitions/vega-wallet.step.ts | 56 ---- .../step_definitions/withdrawals.step.ts | 117 --------- .../support/trading-windows/accounts-list.ts | 44 ---- .../support/trading-windows/deal-ticket.ts | 122 --------- .../support/trading-windows/orders-list.ts | 37 --- .../support/trading-windows/positions-list.ts | 18 -- .../support/trading-windows/trades-list.ts | 23 -- apps/trading-e2e/src/support/trading.ts | 70 +++++ apps/trading-e2e/src/support/vega-wallet.ts | 13 + .../src/support/vega-wallet/index.ts | 69 ----- .../components/grid-tabs/grid-tabs.tsx | 8 +- .../vega-wallet-connect-button.tsx | 2 +- .../web3-container/web3-container.tsx | 2 +- .../trading/pages/markets/[marketId].page.tsx | 8 +- apps/trading/pages/portfolio/index.page.tsx | 2 +- .../withdraw/withdraw-page-container.tsx | 7 +- .../portfolio/withdrawals/index.page.tsx | 5 +- .../components/landing/select-market-list.tsx | 5 +- libs/wallet/src/rest-connector-form.tsx | 6 +- 65 files changed, 1259 insertions(+), 2021 deletions(-) create mode 100644 apps/trading-e2e/src/integration/deposit.ts delete mode 100644 apps/trading-e2e/src/integration/deposits.feature create mode 100644 apps/trading-e2e/src/integration/global.ts delete mode 100644 apps/trading-e2e/src/integration/home-page.feature create mode 100644 apps/trading-e2e/src/integration/home.ts delete mode 100644 apps/trading-e2e/src/integration/markets-page.feature create mode 100644 apps/trading-e2e/src/integration/markets.ts delete mode 100644 apps/trading-e2e/src/integration/portfolio-page.feature create mode 100644 apps/trading-e2e/src/integration/portfolio.ts create mode 100644 apps/trading-e2e/src/integration/trading-accounts.ts create mode 100644 apps/trading-e2e/src/integration/trading-deal-ticket.ts create mode 100644 apps/trading-e2e/src/integration/trading-orders.ts delete mode 100644 apps/trading-e2e/src/integration/trading-page.feature create mode 100644 apps/trading-e2e/src/integration/trading-positions.ts create mode 100644 apps/trading-e2e/src/integration/trading-trades.ts create mode 100644 apps/trading-e2e/src/integration/withdraw.ts delete mode 100644 apps/trading-e2e/src/integration/withdrawals.feature create mode 100644 apps/trading-e2e/src/integration/withdrawals.ts create mode 100644 apps/trading-e2e/src/support/ethereum-wallet.ts delete mode 100644 apps/trading-e2e/src/support/ethereum-wallet/ethereum-wallet.ts delete mode 100644 apps/trading-e2e/src/support/ethereum-wallet/index.ts create mode 100644 apps/trading-e2e/src/support/mocks/generate-deposit-page.ts create mode 100644 apps/trading-e2e/src/support/mocks/generate-markets-landing.ts create mode 100644 apps/trading-e2e/src/support/mocks/generate-withdraw-page-query.ts create mode 100644 apps/trading-e2e/src/support/mocks/generate-withdrawals.ts delete mode 100644 apps/trading-e2e/src/support/pages/base-page.ts delete mode 100644 apps/trading-e2e/src/support/pages/deposits-page.ts delete mode 100644 apps/trading-e2e/src/support/pages/home-page.ts delete mode 100644 apps/trading-e2e/src/support/pages/markets-page.ts delete mode 100644 apps/trading-e2e/src/support/pages/portfolio-page.ts delete mode 100644 apps/trading-e2e/src/support/pages/trading-page.ts delete mode 100644 apps/trading-e2e/src/support/pages/withdrawals-page.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/common.step.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/deal-ticket.step.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/deposits.step.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/home-page.step.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/markets-page.step.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/portfolio-page.step.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/trading-page.step.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/vega-wallet.step.ts delete mode 100644 apps/trading-e2e/src/support/step_definitions/withdrawals.step.ts delete mode 100644 apps/trading-e2e/src/support/trading-windows/accounts-list.ts delete mode 100644 apps/trading-e2e/src/support/trading-windows/deal-ticket.ts delete mode 100644 apps/trading-e2e/src/support/trading-windows/orders-list.ts delete mode 100644 apps/trading-e2e/src/support/trading-windows/positions-list.ts delete mode 100644 apps/trading-e2e/src/support/trading-windows/trades-list.ts create mode 100644 apps/trading-e2e/src/support/trading.ts create mode 100644 apps/trading-e2e/src/support/vega-wallet.ts delete mode 100644 apps/trading-e2e/src/support/vega-wallet/index.ts diff --git a/apps/trading-e2e/cypress.json b/apps/trading-e2e/cypress.json index 32abc4f87..14765f4c1 100644 --- a/apps/trading-e2e/cypress.json +++ b/apps/trading-e2e/cypress.json @@ -23,10 +23,8 @@ "VEGA_PUBLIC_KEY2": "1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4", "TRUNCATED_VEGA_PUBLIC_KEY": "47836c…c7d278", "TRUNCATED_VEGA_PUBLIC_KEY2": "1a18cd…0cf2e4", - "INVALID_DEPOSIT_TO_ADDRESS": "zzz85edfa7ffdb6ed996ca912e9258998e47bf3515c885cf3c63fb56b15de36f", "ETHEREUM_WALLET_ADDRESS": "0x265Cc6d39a1B53d0d92068443009eE7410807158", "ETHERSCAN_URL": "https://ropsten.etherscan.io", - "WITHDRAWAL_ASSET_ID": "tEURO TEST", "tsConfig": "tsconfig.json", "TAGS": "not @todo and not @ignore and not @manual" } diff --git a/apps/trading-e2e/src/integration/deposit.ts b/apps/trading-e2e/src/integration/deposit.ts new file mode 100644 index 000000000..0acf227ab --- /dev/null +++ b/apps/trading-e2e/src/integration/deposit.ts @@ -0,0 +1,60 @@ +import { hasOperationName } from '../support'; +import { generateDepositPage } from '../support/mocks/generate-deposit-page'; + +describe('deposit form validation', () => { + beforeEach(() => { + cy.mockWeb3Provider(); + cy.mockGQL('DepositPage', (req) => { + if (hasOperationName(req, 'DepositPage')) { + req.reply({ + body: { data: generateDepositPage() }, + }); + } + }); + cy.visit('/portfolio/deposit'); + + // Deposit page requires connection Ethereum wallet first + cy.getByTestId('connect-eth-wallet-btn').click(); + cy.getByTestId('web3-connector-MetaMask').click(); + + cy.wait('@DepositPage'); + cy.contains('Deposit'); + }); + + it('handles empty fields', () => { + const assetSelectField = 'select[name="asset"]'; + const toAddressField = 'input[name="to"]'; + const amountField = 'input[name="amount"]'; + const formFieldError = 'input-error-text'; + + // Submit form to trigger any empty validaion messages + cy.getByTestId('deposit-submit').click(); + + cy.getByTestId(formFieldError).should('contain.text', 'Required'); + cy.getByTestId(formFieldError).should('have.length', 3); + + // Invalid public key + cy.get(toAddressField) + .clear() + .type('INVALID_DEPOSIT_TO_ADDRESS') + .next(`[data-testid="${formFieldError}"]`) + .should('have.text', 'Invalid Vega key'); + + // Deposit amount smaller than minimum viable for selected asset + // Select an amount so that we have a known decimal places value to work with + cy.get(assetSelectField).select('Asset 0'); + cy.get(amountField) + .clear() + .type('0.00000000000000000000000000000000001') + .next(`[data-testid="${formFieldError}"]`) + .should('have.text', 'Value is below minimum'); + + // Deposit amount is valid, but less than approved. This will always be the case because our + // CI wallet wont have approved any assets + cy.get(amountField) + .clear() + .type('100') + .next(`[data-testid="${formFieldError}"]`) + .should('have.text', 'Amount is above approved amount'); + }); +}); diff --git a/apps/trading-e2e/src/integration/deposits.feature b/apps/trading-e2e/src/integration/deposits.feature deleted file mode 100644 index 61b464f23..000000000 --- a/apps/trading-e2e/src/integration/deposits.feature +++ /dev/null @@ -1,154 +0,0 @@ -Feature: Deposits to vega wallet - - Background: - Given I can connect to Ethereum - And I navigate to deposits page - - # wallet is already connected before tests start and doesn't prompt the disconnected state - @ignore - Scenario: Connecting Ethereum wallet - Then I can see the eth not connected message "Connect your Ethereum wallet" - And the connect button is displayed - When I connect my Ethereum wallet - Then I can see the deposit form - - @todo - Scenario: Cannot deposit if approved amount is 0 (approval amount is 0) - And I connect my Ethereum wallet - When I set "0" tokens to be approved - And I approve the asset tokens - And I can see the deposit form is displayed - And I select "" asset from the dropdown list - When I enter the following details - | Field | Value | - | To (Vega key) | xxxxxxxx | - | Amount | 50 | - And I click to the deposit the funds - And I approve the ethereum transaction - # The following step is valid and are commented out as currently it cannot be automated - # Then I can see the deposit is unsuccessful - - @todo - Scenario: Cannot deposit if approved amount is lower than deposit amount - When I set "2" tokens to be approved - And I approve the asset tokens - And I can see the deposit form is displayed - And I select "" asset from the dropdown list - When I enter the following details - | Field | Value | - | To (Vega key) | xxxxxxxx | - | Amount | 50 | - And I click to the deposit the funds - And I approve the ethereum transaction - # The following step is valid and are commented out as currently it cannot be automated - # Then I can see the deposit is unsuccessful - - @todo - Scenario: Can succesfully deposit (approved amount is greater than deposit) - When I set "200000000" tokens to be approved - And I approve the asset tokens - And I can see the deposit form is displayed - And I select "" asset from the dropdown list - When I enter the following details - | Field | Value | - | To (Vega key) | xxxxxxxx | - | Amount | 50 | - And I click to the deposit the funds - And I approve the ethereum transaction - # The following steps are valid and are commented out as currently they cannot be automated - # Then I can see the deposit is Successfull - # And Balance is updated to reflect deposit amount - - Scenario: Empty form Validation errors - When I submit a deposit with empty fields - Then I can see empty form validation errors present - - Scenario: Invalid deposit public key validation error - When I enter the following deposit details in deposit form - | asset | tBTC TEST | - | to | INVALID_DEPOSIT_TO_ADDRESS | - | amount | 1 | - And I submit the form - Then Invalid Vega key is shown - - Scenario: Deposit amount too small validation - When I enter the following deposit details in deposit form - | asset | tBTC TEST | - | to | INVALID_DEPOSIT_TO_ADDRESS | - | amount | 0.00000000000000000000000000000000001 | - And I submit the form - Then Amount too small message shown - - @ignore - Scenario: Deposit amount greater than approved amount validation - When I enter the following deposit details in deposit form - | asset | tBTC TEST | - | to | INVALID_DEPOSIT_TO_ADDRESS | - | amount | 788888888888888 | - And I submit the form - And Insufficient amount message shown - # Then Amount too small message shown - # And I enter a valid amount - # And I submit the form - # This next step is being skipped due to account having approved status - # Then Not approved message shown - - @ignore - Scenario: Successful deposit - When I enter the following deposit details in deposit form - | asset | tBTC TEST | - | to | VEGA_PUBLIC_KEY | - | amount | 1 | - And I submit the form - And I can see the 'deposit pending' modal is shown - - @todo - Scenario: Use the 'Use Maximum' button to populate amount input with the balance in the connected wallet - And I can see the deposit form is displayed - And I select "" asset from the dropdown list - When I enter the following details - | Field | Value | - | To (Vega key) | xxxxxxxx | - | Amount | 0 | - When I click the use maximum button - Then I can see the field is updated with the maximum amount of the asset from my wallet - - @todo - Scenario: User is warned if the the amount to deposit is greater than what is available in the connected wallet" - And I can see the deposit form is displayed - And I select "" asset from the dropdown list - When I enter the following details - | Field | Value | - | To (Vega key) | xxxxxxxx | - | Amount | 60000000 | - And I click to the deposit the funds - Then an error message is shown stating not enough tokens in wallet to deposit - - @todo - Scenario: Deposit to a vega wallet key which is not your own - And I can see the deposit form is displayed - And I select "" asset from the dropdown list - When I enter the following details - | Field | Value | - | To (Vega key) | VEGA KEY of another wallet | - | Amount | 50 | - And I click to the deposit the funds - And I approve the ethereum transaction - # The following steps are valid and are commented out as currently they cannot be automated - # Then I can see the deposit is Successfull - # And Balance is updated to reflect deposit amount - - @todo - Scenario: Deposit when vega wallet is not connected - And I disconnect my vega wallet - And I can see the deposit form is displayed - And I select "" asset from the dropdown list - When I enter the following details - | Field | Value | - | To (Vega key) | xxxxxxxx | - | Amount | 50 | - And I click to the deposit the funds - And I approve the ethereum transaction -# The following step is valid and are commented out as currently it cannot be automated -# Then I can see the deposit is unsuccessful - diff --git a/apps/trading-e2e/src/integration/global.ts b/apps/trading-e2e/src/integration/global.ts new file mode 100644 index 000000000..0736720a9 --- /dev/null +++ b/apps/trading-e2e/src/integration/global.ts @@ -0,0 +1,78 @@ +import { connectVegaWallet } from '../support/vega-wallet'; + +describe('vega wallet', () => { + const connectVegaBtn = 'connect-vega-wallet'; + const manageVegaBtn = 'manage-vega-wallet'; + const form = 'rest-connector-form'; + const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME'); + const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE'); + + beforeEach(() => { + // Using portfolio page as it requires vega wallet connection + cy.visit('/portfolio'); + }); + + it('can connect', () => { + cy.getByTestId(connectVegaBtn).click(); + cy.contains('Connects using REST to a running Vega wallet service'); + cy.getByTestId('connectors-list').find('button').click(); + cy.getByTestId(form).find('#wallet').click().type(walletName); + cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase); + cy.getByTestId('rest-connector-form').find('button[type=submit]').click(); + cy.getByTestId(manageVegaBtn).should('exist'); + }); + + it('doesnt connect with invalid credentials', () => { + cy.getByTestId(connectVegaBtn).click(); + cy.getByTestId('connectors-list').find('button').click(); + cy.getByTestId(form).find('#wallet').click().type('invalid name'); + cy.getByTestId(form).find('#passphrase').click().type('invalid password'); + cy.getByTestId('rest-connector-form').find('button[type=submit]').click(); + cy.getByTestId('form-error').should('have.text', 'Authentication failed'); + }); + + it('doesnt connect with invalid fields', () => { + cy.getByTestId(connectVegaBtn).click(); + cy.getByTestId('connectors-list').find('button').click(); + cy.getByTestId('rest-connector-form').find('button[type=submit]').click(); + cy.getByTestId(form) + .find('#wallet') + .next('[data-testid="input-error-text"]') + .should('have.text', 'Required'); + cy.getByTestId(form) + .find('#passphrase') + .next('[data-testid="input-error-text"]') + .should('have.text', 'Required'); + }); + + it('can change selected public key and disconnect', () => { + connectVegaWallet(); + cy.getByTestId('manage-vega-wallet').click(); + cy.getByTestId('keypair-list').should('exist'); + cy.getByTestId('select-keypair-button').click(); + cy.getByTestId('keypair-list').should('not.exist'); + cy.getByTestId('manage-vega-wallet').contains( + Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY2') + ); + cy.getByTestId('manage-vega-wallet').click(); + cy.getByTestId('disconnect').click(); + cy.getByTestId('connect-vega-wallet').should('exist'); + cy.getByTestId('manage-vega-wallet').should('not.exist'); + }); +}); + +describe('ethereum wallet', () => { + beforeEach(() => { + cy.mockWeb3Provider(); + // Using portfolio is it requires Ethereum wallet connection + cy.visit('/portfolio'); + }); + + it('can connect', () => { + cy.getByTestId('connect-eth-wallet-btn').click(); + cy.getByTestId('web3-connector-list').should('exist'); + cy.getByTestId('web3-connector-MetaMask').click(); + cy.getByTestId('web3-connector-list').should('not.exist'); + cy.getByTestId('portfolio-grid').should('exist'); + }); +}); diff --git a/apps/trading-e2e/src/integration/home-page.feature b/apps/trading-e2e/src/integration/home-page.feature deleted file mode 100644 index 940f6b1ff..000000000 --- a/apps/trading-e2e/src/integration/home-page.feature +++ /dev/null @@ -1,67 +0,0 @@ -Feature: Home page - - Background: - Given I am on the homepage - And I query the server for Open Markets - When the server contains at least 6 open markets - - Scenario: Choose market overlay: prompted to choose market - Then I am prompted to select a market - - Scenario: Choose market overlay: oldest currently trading market shown in background by default - Then the oldest current trading market is loaded on the trading tab - - Scenario: Choose market overlay: contains at least 6 listed markets - Then I expect the market overlay table to contain at least 6 rows - - Scenario: Choose market overlay: each listed item reflects an open market - Then each market shown in overlay table exists as open market on server - - Scenario: Choose market overlay: each listed item contains values for last price and change - Then each market shown in overlay table contains content under the last price and change fields - - Scenario: Choose market overlay: oldest trading market appears at top of list - Then the oldest market trading in continous mode shown at top of overlay table - - Scenario: Choose market overlay: can be closed without choosing an option - When I close the dialog form - And the choose market overlay is no longer showing - Then the oldest current trading market is loaded on the trading tab - - Scenario: Choose market overlay: clicking a market name will load that market - When I click the most recent trading market - And the choose market overlay is no longer showing - Then the most recent current trading market is loaded on the trading tab - - Scenario: Choose market overlay: full market list: clicking the full market list shows all open markets - When I click the view full market list - And the choose market overlay is no longer showing - Then each market shown in the full list exists as open market on server - - Scenario: Choose market overlay: full market list: clicking a market name will load that market - When I click the view full market list - And the choose market overlay is no longer showing - When I click the most recent trading market - Then the most recent current trading market is loaded on the trading tab - - Scenario: Navigation: Visit Portfolio page - When I close the dialog form - And I navigate to portfolio page - - Scenario: Navigation: Visit Markets page - When I close the dialog form - And I navigate to markets page - - Scenario: Vega Wallett Overlay: Able to switch public key for connected Vega wallet - Given I connect to Vega Wallet - When I open wallet dialog - And select a different public key - Then public key is switched - - Scenario: Vega Wallett Overlay: Unable to connect Vega wallet with incorrect credentials - When I try to connect Vega wallet with incorrect details - Then wallet not running error message is displayed - - Scenario: Vega Wallett Overlay: Unable to connect Vega wallet with blank fields - When I try to connect Vega wallet with blank fields - Then wallet field validation errors are shown \ No newline at end of file diff --git a/apps/trading-e2e/src/integration/home.ts b/apps/trading-e2e/src/integration/home.ts new file mode 100644 index 000000000..efe96f917 --- /dev/null +++ b/apps/trading-e2e/src/integration/home.ts @@ -0,0 +1,171 @@ +import type { MarketList, MarketList_markets } from '@vegaprotocol/market-list'; +import { MarketState } from '@vegaprotocol/types'; +import { hasOperationName } from '../support'; +import { generateMarketList } from '../support/mocks/generate-market-list'; +import { generateMarkets } from '../support/mocks/generate-markets'; +import type { MarketsLanding } from '../support/mocks/generate-markets-landing'; +import { generateMarketsLanding } from '../support/mocks/generate-markets-landing'; +import { mockTradingPage } from '../support/trading'; + +describe('home', () => { + const selectMarketOverlay = 'select-market-list'; + + describe('default market found', () => { + let marketsLanding: MarketsLanding; + let marketList: MarketList; + let oldestMarket: MarketList_markets; + + beforeEach(() => { + marketsLanding = generateMarketsLanding(); + marketList = generateMarketList(); + oldestMarket = getOldestOpenMarket( + marketList.markets as MarketList_markets[] + ); + + // Mock markets query that is triggered by home page to find default market + cy.mockGQL('MarketsLanding', (req) => { + if (hasOperationName(req, 'MarketsLanding')) { + req.reply({ + body: { data: marketsLanding }, + }); + } + }); + + // Market market list for the dialog that opens on a trading page + cy.mockGQL('MarketList', (req) => { + if (hasOperationName(req, 'MarketList')) { + req.reply({ + body: { data: marketList }, + }); + } + }); + + // Mock market page + mockTradingPage(MarketState.Active); + + cy.visit('/'); + cy.wait('@MarketsLanding'); + cy.url().should('include', `/markets/${oldestMarket.id}`); // Should redirect to oldest market + cy.wait('@MarketList'); + }); + + it('redirects to a default market with the landing dialog open', () => { + // Overlay should be shown + cy.getByTestId(selectMarketOverlay).should('exist'); + cy.contains('Select a market to get started').should('be.visible'); + + // I expect the market overlay table to contain at least 3 rows (one header row) + cy.getByTestId(selectMarketOverlay) + .get('table tr') + .then((row) => { + expect(row.length).to.eq(3); + }); + + // each market shown in overlay table contains content under the last price and change fields + cy.getByTestId(selectMarketOverlay) + .get('table tr') + .each(($element, index) => { + if (index > 0) { + // skip header row + cy.root().within(() => { + cy.getByTestId('price').should('not.be.empty'); + }); + } + }); + + // the oldest market trading in continous mode shown at top of overlay table + cy.get('table tr') + .eq(1) + .within(() => + cy + .contains(oldestMarket.tradableInstrument.instrument.code) + .should('be.visible') + ); + + cy.getByTestId('dialog-close').click(); + cy.getByTestId(selectMarketOverlay).should('not.exist'); + + // the choose market overlay is no longer showing + cy.contains('Select a market to get started').should('not.exist'); + cy.contains('Loading...').should('not.exist'); + }); + + it('can click a market name to load that market', () => { + // Click newer market + cy.getByTestId(selectMarketOverlay) + .should('exist') + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .contains(marketList.markets![1].tradableInstrument.instrument.code) + .click(); + cy.getByTestId(selectMarketOverlay).should('not.exist'); + cy.url().should('include', 'market-1'); + }); + + it('view full market list goes to markets page', () => { + cy.getByTestId(selectMarketOverlay) + .should('exist') + .contains('Or view full market list') + .click(); + cy.getByTestId(selectMarketOverlay).should('not.exist'); + cy.url().should('include', '/markets'); + cy.get('main[data-testid="markets"]').should('exist'); + }); + }); + + describe('no default found', () => { + it('redirects to a the market list page if no sensible default is found', () => { + // Mock markets query that is triggered by home page to find default market + cy.mockGQL('MarketsLanding', (req) => { + if (hasOperationName(req, 'MarketsLanding')) { + req.reply({ + body: { + // Remove open timestamps so we can't calculate a sensible default market + data: generateMarketsLanding({ + markets: [ + { + marketTimestamps: { + __typename: 'MarketTimestamps', + open: '', + }, + }, + { + marketTimestamps: { + __typename: 'MarketTimestamps', + open: '', + }, + }, + ], + }), + }, + }); + } + }); + + cy.mockGQL('Markets', (req) => { + if (hasOperationName(req, 'Markets')) { + req.reply({ + body: { + data: generateMarkets(), + }, + }); + } + }); + + cy.visit('/'); + cy.wait('@MarketsLanding'); + cy.url().should('include', '/markets'); + }); + }); +}); + +function getOldestOpenMarket(openMarkets: MarketList_markets[]) { + const [oldestMarket] = openMarkets.sort( + (a, b) => + new Date(a.marketTimestamps.open as string).getTime() - + new Date(b.marketTimestamps.open as string).getTime() + ); + if (!oldestMarket) { + throw new Error('Could not find oldest market'); + } + return oldestMarket; +} diff --git a/apps/trading-e2e/src/integration/markets-page.feature b/apps/trading-e2e/src/integration/markets-page.feature deleted file mode 100644 index e9fb1886a..000000000 --- a/apps/trading-e2e/src/integration/markets-page.feature +++ /dev/null @@ -1,19 +0,0 @@ -Feature: Markets page - - Scenario: Navigation - Given I am on the homepage - When I navigate to markets page - Then I can view markets - And the market table is displayed - - Scenario: Select active market - Given I am on the markets page - And I can view markets - When I click on "Active" mocked market - Then trading page for "active" market is displayed - - Scenario: Select suspended market - Given I am on the markets page - And I can view markets - When I click on "Suspended" mocked market - Then trading page for "suspended" market is displayed diff --git a/apps/trading-e2e/src/integration/markets.ts b/apps/trading-e2e/src/integration/markets.ts new file mode 100644 index 000000000..160c8a572 --- /dev/null +++ b/apps/trading-e2e/src/integration/markets.ts @@ -0,0 +1,92 @@ +import { MarketState } from '@vegaprotocol/types'; +import { hasOperationName } from '../support'; +import { generateMarkets } from '../support/mocks/generate-markets'; +import { mockTradingPage } from '../support/trading'; + +describe('markets table', () => { + beforeEach(() => { + cy.mockGQL('Markets', (req) => { + if (hasOperationName(req, 'Markets')) { + req.reply({ + body: { data: generateMarkets() }, + }); + } + }); + cy.visit('/markets'); + }); + + it('renders correctly', () => { + const marketRowHeaderClassname = 'div > span.ag-header-cell-text'; + const marketRowNameColumn = 'tradableInstrument.instrument.code'; + const marketRowSymbolColumn = + 'tradableInstrument.instrument.product.settlementAsset.symbol'; + const marketRowPrices = 'flash-cell'; + const marketRowDescription = 'name'; + + cy.wait('@Markets'); + cy.get('.ag-root-wrapper').should('be.visible'); + + const expectedMarketHeaders = [ + 'Market', + 'Settlement asset', + 'State', + 'Best bid', + 'Best offer', + 'Mark price', + 'Description', + ]; + + for (let index = 0; index < expectedMarketHeaders.length; index++) { + cy.get(marketRowHeaderClassname).should( + 'contain.text', + expectedMarketHeaders[index] + ); + } + + cy.get(`[col-id='${marketRowNameColumn}']`).each(($marketName) => { + cy.wrap($marketName).should('not.be.empty'); + }); + + cy.get(`[col-id='${marketRowSymbolColumn}']`).each(($marketSymbol) => { + cy.wrap($marketSymbol).should('not.be.empty'); + }); + + cy.getByTestId(marketRowPrices).each(($price) => { + cy.wrap($price).should('not.be.empty').and('contain.text', '.'); + }); + + cy.get(`[col-id='${marketRowDescription}']`).each(($marketDescription) => { + cy.wrap($marketDescription).should('not.be.empty'); + }); + }); + + it('can select an active market', () => { + cy.wait('@Markets'); + cy.get('.ag-root-wrapper').should('be.visible'); + + mockTradingPage(MarketState.Active); + + // click on active market + cy.get('[role="gridcell"][col-id=data]').should('be.visible'); + cy.get('[role="gridcell"][col-id=data]').contains('Active').click(); + + cy.wait('@Market'); + cy.contains('ACTIVE MARKET'); + cy.url().should('include', '/markets/market-0'); + }); + + it('can select a suspended market', () => { + cy.wait('@Markets'); + cy.get('.ag-root-wrapper').should('be.visible'); + + mockTradingPage(MarketState.Suspended); + + // click on active market + cy.get('[role="gridcell"][col-id=data]').should('be.visible'); + cy.get('[role="gridcell"][col-id=data]').contains('Suspended').click(); + + cy.wait('@Market'); + cy.contains('SUSPENDED MARKET'); + cy.url().should('include', '/markets/market-1'); + }); +}); diff --git a/apps/trading-e2e/src/integration/portfolio-page.feature b/apps/trading-e2e/src/integration/portfolio-page.feature deleted file mode 100644 index 7f97d0828..000000000 --- a/apps/trading-e2e/src/integration/portfolio-page.feature +++ /dev/null @@ -1,5 +0,0 @@ - -Feature: Portfolio page - Scenario: Navigation - Given I am on the homepage - Then I navigate to portfolio page diff --git a/apps/trading-e2e/src/integration/portfolio.ts b/apps/trading-e2e/src/integration/portfolio.ts new file mode 100644 index 000000000..7415cac05 --- /dev/null +++ b/apps/trading-e2e/src/integration/portfolio.ts @@ -0,0 +1,6 @@ +describe('portfolio', () => { + it('requires connecting', () => { + cy.visit('/portfolio'); + cy.get('main[data-testid="portfolio"]').should('exist'); + }); +}); diff --git a/apps/trading-e2e/src/integration/trading-accounts.ts b/apps/trading-e2e/src/integration/trading-accounts.ts new file mode 100644 index 000000000..1d18885ed --- /dev/null +++ b/apps/trading-e2e/src/integration/trading-accounts.ts @@ -0,0 +1,38 @@ +import { MarketState } from '@vegaprotocol/types'; +import { mockTradingPage } from '../support/trading'; +import { connectVegaWallet } from '../support/vega-wallet'; + +beforeEach(() => { + mockTradingPage(MarketState.Active); + cy.visit('/markets/market-0'); +}); + +describe('accounts', () => { + it('renders accounts', () => { + cy.getByTestId('Accounts').click(); + cy.getByTestId('tab-accounts').contains('Please connect Vega wallet'); + + connectVegaWallet(); + + cy.getByTestId('tab-accounts').should('be.visible'); + cy.getByTestId('tab-accounts') + .get(`[row-id='General-tEURO-null']`) + .find('[col-id="asset.symbol"]') + .should('have.text', 'tEURO'); + + cy.getByTestId('tab-accounts') + .get(`[row-id='General-tEURO-null']`) + .find('[col-id="type"]') + .should('have.text', 'General'); + + cy.getByTestId('tab-accounts') + .get(`[row-id='General-tEURO-null']`) + .find('[col-id="market.name"]') + .should('have.text', '—'); + + cy.getByTestId('tab-accounts') + .get(`[row-id='General-tEURO-null']`) + .find('[col-id="balance"]') + .should('have.text', '1,000.00000'); + }); +}); diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket.ts b/apps/trading-e2e/src/integration/trading-deal-ticket.ts new file mode 100644 index 000000000..a174e0e0b --- /dev/null +++ b/apps/trading-e2e/src/integration/trading-deal-ticket.ts @@ -0,0 +1,166 @@ +import { MarketState } from '@vegaprotocol/types'; +import { mockTradingPage } from '../support/trading'; +import { connectVegaWallet } from '../support/vega-wallet'; + +interface Order { + type: 'TYPE_MARKET' | 'TYPE_LIMIT'; + side: 'SIDE_BUY' | 'SIDE_SELL'; + size: string; + price?: string; + timeInForce: + | 'TIME_IN_FORCE_GTT' + | 'TIME_IN_FORCE_GTC' + | 'TIME_IN_FORCE_IOC' + | 'TIME_IN_FORCE_FOK' + | 'TIME_IN_FORCE_GFN' + | 'TIME_IN_FORCE_GFA'; + expiresAt?: string; +} + +describe('deal ticket orders', () => { + const orderSizeField = 'order-size'; + const orderPriceField = 'order-price'; + const orderTIFDropDown = 'order-tif'; + const placeOrderBtn = 'place-order'; + const orderStatusHeader = 'order-status-header'; + const orderTransactionHash = 'tx-hash'; + + before(() => { + mockTradingPage(MarketState.Active); + cy.visit('/markets/market-0'); + connectVegaWallet(); + }); + + beforeEach(() => { + cy.mockVegaCommandSync({ + txHash: 'test-tx-hash', + tx: { + signature: { + value: + 'd86138bba739bbc1069b3dc975d20b3a1517c2b9bdd401c70eeb1a0ecbc502ec268cf3129824841178b8b506b0b7d650c76644dbd96f524a6cb2158fb7121800', + }, + }, + }); + }); + + it('successfully places market buy order', () => { + const order: Order = { + type: 'TYPE_MARKET', + side: 'SIDE_BUY', + size: '100', + timeInForce: 'TIME_IN_FORCE_FOK', + }; + testOrder(order); + }); + + it('successfully places market sell order', () => { + const order: Order = { + type: 'TYPE_MARKET', + side: 'SIDE_SELL', + size: '100', + timeInForce: 'TIME_IN_FORCE_IOC', + }; + testOrder(order); + }); + + it('successfully places limit buy order', () => { + const order: Order = { + type: 'TYPE_LIMIT', + side: 'SIDE_BUY', + size: '100', + price: '200', + timeInForce: 'TIME_IN_FORCE_GTC', + }; + testOrder(order, { price: '20000' }); + }); + + it('successfully places limit sell order', () => { + const order: Order = { + type: 'TYPE_LIMIT', + side: 'SIDE_SELL', + size: '100', + price: '50000', + timeInForce: 'TIME_IN_FORCE_GFN', + }; + testOrder(order, { price: '5000000' }); + }); + + it('successfully places GTT limit buy order', () => { + const order: Order = { + type: 'TYPE_LIMIT', + side: 'SIDE_SELL', + size: '100', + price: '1.00', + timeInForce: 'TIME_IN_FORCE_GTT', + expiresAt: '2022-01-01T00:00', + }; + testOrder(order, { + price: '100', + expiresAt: + new Date(order.expiresAt as string).getTime().toString() + '000000', + }); + }); + + const testOrder = (order: Order, expected?: Partial) => { + const { type, side, size, price, timeInForce, expiresAt } = order; + cy.get(`[name="order-type"][value="${type}"`).click({ force: true }); // force as input is hidden and displayed as a button + cy.get(`[name="order-side"][value="${side}"`).click({ force: true }); + cy.getByTestId(orderSizeField).clear().type(size); + if (price) { + cy.getByTestId(orderPriceField).clear().type(price); + } + cy.getByTestId(orderTIFDropDown).select(timeInForce); + if (timeInForce === 'TIME_IN_FORCE_GTT') { + if (!expiresAt) { + throw new Error('Specify expiresAt if using GTT'); + } + // select expiry + cy.getByTestId('date-picker-field').type(expiresAt); + } + cy.getByTestId(placeOrderBtn).click(); + + const expectedOrder = { + ...order, + ...expected, + }; + + cy.wait('@VegaCommandSync') + .its('request.body') + .should('deep.equal', { + pubKey: Cypress.env('VEGA_PUBLIC_KEY'), + propagate: true, + orderSubmission: { + marketId: 'market-0', + ...expectedOrder, + }, + }); + cy.getByTestId(orderStatusHeader).should( + 'have.text', + 'Awaiting network confirmation' + ); + cy.getByTestId(orderTransactionHash) + .invoke('text') + .should('contain', 'Tx hash: test-tx-hash'); + cy.getByTestId('dialog-close').click(); + }; + + it.skip('cannot place an order if market is suspended'); + it.skip('cannot place an order if size is 0'); + it.skip('cannot place an order expiry date is invalid'); + it.skip('unsuccessfull order due to no collateral'); +}); + +describe('deal ticket validation', () => { + before(() => { + mockTradingPage(MarketState.Active); + cy.visit('/markets/market-0'); + }); + + it('cannot place an order if wallet is not connected', () => { + cy.getByTestId('connect-vega-wallet'); // Not connected + cy.getByTestId('place-order').should('be.disabled'); + cy.getByTestId('dealticket-error-message').contains( + 'No public key selected' + ); + }); +}); diff --git a/apps/trading-e2e/src/integration/trading-orders.ts b/apps/trading-e2e/src/integration/trading-orders.ts new file mode 100644 index 000000000..cfe9fae77 --- /dev/null +++ b/apps/trading-e2e/src/integration/trading-orders.ts @@ -0,0 +1,69 @@ +import { MarketState } from '@vegaprotocol/types'; +import { mockTradingPage } from '../support/trading'; +import { connectVegaWallet } from '../support/vega-wallet'; + +beforeEach(() => { + mockTradingPage(MarketState.Active); + cy.visit('/markets/market-0'); +}); + +describe('orders', () => { + const orderSymbol = 'market.tradableInstrument.instrument.code'; + const orderSize = 'size'; + const orderType = 'type'; + const orderStatus = 'status'; + const orderRemaining = 'remaining'; + const orderPrice = 'price'; + const orderTimeInForce = 'timeInForce'; + const orderCreatedAt = 'createdAt'; + + it('renders orders', () => { + cy.getByTestId('Orders').click(); + cy.getByTestId('tab-orders').contains('Please connect Vega wallet'); + + connectVegaWallet(); + + cy.getByTestId('tab-orders').should('be.visible'); + + cy.getByTestId('tab-orders') + .get(`[col-id='${orderSymbol}']`) + .each(($symbol) => { + cy.wrap($symbol).invoke('text').should('not.be.empty'); + }); + cy.getByTestId('tab-orders') + .get(`[col-id='${orderSize}']`) + .each(($size) => { + cy.wrap($size).invoke('text').should('not.be.empty'); + }); + cy.getByTestId('tab-orders') + .get(`[col-id='${orderType}']`) + .each(($type) => { + cy.wrap($type).invoke('text').should('not.be.empty'); + }); + cy.getByTestId('tab-orders') + .get(`[col-id='${orderStatus}']`) + .each(($status) => { + cy.wrap($status).invoke('text').should('not.be.empty'); + }); + cy.getByTestId('tab-orders') + .get(`[col-id='${orderRemaining}']`) + .each(($remaining) => { + cy.wrap($remaining).invoke('text').should('not.be.empty'); + }); + cy.getByTestId('tab-orders') + .get(`[col-id='${orderPrice}']`) + .each(($price) => { + cy.wrap($price).invoke('text').should('not.be.empty'); + }); + cy.getByTestId('tab-orders') + .get(`[col-id='${orderTimeInForce}']`) + .each(($timeInForce) => { + cy.wrap($timeInForce).invoke('text').should('not.be.empty'); + }); + cy.getByTestId('tab-orders') + .get(`[col-id='${orderCreatedAt}']`) + .each(($dateTime) => { + cy.wrap($dateTime).invoke('text').should('not.be.empty'); + }); + }); +}); diff --git a/apps/trading-e2e/src/integration/trading-page.feature b/apps/trading-e2e/src/integration/trading-page.feature deleted file mode 100644 index 0e4c2f3f1..000000000 --- a/apps/trading-e2e/src/integration/trading-page.feature +++ /dev/null @@ -1,127 +0,0 @@ -Feature: Trading page - Scenario Outline: Deal ticket: Successfull market buy orders - Given I am on the trading page for an active market - And I connect to Vega Wallet - When I place a buy '' market order - Then order request is sent - - Examples: - | marketOrderType | - | FOK | - | IOC | - - Scenario Outline: Deal ticket: Successfull Limit buy orders - Given I am on the trading page for an active market - And I connect to Vega Wallet - When I place a buy '' limit order - Then order request is sent - - Examples: - | limitOrderType | - | IOC | - | FOK | - | GTT | - # | GFA | Requires market to be in auction - | GFN | - - Scenario Outline: Deal ticket: Successfull market sell order - Given I am on the trading page for an active market - And I connect to Vega Wallet - When I place a sell '' market order - Then order request is sent - - Examples: - | marketOrderType | - | FOK | - | IOC | - - Scenario Outline: Deal ticket: Successfull limit sell order - Given I am on the trading page for an active market - And I connect to Vega Wallet - When I place a sell '' limit order - Then order request is sent - - Examples: - | limitOrderType | - | IOC | - | FOK | - | GTT | - # | GFA | Requires market to be in auction - | GFN | - - @ignore - Scenario: Deal ticket: Unsuccessfull order because lack of funds - Given I am on the homepage - And I navigate to markets page - When I click on active market - And I connect to Vega Wallet - And place a buy 'FOK' market order - Then error message for insufficient funds is displayed - - Scenario: Deal ticket: Unable to order because market is suspended - Given I am on the trading page for a suspended market - And I connect to Vega Wallet - Then place order button is disabled - And "Market is currently suspended" error is shown - - Scenario: Deal ticket: Unable to order because wallet is not connected - Given I am on the trading page for an active market - Then place order button is disabled - And "No public key selected" error is shown - - @ignore - Scenario: Deal ticket: Unsuccessfull because quantity is 0 - Given I am on the trading page for an active market - And I connect to Vega Wallet - And place a buy 'FOK' market order with amount of 0 - Then Order rejected by wallet error shown containing text "must be positive" - - Scenario: Positions: Displayed when connected to wallet - Given I am on the trading page for an active market - And I connect to Vega Wallet - When I click on positions tab - Then positions are displayed - - Scenario: Accounts: Displayed when connected to wallet - Given I am on the trading page for an active market - And I connect to Vega Wallet - When I click on accounts tab - Then accounts are displayed - And I can see account for tEURO - - Scenario: Orders: Placed orders displayed - Given I am on the trading page for an active market - And I connect to Vega Wallet - When I click on orders tab - Then placed orders are displayed - - Scenario: Orderbook displayed - Given I am on the trading page for an active market - When I click on order book tab - Then orderbook is displayed with expected orders - And orderbook can be reduced and expanded - - @todo - Scenario: Orderbook paginated with over 100 orders - Given I am on the trading page for an active market - When I click on order book tab - And a large amount is orders are received - Then a certain amount of orders are displayed - - @todo - Scenario: Orderbook uses non-static prices for market in auction - Given I am on the trading page for a market in auction - When I click on order book tab - Then order book is rendered using non-static offers - - @todo - Scenario: Orderbook updated when large order is made - Given I am on the trading page for an active market - When I place a large order - Then I should see my order have an effect on the order book - - @todo - Scenario: Able to place order by clicking on order from orderbook - Given I am on the trading page for an active market - When I place a large order - Then I should see my order have an effect on the order book diff --git a/apps/trading-e2e/src/integration/trading-positions.ts b/apps/trading-e2e/src/integration/trading-positions.ts new file mode 100644 index 000000000..c2e53f376 --- /dev/null +++ b/apps/trading-e2e/src/integration/trading-positions.ts @@ -0,0 +1,35 @@ +import { MarketState } from '@vegaprotocol/types'; +import { mockTradingPage } from '../support/trading'; +import { connectVegaWallet } from '../support/vega-wallet'; + +beforeEach(() => { + mockTradingPage(MarketState.Active); + cy.visit('/markets/market-0'); +}); + +describe('positions', () => { + it('renders positions', () => { + cy.getByTestId('Positions').click(); + cy.getByTestId('tab-positions').contains('Please connect Vega wallet'); + + connectVegaWallet(); + + cy.getByTestId('tab-positions').should('be.visible'); + cy.getByTestId('tab-positions') + .get('[col-id="market.tradableInstrument.instrument.code"]') + .each(($marketSymbol) => { + cy.wrap($marketSymbol).invoke('text').should('not.be.empty'); + }); + cy.getByTestId('tab-positions') + .get('[col-id="openVolume"]') + .each(($openVolume) => { + cy.wrap($openVolume).invoke('text').should('not.be.empty'); + }); + // includes average entry price, mark price & realised PNL + cy.getByTestId('tab-positions') + .getByTestId('flash-cell') + .each(($prices) => { + cy.wrap($prices).invoke('text').should('not.be.empty'); + }); + }); +}); diff --git a/apps/trading-e2e/src/integration/trading-trades.ts b/apps/trading-e2e/src/integration/trading-trades.ts new file mode 100644 index 000000000..05b3e7c6c --- /dev/null +++ b/apps/trading-e2e/src/integration/trading-trades.ts @@ -0,0 +1,34 @@ +import { MarketState } from '@vegaprotocol/types'; +import { mockTradingPage } from '../support/trading'; + +beforeEach(() => { + mockTradingPage(MarketState.Active); + cy.visit('/markets/market-0'); +}); + +describe('trades', () => { + const colIdPrice = 'price'; + const colIdSize = 'size'; + const colIdCreatedAt = 'createdAt'; + + it('renders trades', () => { + cy.getByTestId('Trades').click(); + cy.getByTestId('tab-trades').should('be.visible'); + + cy.get(`[col-id=${colIdPrice}]`).each(($tradePrice) => { + cy.wrap($tradePrice).invoke('text').should('not.be.empty'); + }); + cy.get(`[col-id=${colIdSize}]`).each(($tradeSize) => { + cy.wrap($tradeSize).invoke('text').should('not.be.empty'); + }); + + const dateTimeRegex = + /(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm; + cy.get(`[col-id=${colIdCreatedAt}]`).each(($tradeDateTime, index) => { + if (index != 0) { + //ignore header + cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex); + } + }); + }); +}); diff --git a/apps/trading-e2e/src/integration/withdraw.ts b/apps/trading-e2e/src/integration/withdraw.ts new file mode 100644 index 000000000..840ef4aed --- /dev/null +++ b/apps/trading-e2e/src/integration/withdraw.ts @@ -0,0 +1,97 @@ +import { hasOperationName } from '../support'; +import { connectEthereumWallet } from '../support/ethereum-wallet'; +import { generateWithdrawPageQuery } from '../support/mocks/generate-withdraw-page-query'; +import { connectVegaWallet } from '../support/vega-wallet'; + +describe('withdraw', () => { + const formFieldError = 'input-error-text'; + const toAddressField = 'input[name="to"]'; + const assetSelectField = 'select[name="asset"]'; + const amountField = 'input[name="amount"]'; + const useMaximumAmount = 'use-maximum'; + const submitWithdrawBtn = 'submit-withdrawal'; + + beforeEach(() => { + cy.mockWeb3Provider(); + cy.mockGQL('WithdrawPageQuery', (req) => { + if (hasOperationName(req, 'WithdrawPageQuery')) { + req.reply({ + body: { data: generateWithdrawPageQuery() }, + }); + } + }); + cy.visit('/portfolio/withdraw'); + + // Withdraw page requires vega wallet connection + connectVegaWallet(); + + // It also requires connection Ethereum wallet + connectEthereumWallet(); + + cy.wait('@WithdrawPageQuery'); + cy.contains('Withdraw'); + }); + + it('form validation', () => { + // Prompts that there are incomplete withdrawals + cy.contains('You have incomplete withdrawals'); + cy.getByTestId('complete-withdrawals-prompt').should('exist'); + + cy.getByTestId(submitWithdrawBtn).click(); + + cy.getByTestId(formFieldError).should('contain.text', 'Required'); + // only 2 despite 3 fields because the ethereum address will be auto populated + cy.getByTestId(formFieldError).should('have.length', 2); + + // Test for invalid Ethereum address + cy.get(toAddressField) + .clear() + .type('invalid-ethereum-address') + .next('[data-testid="input-error-text"]') + .should('contain.text', 'Invalid Ethereum address'); + + // Test min amount + cy.get(assetSelectField).select('Asset 1'); // Select asset so we have a min viable amount calculated + cy.get(amountField) + .clear() + .type('0') + .next('[data-testid="input-error-text"]') + .should('contain.text', 'Value is below minimum'); + + // Test max amount + cy.get(amountField) + .clear() + .type('1') // Will be above maximum because the vega wallet doesnt have any collateral + .next('[data-testid="input-error-text"]') + .should('contain.text', 'Value is above maximum'); + }); + + it('can set amount using use maximum button', () => { + cy.get(assetSelectField).select('Asset 0'); + cy.getByTestId(useMaximumAmount).click(); + cy.get(amountField).should('have.value', '1000.00000'); + }); + + it('triggers transaction when submitted', () => { + cy.mockVegaCommandSync({ + txHash: 'test-tx-hash', + tx: { + signature: { + value: + 'd86138bba739bbc1069b3dc975d20b3a1517c2b9bdd401c70eeb1a0ecbc502ec268cf3129824841178b8b506b0b7d650c76644dbd96f524a6cb2158fb7121800', + }, + }, + }); + cy.get(assetSelectField).select('Asset 0'); + cy.get(amountField).clear().type('10'); + cy.getByTestId(submitWithdrawBtn).click(); + cy.getByTestId('dialog-title').should( + 'have.text', + 'Withdrawal transaction pending' + ); + cy.getByTestId('dialog-text').should('have.text', 'Awaiting transaction'); + }); + + it.skip('creates a withdrawal on submit'); // Needs capsule + it.skip('creates a withdrawal on submit and prompts to complete withdrawal'); // Needs capsule +}); diff --git a/apps/trading-e2e/src/integration/withdrawals.feature b/apps/trading-e2e/src/integration/withdrawals.feature deleted file mode 100644 index dbfd609e5..000000000 --- a/apps/trading-e2e/src/integration/withdrawals.feature +++ /dev/null @@ -1,68 +0,0 @@ -Feature: Withdrawals to eth wallet - - Background: - Given I can connect to Ethereum - And I navigate to withdrawal page - And I connect to Vega Wallet - - Scenario: Succesfull withdrawal - When I succesfully fill in and submit withdrawal form - Then withdrawal modal is displayed - - Scenario: Error displayed when fields are empty - When I clear ethereum address - And click submit - Then errors are displayed for empty fields - - Scenario: Error displayed when invalid Ethereum address is entered - When I enter an invalid ethereum address - Then error for invalid ethereum address is displayed - - Scenario: Error displayed when not in range of acceptable amount - When I enter the following details in withdrawal form - | asset | tUSDC TEST | - | amount | 0 | - Then error for below minumum amount is displayed - When I enter the following details in withdrawal form - | asset | tUSDC TEST | - | amount | 1 | - Then error for above maximum amount is displayed - - Scenario: Fill in amount using maximum - When I select "tDAI TEST" - And ethereum address is connected Ethereum wallet - And I click Use maximum - Then expected amount is "5.00000" - - @ignore - Scenario: Able to view history of withdrawals on withdrawals page - Given I navigate to withdrawals page - Then history of withdrawals are displayed - - Scenario: Vega wallet connect text shown when Vega wallet is disconnected - When I disconnect my Vega wallet - Then connect to Vega wallet is displayed - - @manual - Scenario: Can see pending / unfinished withdrawals - Given I am on the withdrawals page - And I can see there are unfinished withdrawals - And I can see the complete withdrawals button - - # Needs capsule - @manual - Scenario: Finish withdrawal to eth wallet - Given I am on the withdrawals page - And I can see there are unfinished withdrawals - And I click on an unfinished withdrawal button - Then I approve transaction on ethereum - Then I can see the withdrawal button state has changed to pending - When The transaction is complete - Then My balance has been updated - - @manual - Scenario: Withdrawals after chain reset - Given I am on the withdrawals page - And I previously had withdrawals - And There has been a chain reset - Then There should be no incomplete withdrawals diff --git a/apps/trading-e2e/src/integration/withdrawals.ts b/apps/trading-e2e/src/integration/withdrawals.ts new file mode 100644 index 000000000..e8916600e --- /dev/null +++ b/apps/trading-e2e/src/integration/withdrawals.ts @@ -0,0 +1,82 @@ +import { hasOperationName } from '../support'; +import { connectEthereumWallet } from '../support/ethereum-wallet'; +import { generateWithdrawals } from '../support/mocks/generate-withdrawals'; +import { connectVegaWallet } from '../support/vega-wallet'; + +describe('withdrawals', () => { + beforeEach(() => { + cy.mockWeb3Provider(); + cy.mockGQL('Withdrawals', (req) => { + if (hasOperationName(req, 'Withdrawals')) { + req.reply({ + body: { data: generateWithdrawals() }, + }); + } + }); + cy.visit('/portfolio/withdrawals'); + + // Withdraw page requires vega wallet connection + connectVegaWallet(); + + // It also requires connection Ethereum wallet + connectEthereumWallet(); + + cy.contains('Withdrawals'); + }); + + it('renders history of withdrawals', () => { + const ethAddressLink = `${Cypress.env( + 'ETHERSCAN_URL' + )}/address/0x72c22822A19D20DE7e426fB84aa047399Ddd8853`; + const etherScanLink = `${Cypress.env( + 'ETHERSCAN_URL' + )}/tx/0x5d7b1a35ba6bd23be17bb7a159c13cdbb3121fceb94e9c6c510f5503dce48d03`; + cy.contains('Withdrawals'); + + const row = '.ag-center-cols-container[role="rowgroup"] > [role="row"]'; + + // First row is incomplete + cy.get(row) + .eq(0) + .find('[col-id="asset.symbol"]') + .should('contain.text', 'AST0'); + cy.get(row) + .eq(0) + .find('[col-id="amount"]') + .should('contain.text', '100.00000'); + cy.get(row) + .eq(0) + .find('[col-id="details.receiverAddress"]') + .should('contain.text', '0x72c2…dd8853') + .find('a') + .should('have.attr', 'href', ethAddressLink); + cy.get(row) + .eq(0) + .find('[col-id="createdTimestamp"]') + .invoke('text') + .should('not.be.empty'); + cy.get(row) + .eq(0) + .find('[col-id="status"]') + .should('contain.text', 'Open') + .find('button') + .contains('Complete'); + + // Second row is complete so last cell should have a link to the tx + cy.get(row) + .eq(1) + .find('[col-id="status"]') + .should('contain.text', 'Finalized') + .find('a') + .contains('View on Etherscan') + .should('have.attr', 'href', etherScanLink); + }); + + it('renders a link to start a new withdrawal', () => { + cy.getByTestId('start-withdrawal').click(); + cy.url().should('include', '/portfolio/withdraw'); + }); + + it.skip('renders pending and unfinished withdrawals'); + it.skip('can complete unfinished withdrawals'); // Needs capsule +}); diff --git a/apps/trading-e2e/src/support/ethereum-wallet.ts b/apps/trading-e2e/src/support/ethereum-wallet.ts new file mode 100644 index 000000000..a0a3a7b76 --- /dev/null +++ b/apps/trading-e2e/src/support/ethereum-wallet.ts @@ -0,0 +1,5 @@ +export const connectEthereumWallet = () => { + cy.getByTestId('connect-eth-wallet-btn').should('be.enabled').click(); + cy.getByTestId('web3-connector-list').should('be.visible'); + cy.getByTestId('web3-connector-MetaMask').click(); +}; diff --git a/apps/trading-e2e/src/support/ethereum-wallet/ethereum-wallet.ts b/apps/trading-e2e/src/support/ethereum-wallet/ethereum-wallet.ts deleted file mode 100644 index f9e418115..000000000 --- a/apps/trading-e2e/src/support/ethereum-wallet/ethereum-wallet.ts +++ /dev/null @@ -1,22 +0,0 @@ -export class EthereumWallet { - connectWalletBtnId = 'connect-eth-wallet-btn'; - connectWalletMsgId = 'connect-eth-wallet-msg'; - - connect() { - cy.getByTestId(this.connectWalletBtnId).should('be.enabled').click(); - cy.getByTestId('web3-connector-list').should('be.visible'); - cy.getByTestId('web3-connector-MetaMask').click(); - } - - verifyEthConnectBtnIsDisplayed() { - cy.getByTestId(this.connectWalletBtnId) - .should('be.visible') - .and('have.text', 'Connect'); - } - - verifyConnectWalletMsg(ethNotConnectedText: string) { - cy.getByTestId(this.connectWalletMsgId) - .should('be.visible') - .and('have.text', ethNotConnectedText); - } -} diff --git a/apps/trading-e2e/src/support/ethereum-wallet/index.ts b/apps/trading-e2e/src/support/ethereum-wallet/index.ts deleted file mode 100644 index 900eb3e42..000000000 --- a/apps/trading-e2e/src/support/ethereum-wallet/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ethereum-wallet'; diff --git a/apps/trading-e2e/src/support/mocks/generate-candles.ts b/apps/trading-e2e/src/support/mocks/generate-candles.ts index 32bd97d29..2ce58d860 100644 --- a/apps/trading-e2e/src/support/mocks/generate-candles.ts +++ b/apps/trading-e2e/src/support/mocks/generate-candles.ts @@ -37,7 +37,7 @@ export const generateCandles = (override?: PartialDeep): Candles => { ]; const defaultResult = { market: { - id: 'market-id', + id: 'market-0', decimalPlaces: 5, tradableInstrument: { instrument: { diff --git a/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts b/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts index 58f6854d9..58b3764c8 100644 --- a/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts +++ b/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts @@ -8,10 +8,10 @@ export const generateDealTicketQuery = ( ): DealTicketQuery => { const defaultResult: DealTicketQuery = { market: { - id: 'market-id', + id: 'market-0', name: 'ETHBTC Quarterly (30 Jun 2022)', decimalPlaces: 2, - positionDecimalPlaces: 1, + positionDecimalPlaces: 0, state: MarketState.Active, tradingMode: MarketTradingMode.Continuous, tradableInstrument: { diff --git a/apps/trading-e2e/src/support/mocks/generate-deposit-page.ts b/apps/trading-e2e/src/support/mocks/generate-deposit-page.ts new file mode 100644 index 000000000..66bea58fe --- /dev/null +++ b/apps/trading-e2e/src/support/mocks/generate-deposit-page.ts @@ -0,0 +1,36 @@ +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; + +export const generateDepositPage = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + override?: PartialDeep +) => { + const defaultResult = { + assets: [ + { + id: 'asset-0', + symbol: 'AST0', + name: 'Asset 0', + decimals: 5, + source: { + __typename: 'ERC20', + contractAddress: '0x5E4b9aDA947130Fc320a144cd22bC1641e5c9d81', + }, + __typename: 'Asset', + }, + { + id: 'asset-1', + symbol: 'AST1', + name: 'Asset 1', + decimals: 5, + source: { + __typename: 'ERC20', + contractAddress: '0x444b9aDA947130Fc320a144cd22bC1641e5c9d81', + }, + __typename: 'Asset', + }, + ], + }; + + return merge(defaultResult, override); +}; diff --git a/apps/trading-e2e/src/support/mocks/generate-market-list.ts b/apps/trading-e2e/src/support/mocks/generate-market-list.ts index d8dd065ff..a449ce274 100644 --- a/apps/trading-e2e/src/support/mocks/generate-market-list.ts +++ b/apps/trading-e2e/src/support/mocks/generate-market-list.ts @@ -7,7 +7,7 @@ export const generateMarketList = ( ): MarketList => { const markets: MarketList_markets[] = [ { - id: 'market-id', + id: 'market-0', decimalPlaces: 5, data: { market: { @@ -38,7 +38,7 @@ export const generateMarketList = ( __typename: 'Market', }, { - id: 'test-market-suspended', + id: 'market-1', decimalPlaces: 2, data: { market: { diff --git a/apps/trading-e2e/src/support/mocks/generate-market.ts b/apps/trading-e2e/src/support/mocks/generate-market.ts index 9e5244862..5a21d09c4 100644 --- a/apps/trading-e2e/src/support/mocks/generate-market.ts +++ b/apps/trading-e2e/src/support/mocks/generate-market.ts @@ -14,7 +14,7 @@ export interface Market { export const generateMarket = (override?: PartialDeep): Market => { const defaultResult = { market: { - id: 'market-id', + id: 'market-0', name: 'MARKET NAME', __typename: 'Market', }, diff --git a/apps/trading-e2e/src/support/mocks/generate-markets-landing.ts b/apps/trading-e2e/src/support/mocks/generate-markets-landing.ts new file mode 100644 index 000000000..e6bbdaffb --- /dev/null +++ b/apps/trading-e2e/src/support/mocks/generate-markets-landing.ts @@ -0,0 +1,51 @@ +import merge from 'lodash/merge'; +import { MarketTradingMode } from '@vegaprotocol/types'; +import type { DeepPartial } from 'react-hook-form'; + +export interface MarketsLanding_markets_marketTimestamps { + __typename: 'MarketTimestamps'; + open: string | null; +} + +export interface MarketsLanding_markets { + __typename: 'Market'; + id: string; + tradingMode: MarketTradingMode; + marketTimestamps: MarketsLanding_markets_marketTimestamps; +} + +export interface MarketsLanding { + markets: MarketsLanding_markets[] | null; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const generateMarketsLanding = ( + override?: DeepPartial +): MarketsLanding => { + const markets: MarketsLanding_markets[] = [ + { + id: 'market-0', + tradingMode: MarketTradingMode.Continuous, + marketTimestamps: { + __typename: 'MarketTimestamps', + open: '1', + }, + __typename: 'Market', + }, + { + id: 'market-1', + tradingMode: MarketTradingMode.OpeningAuction, + marketTimestamps: { + __typename: 'MarketTimestamps', + open: '2', + }, + __typename: 'Market', + }, + ]; + + const defaultResult: MarketsLanding = { + markets, + }; + + return merge(defaultResult, override); +}; diff --git a/apps/trading-e2e/src/support/mocks/generate-markets.ts b/apps/trading-e2e/src/support/mocks/generate-markets.ts index d90cc13b3..21e358fd8 100644 --- a/apps/trading-e2e/src/support/mocks/generate-markets.ts +++ b/apps/trading-e2e/src/support/mocks/generate-markets.ts @@ -6,7 +6,7 @@ import type { Markets, Markets_markets } from '@vegaprotocol/market-list'; export const generateMarkets = (override?: PartialDeep): Markets => { const markets: Markets_markets[] = [ { - id: 'market-id', + id: 'market-0', name: 'ACTIVE MARKET', decimalPlaces: 5, data: { @@ -38,7 +38,7 @@ export const generateMarkets = (override?: PartialDeep): Markets => { __typename: 'Market', }, { - id: 'test-market-suspended', + id: 'market-1', name: 'SUSPENDED MARKET', decimalPlaces: 2, data: { diff --git a/apps/trading-e2e/src/support/mocks/generate-trades.ts b/apps/trading-e2e/src/support/mocks/generate-trades.ts index 2721e0dc9..4510acb76 100644 --- a/apps/trading-e2e/src/support/mocks/generate-trades.ts +++ b/apps/trading-e2e/src/support/mocks/generate-trades.ts @@ -43,7 +43,7 @@ export const generateTrades = (override?: PartialDeep): Trades => { ]; const defaultResult = { market: { - id: 'market-id', + id: 'market-0', trades, __typename: 'Market', }, diff --git a/apps/trading-e2e/src/support/mocks/generate-withdraw-page-query.ts b/apps/trading-e2e/src/support/mocks/generate-withdraw-page-query.ts new file mode 100644 index 000000000..9c4770149 --- /dev/null +++ b/apps/trading-e2e/src/support/mocks/generate-withdraw-page-query.ts @@ -0,0 +1,60 @@ +import { AccountType } from '@vegaprotocol/types'; +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; + +export const generateWithdrawPageQuery = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + override?: PartialDeep +) => { + const defaultResult = { + party: { + id: 'party-0', + withdrawals: [ + { + id: 'withdrawal-0', + txHash: null, + __typename: 'Withdrawal', + }, + ], + accounts: [ + { + type: AccountType.General, + balance: '100000000', + asset: { + __typename: 'Asset', + id: 'asset-0', + symbol: 'AST0', + }, + __typename: 'Account', + }, + ], + __typename: 'Party', + }, + assets: [ + { + id: 'asset-0', + symbol: 'AST0', + name: 'Asset 0', + decimals: 5, + source: { + __typename: 'ERC20', + contractAddress: '0x5E4b9aDA947130Fc320a144cd22bC1641e5c9d81', + }, + __typename: 'Asset', + }, + { + id: 'asset-1', + symbol: 'AST1', + name: 'Asset 1', + decimals: 5, + source: { + __typename: 'ERC20', + contractAddress: '0x444b9aDA947130Fc320a144cd22bC1641e5c9d81', + }, + __typename: 'Asset', + }, + ], + }; + + return merge(defaultResult, override); +}; diff --git a/apps/trading-e2e/src/support/mocks/generate-withdrawals.ts b/apps/trading-e2e/src/support/mocks/generate-withdrawals.ts new file mode 100644 index 000000000..f25534b37 --- /dev/null +++ b/apps/trading-e2e/src/support/mocks/generate-withdrawals.ts @@ -0,0 +1,58 @@ +import { WithdrawalStatus } from '@vegaprotocol/types'; +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; + +export const generateWithdrawals = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + override?: PartialDeep +) => { + const defaultResult = { + party: { + id: 'party-0', + withdrawals: [ + { + id: 'withdrawal-0', + status: WithdrawalStatus.Finalized, + amount: '100', + txHash: null, + createdTimestamp: new Date().toISOString(), + withdrawnTimestamp: new Date().toISOString(), + details: { + __typename: 'Erc20WithdrawalDetails', + receiverAddress: '0x72c22822A19D20DE7e426fB84aa047399Ddd8853', + }, + asset: { + __typename: 'Asset', + id: 'asset-0', + symbol: 'AST0', + decimals: 5, + }, + __typename: 'Withdrawal', + }, + { + id: 'withdrawal-1', + status: WithdrawalStatus.Finalized, + amount: '100', + txHash: + '0x5d7b1a35ba6bd23be17bb7a159c13cdbb3121fceb94e9c6c510f5503dce48d03', + createdTimestamp: new Date().toISOString(), + withdrawnTimestamp: new Date().toISOString(), + details: { + __typename: 'Erc20WithdrawalDetails', + receiverAddress: '0x72c22822A19D20DE7e426fB84aa047399Ddd8853', + }, + asset: { + __typename: 'Asset', + id: 'asset-0', + symbol: 'AST0', + decimals: 5, + }, + __typename: 'Withdrawal', + }, + ], + __typename: 'Party', + }, + }; + + return merge(defaultResult, override); +}; diff --git a/apps/trading-e2e/src/support/pages/base-page.ts b/apps/trading-e2e/src/support/pages/base-page.ts deleted file mode 100644 index c7e0a0b3c..000000000 --- a/apps/trading-e2e/src/support/pages/base-page.ts +++ /dev/null @@ -1,57 +0,0 @@ -export default class BasePage { - closeDialogBtn = 'dialog-close'; - portfolioUrl = '/portfolio'; - marketsUrl = '/markets'; - assetSelectField = 'select[name="asset"]'; - toAddressField = 'input[name="to"]'; - amountField = 'input[name="amount"]'; - formFieldError = 'input-error-text'; - dialogHeader = 'dialog-title'; - dialogText = 'dialog-text'; - - closeDialog() { - cy.getByTestId(this.closeDialogBtn, { timeout: 12000 })?.click({ - force: true, - }); - } - - navigateToPortfolio() { - cy.get(`a[href='${this.portfolioUrl}']`) - .first() - .should('be.visible') - .click({ force: true }); - cy.url().should('include', '/portfolio'); - } - - navigateToMarkets() { - cy.get(`a[href='${this.marketsUrl}']`) - .first() - .should('be.visible') - .click({ force: true }); - cy.url().should('include', '/markets'); - } - - verifyFormErrorDisplayed(expectedError: string, expectedNumErrors: number) { - cy.getByTestId(this.formFieldError).should('contain.text', expectedError); - cy.getByTestId(this.formFieldError).should( - 'have.length', - expectedNumErrors - ); - } - - updateTransactionForm(args?: { - asset?: string; - to?: string; - amount?: string; - }) { - if (args?.asset) { - cy.get(this.assetSelectField).select(args.asset); - } - if (args?.to) { - cy.get(this.toAddressField).clear().type(args.to); - } - if (args?.amount) { - cy.get(this.amountField).clear().type(args.amount); - } - } -} diff --git a/apps/trading-e2e/src/support/pages/deposits-page.ts b/apps/trading-e2e/src/support/pages/deposits-page.ts deleted file mode 100644 index 65dcf4468..000000000 --- a/apps/trading-e2e/src/support/pages/deposits-page.ts +++ /dev/null @@ -1,55 +0,0 @@ -import BasePage from './base-page'; - -export default class DepositsPage extends BasePage { - requiredText = 'Required'; - assetError = '[role="alert"][aria-describedby="asset"]'; - toError = '[role="alert"][aria-describedby="to"]'; - amountError = '[role="alert"][aria-describedby="amount"]'; - depositSubmitBtn = 'deposit-submit'; - depositApproveSubmitBtn = 'deposit-approve-submit'; - - navigateToDeposits() { - cy.visit('/portfolio/deposit'); - cy.url().should('include', '/portfolio/deposit'); - cy.getByTestId('deposit-form').should('be.visible'); - } - - verifyFormDisplayed() { - cy.getByTestId('deposit-form').should('be.visible'); - } - - checkModalContains(text: string) { - cy.get('[role="dialog"] > div > div > h1').should('have.text', text); - } - - clickDepositSubmit() { - cy.getByTestId(this.depositSubmitBtn).click(); - } - - clickDepositApproveSubmit() { - cy.getByTestId(this.depositApproveSubmitBtn).click(); - } - - verifyInvalidPublicKey() { - cy.get(this.toError).should('have.text', 'Invalid Vega key'); - } - - verifyAmountTooSmall() { - cy.get(this.amountError).should('have.text', 'Value is below minimum'); - } - - verifyInsufficientAmountMessage() { - cy.getByTestId('input-error-text').should( - 'contain.text', - 'Insufficient amount in Ethereum wallet' - ); - } - - verifyNotApproved() { - cy.get(this.amountError).should( - 'have.text', - 'Amount is above approved amount' - ); - cy.contains('Deposits of tBTC not approved').should('be.visible'); - } -} diff --git a/apps/trading-e2e/src/support/pages/home-page.ts b/apps/trading-e2e/src/support/pages/home-page.ts deleted file mode 100644 index 7c8594e0f..000000000 --- a/apps/trading-e2e/src/support/pages/home-page.ts +++ /dev/null @@ -1,80 +0,0 @@ -import BasePage from './base-page'; -import type { OpenMarketType } from '../step_definitions/home-page.step'; - -export default class HomePage extends BasePage { - validateStringIsDisplayedAtTopOfTable(value: string) { - // Ignore header row - cy.get('table tr') - .eq(1) - .within(() => cy.contains(value).should('be.visible')); - } - - getOpenMarketsFromServer() { - const query = `{markets{marketTimestamps{open},tradableInstrument{instrument{code,name}}}}`; - return cy - .request({ - method: 'POST', - url: `https://lb.testnet.vega.xyz/query`, - body: { query }, - headers: { 'content-type': 'application/json' }, - }) - .its('body.data.markets'); - } - - getOpenMarketCodes(openMarkets: OpenMarketType[]) { - const openMarketCodes: string[] = []; - openMarkets.forEach((market: OpenMarketType) => { - openMarketCodes.push(market.tradableInstrument.instrument.code); - }); - return openMarketCodes; - } - - getOldestOpenMarket(openMarkets: OpenMarketType[]) { - const [oldestMarket] = openMarkets.sort( - (a, b) => - new Date(a.marketTimestamps.open).getTime() - - new Date(b.marketTimestamps.open).getTime() - ); - if (!oldestMarket) { - throw new Error('Could not find oldest market'); - } - return oldestMarket; - } - - getMostRecentOpenMarket(openMarkets: OpenMarketType[]) { - const [recentMarket] = openMarkets.sort( - (b, a) => - new Date(a.marketTimestamps.open).getTime() - - new Date(b.marketTimestamps.open).getTime() - ); - if (!recentMarket) { - throw new Error('Could not find most recent market'); - } - return recentMarket; - } - - validateTableCodesExistOnServer(openMarketCodes: string[]) { - cy.get('table tr', { timeout: 12000 }).each(($element, index) => { - if (index > 0) { - // skip header row - const openMarketCodeText: string = $element.children().first().text(); - assert.include( - openMarketCodes, - openMarketCodeText, - `Checking ${openMarketCodeText} is shown within server open markets response` - ); - } - }); - } - - validateTableContainsLastPriceAndChange() { - cy.get('table tr').each(($element, index) => { - if (index > 0) { - // skip header row - cy.root().within(() => { - cy.getByTestId('price').should('not.be.empty'); - }); - } - }); - } -} diff --git a/apps/trading-e2e/src/support/pages/markets-page.ts b/apps/trading-e2e/src/support/pages/markets-page.ts deleted file mode 100644 index 7335b1546..000000000 --- a/apps/trading-e2e/src/support/pages/markets-page.ts +++ /dev/null @@ -1,60 +0,0 @@ -import BasePage from './base-page'; - -export default class MarketPage extends BasePage { - marketRowHeaderClassname = 'div > span.ag-header-cell-text'; - marketRowNameColumn = 'tradableInstrument.instrument.code'; - marketRowSymbolColumn = - 'tradableInstrument.instrument.product.settlementAsset.symbol'; - marketRowPrices = 'flash-cell'; - marketRowDescription = 'name'; - marketStateColId = 'data'; - - validateMarketsAreDisplayed() { - // We need this to ensure that ag-grid is fully rendered before asserting - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(1000); - cy.get('.ag-root-wrapper').should('be.visible'); - } - - validateMarketTableDisplayed() { - const expectedMarketHeaders = [ - 'Market', - 'Settlement asset', - 'State', - 'Best bid', - 'Best offer', - 'Mark price', - 'Description', - ]; - - for (let index = 0; index < expectedMarketHeaders.length; index++) { - cy.get(this.marketRowHeaderClassname).should( - 'contain.text', - expectedMarketHeaders[index] - ); - } - - cy.get(`[col-id='${this.marketRowNameColumn}']`).each(($marketName) => { - cy.wrap($marketName).should('not.be.empty'); - }); - - cy.get(`[col-id='${this.marketRowSymbolColumn}']`).each(($marketSymbol) => { - cy.wrap($marketSymbol).should('not.be.empty'); - }); - - cy.getByTestId(this.marketRowPrices).each(($price) => { - cy.wrap($price).should('not.be.empty').and('contain.text', '.'); - }); - - cy.get(`[col-id='${this.marketRowDescription}']`).each( - ($marketDescription) => { - cy.wrap($marketDescription).should('not.be.empty'); - } - ); - } - - clickOnMarket(text: string) { - cy.get(`[col-id=${this.marketStateColId}]`).should('be.visible'); - cy.get(`[col-id=${this.marketStateColId}]`).contains(text).click(); - } -} diff --git a/apps/trading-e2e/src/support/pages/portfolio-page.ts b/apps/trading-e2e/src/support/pages/portfolio-page.ts deleted file mode 100644 index ba8feb53d..000000000 --- a/apps/trading-e2e/src/support/pages/portfolio-page.ts +++ /dev/null @@ -1,44 +0,0 @@ -import BasePage from './base-page'; - -export default class PortfolioPage extends BasePage { - deposit = 'deposit'; - depositTEuro = 'deposit-tEuro'; - viewWithdrawals = 'view-withdrawals'; - withdraw = 'withdraw'; - withdrawTEuro = 'withdraw-tEuro'; - - navigateToDeposit() { - cy.getByTestId(this.deposit) - .should('have.attr', 'href') - .and('include', '/portfolio/deposit'); - cy.getByTestId(this.deposit).click(); - } - - navigateToDepositTEuro() { - cy.getByTestId(this.depositTEuro) - .should('have.attr', 'href') - .and('include', '/portfolio/deposit?assetId'); - cy.getByTestId(this.depositTEuro).click(); - } - - navigateToWithdrawals() { - cy.getByTestId(this.viewWithdrawals) - .should('have.attr', 'href') - .and('include', '/portfolio/withdrawals'); - cy.getByTestId(this.viewWithdrawals).click(); - } - - navigateToWithdraw() { - cy.getByTestId(this.withdraw) - .should('have.attr', 'href') - .and('include', '/portfolio/withdraw'); - cy.getByTestId(this.withdraw).click(); - } - - navigateToWithdrawTEuro() { - cy.getByTestId(this.withdrawTEuro) - .should('have.attr', 'href') - .and('include', '/portfolio/withdraw?assetId'); - cy.getByTestId(this.withdrawTEuro).click(); - } -} diff --git a/apps/trading-e2e/src/support/pages/trading-page.ts b/apps/trading-e2e/src/support/pages/trading-page.ts deleted file mode 100644 index 837415db8..000000000 --- a/apps/trading-e2e/src/support/pages/trading-page.ts +++ /dev/null @@ -1,41 +0,0 @@ -import BasePage from './base-page'; - -export default class TradingPage extends BasePage { - chartTab = 'Chart'; - ticketTab = 'Ticket'; - orderbookTab = 'Orderbook'; - ordersTab = 'Orders'; - positionsTab = 'Positions'; - accountsTab = 'Accounts'; - collateralTab = 'Collateral'; - tradesTab = 'Trades'; - completedTrades = 'Market-trades'; - - clickOnOrdersTab() { - cy.getByTestId(this.ordersTab).click(); - } - - clickOnAccountsTab() { - cy.getByTestId(this.accountsTab).click(); - } - - clickOnPositionsTab() { - cy.getByTestId(this.positionsTab).click(); - } - - clickOnTicketTab() { - cy.getByTestId(this.ticketTab).click(); - } - - clickOnCollateralTab() { - cy.getByTestId(this.collateralTab).click(); - } - - clickOnTradesTab() { - cy.getByTestId(this.tradesTab).click(); - } - - clickOrderBookTab() { - cy.getByTestId(this.orderbookTab).click(); - } -} diff --git a/apps/trading-e2e/src/support/pages/withdrawals-page.ts b/apps/trading-e2e/src/support/pages/withdrawals-page.ts deleted file mode 100644 index 94bcd8d4b..000000000 --- a/apps/trading-e2e/src/support/pages/withdrawals-page.ts +++ /dev/null @@ -1,94 +0,0 @@ -import BasePage from './base-page'; - -export default class WithdrawalsPage extends BasePage { - useConnectedEthWallet = 'use-connected'; - useMaximumAmount = 'use-maximum'; - submitBtn = 'submit-withdrawal'; - connectVegaWalletText = 'connect-vega-wallet-text'; - assetSymbolColId = 'asset.symbol'; - amountColId = 'amount'; - recipientColdId = 'details.receiverAddress'; - createdAtTimeStampId = 'createdTimestamp'; - statusColId = 'status'; - etherScanLink = 'etherscan-link'; - - clearEthereumAddress() { - cy.get(this.toAddressField).clear(); - } - - clickUseConnected() { - cy.getByTestId(this.useConnectedEthWallet).click(); - } - - clickUseMaximum() { - cy.getByTestId(this.useMaximumAmount).click(); - } - - clickSubmit() { - cy.getByTestId(this.submitBtn).click(); - } - - validateConnectWalletText() { - cy.getByTestId(this.connectVegaWalletText).should( - 'have.text', - 'Connect your Vega wallet' - ); - } - - validateTestWalletEthWalletAddress() { - cy.get(this.toAddressField).should( - 'have.value', - Cypress.env('ETHEREUM_WALLET_ADDRESS') - ); - } - - validateAmount(expectedAmount: string) { - cy.get(this.amountField).should('have.value', expectedAmount); - } - - validateConfirmWithdrawalModal() { - cy.getByTestId(this.dialogHeader).should('have.text', 'Confirm withdrawal'); - cy.getByTestId(this.dialogText).should( - 'have.text', - 'Confirm withdrawal in Vega wallet' - ); - } - - validateWithdrawalAssetDisplayed(assetSymbol: string) { - cy.get(`[col-id="${this.assetSymbolColId}"]`).should( - 'contain.text', - assetSymbol - ); - } - - validateWithdrawalAmountDisplayed(amount: string) { - cy.get(`[col-id="${this.amountColId}"]`).should('contain.text', amount); - } - - validateWithdrawalRecipientDisplayed( - truncatedEthAddress: string, - ethAddressLink: string - ) { - cy.get(`[col-id="${this.recipientColdId}"]`) - .should('contain.text', truncatedEthAddress) - .find(`[data-testid=${this.etherScanLink}]`) - .should('have.attr', 'href', ethAddressLink); - } - - validateWithdrawalDateDisplayed() { - cy.get(`[col-id="${this.createdAtTimeStampId}"]`) - .invoke('text') - .should('not.be.empty'); - } - - validateWithdrawalStatusDisplayed(status: string) { - cy.get(`[col-id="${this.statusColId}"]`).should('contain.text', status); - } - - validateEtherScanLinkDisplayed(txlink: string) { - cy.getByTestId(this.etherScanLink) - .last() - .should('have.text', 'View on Etherscan') - .and('have.attr', 'href', txlink); - } -} diff --git a/apps/trading-e2e/src/support/step_definitions/common.step.ts b/apps/trading-e2e/src/support/step_definitions/common.step.ts deleted file mode 100644 index 19a21d3b9..000000000 --- a/apps/trading-e2e/src/support/step_definitions/common.step.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Given, When } from 'cypress-cucumber-preprocessor/steps'; -import { hasOperationName } from '..'; -import { generateMarketList } from '../mocks/generate-market-list'; -import BasePage from '../pages/base-page'; - -const basePage = new BasePage(); - -Given('I am on the homepage', () => { - cy.mockGQL('MarketsList', (req) => { - if (hasOperationName(req, 'MarketsList')) { - req.reply({ - body: { data: generateMarketList() }, - }); - } - }); - cy.visit('/'); - cy.getByTestId('market', { timeout: 60000 }).should('be.visible', { - timeout: 20000, - }); - cy.contains('Loading...', { timeout: 20000 }).should('not.exist'); -}); - -When('I close the dialog form', () => { - basePage.closeDialog(); -}); - -Given('I can connect to Ethereum', () => { - cy.mockWeb3Provider(); -}); diff --git a/apps/trading-e2e/src/support/step_definitions/deal-ticket.step.ts b/apps/trading-e2e/src/support/step_definitions/deal-ticket.step.ts deleted file mode 100644 index e133de98e..000000000 --- a/apps/trading-e2e/src/support/step_definitions/deal-ticket.step.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Then, When } from 'cypress-cucumber-preprocessor/steps'; -import DealTicket from '../trading-windows/deal-ticket'; - -const dealTicket = new DealTicket(); - -When('I place a buy {string} market order', (orderType) => { - dealTicket.placeMarketOrder(true, '100', orderType); - dealTicket.clickPlaceOrder(); -}); - -When('I place a sell {string} market order', (orderType) => { - dealTicket.placeMarketOrder(false, '100', orderType); - dealTicket.clickPlaceOrder(); -}); - -When('I place a buy {string} limit order', (limitOrderType) => { - dealTicket.placeLimitOrder(true, '100', '2000', limitOrderType); - dealTicket.clickPlaceOrder(); -}); - -When('I place a sell {string} limit order', (limitOrderType) => { - dealTicket.placeLimitOrder(false, '100', '2000', limitOrderType); - dealTicket.clickPlaceOrder(); -}); - -When('I place a buy {string} market order with amount of 0', (orderType) => { - dealTicket.placeMarketOrder(true, '0', orderType); - dealTicket.clickPlaceOrder(); -}); - -Then('order request is sent', () => { - dealTicket.verifyOrderRequestSent(); -}); - -Then('error message for insufficient funds is displayed', () => { - dealTicket.verifyOrderFailedInsufficientFunds(); -}); - -Then('place order button is disabled', () => { - dealTicket.verifyPlaceOrderBtnDisabled(); -}); - -Then('{string} error is shown', (errorMsg) => { - dealTicket.verifySubmitBtnErrorText(errorMsg); -}); - -Then( - 'Order rejected by wallet error shown containing text {string}', - (expectedError) => { - dealTicket.verifyOrderRejected(expectedError); - } -); diff --git a/apps/trading-e2e/src/support/step_definitions/deposits.step.ts b/apps/trading-e2e/src/support/step_definitions/deposits.step.ts deleted file mode 100644 index 928f60426..000000000 --- a/apps/trading-e2e/src/support/step_definitions/deposits.step.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { And, Then, When } from 'cypress-cucumber-preprocessor/steps'; -import { EthereumWallet } from '../ethereum-wallet'; -import DepositsPage from '../pages/deposits-page'; - -const depositsPage = new DepositsPage(); -const ethWallet = new EthereumWallet(); - -Then('I navigate to deposits page', () => { - depositsPage.navigateToDeposits(); -}); - -Then('I can see the eth not connected message {string}', (message) => { - ethWallet.verifyConnectWalletMsg(message); -}); - -And('the connect button is displayed', () => { - ethWallet.verifyEthConnectBtnIsDisplayed(); -}); - -When('I connect my Ethereum wallet', () => { - ethWallet.connect(); -}); - -Then('I can see the deposit form', () => { - depositsPage.verifyFormDisplayed(); -}); - -When('I submit a deposit with empty fields', () => { - depositsPage.updateTransactionForm(); - depositsPage.clickDepositSubmit(); -}); - -Then('I can see empty form validation errors present', () => { - depositsPage.verifyFormErrorDisplayed('Required', 3); -}); - -Then('I enter the following deposit details in deposit form', (table) => { - depositsPage.updateTransactionForm({ - asset: table.rowsHash().asset, - to: Cypress.env(table.rowsHash().to), - amount: table.rowsHash().amount, - }); -}); - -And('I submit the form', () => { - depositsPage.clickDepositSubmit(); -}); - -Then('Invalid Vega key is shown', () => { - depositsPage.verifyInvalidPublicKey(); -}); - -Then('Amount too small message shown', () => { - depositsPage.verifyAmountTooSmall(); -}); - -And('I enter a valid amount', () => { - depositsPage.updateTransactionForm({ amount: '1' }); -}); - -Then('Not approved message shown', () => { - depositsPage.verifyNotApproved(); -}); - -And('I can see the {string} modal is shown', (text) => { - depositsPage.checkModalContains(text); -}); - -And('Insufficient amount message shown', () => { - depositsPage.verifyInsufficientAmountMessage(); -}); diff --git a/apps/trading-e2e/src/support/step_definitions/home-page.step.ts b/apps/trading-e2e/src/support/step_definitions/home-page.step.ts deleted file mode 100644 index c6ec79470..000000000 --- a/apps/trading-e2e/src/support/step_definitions/home-page.step.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { Then, When } from 'cypress-cucumber-preprocessor/steps'; -import VegaWallet from '../vega-wallet'; -import HomePage from '../pages/home-page'; - -const vegaWallet = new VegaWallet(); -const homePage = new HomePage(); - -export interface OpenMarketType { - marketTimestamps: { - open: string; - }; - tradableInstrument: { - instrument: { - code: string; - name: string; - }; - }; -} - -When('I query the server for Open Markets', function () { - homePage.getOpenMarketsFromServer().then((openMarkets: OpenMarketType[]) => { - cy.wrap(openMarkets).as('openMarketData'); - }); -}); - -Then('I am prompted to select a market', () => { - cy.contains('Select a market to get started', { timeout: 20000 }).should( - 'be.visible' - ); -}); - -Then('the choose market overlay is no longer showing', () => { - cy.contains('Select a market to get started').should('not.exist'); - cy.contains('Loading...', { timeout: 20000 }).should('not.exist'); -}); - -Then( - 'the server contains at least {int} open markets', - (expectedNumber: number) => { - cy.get('@openMarketData') - .its('length') - .should('be.at.least', expectedNumber); - } -); - -Then( - 'I expect the market overlay table to contain at least {int} rows', - (expectedNumber: number) => { - cy.get('table tr').then((row) => { - expect(row.length).to.be.at.least(expectedNumber); - }); - } -); - -Then( - 'each market shown in overlay table exists as open market on server', - () => { - const arrayOfOpenMarketCodes: string[] = []; - cy.get('@openMarketData') - .each((openMarket: OpenMarketType) => { - arrayOfOpenMarketCodes.push( - openMarket.tradableInstrument.instrument.code - ); - }) - .then(() => { - homePage.validateTableCodesExistOnServer(arrayOfOpenMarketCodes); - }); - } -); - -Then( - 'each market shown in overlay table contains content under the last price and change fields', - () => { - homePage.validateTableContainsLastPriceAndChange(); - } -); - -Then( - 'each market shown in the full list exists as open market on server', - () => { - cy.get('@openMarketData').each((openMarket: OpenMarketType) => { - cy.contains(openMarket.tradableInstrument.instrument.code).should( - 'be.visible' - ); - }); - } -); - -Then( - 'the oldest market trading in continous mode shown at top of overlay table', - () => { - cy.get('@openMarketData').then( - (openMarkets: OpenMarketType[]) => { - const oldestMarket = homePage.getOldestOpenMarket(openMarkets); - homePage.validateStringIsDisplayedAtTopOfTable( - oldestMarket.tradableInstrument.instrument.code - ); - } - ); - } -); - -Then('the oldest current trading market is loaded on the trading tab', () => { - cy.get('@openMarketData').then( - (openMarkets: OpenMarketType[]) => { - const oldestMarket = homePage.getOldestOpenMarket(openMarkets); - cy.getByTestId('market', { timeout: 12000 }).within(() => { - cy.get('button') - .contains(oldestMarket.tradableInstrument.instrument.name) - .should('be.visible'); - }); - } - ); -}); - -Then( - 'the most recent current trading market is loaded on the trading tab', - () => { - cy.get('@openMarketData').then( - (openMarkets: OpenMarketType[]) => { - const latestMarket = homePage.getMostRecentOpenMarket(openMarkets); - cy.getByTestId('market', { timeout: 12000 }).within(() => { - cy.get('button') - .contains(latestMarket.tradableInstrument.instrument.name) - .should('be.visible'); - }); - } - ); - } -); - -When('I click the most recent trading market', () => { - cy.get('@openMarketData').then( - (openMarkets: OpenMarketType[]) => { - const latestMarket = homePage.getMostRecentOpenMarket(openMarkets); - cy.contains(latestMarket.tradableInstrument.instrument.code).click(); - } - ); -}); - -When('I click the view full market list', () => { - cy.contains('Or view full market list').click(); - cy.contains('Loading...').should('be.visible'); - cy.contains('Loading...').should('not.exist'); -}); - -When('I try to connect Vega wallet with incorrect details', () => { - vegaWallet.openVegaWalletConnectDialog(); - vegaWallet.fillInWalletForm('name', 'wrong passphrase'); - vegaWallet.clickConnectVegaWallet(); -}); - -When('I try to connect Vega wallet with blank fields', () => { - vegaWallet.openVegaWalletConnectDialog(); - vegaWallet.clickConnectVegaWallet(); -}); - -Then('wallet not running error message is displayed', () => { - vegaWallet.validateWalletNotRunningError(); -}); - -Then('wallet field validation errors are shown', () => { - vegaWallet.validateWalletErrorFieldsDisplayed(); -}); diff --git a/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts b/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts deleted file mode 100644 index d691fe63c..000000000 --- a/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { And, Given, Then, When } from 'cypress-cucumber-preprocessor/steps'; -import { hasOperationName } from '..'; -import { generateMarkets } from '../mocks/generate-markets'; -import MarketsPage from '../pages/markets-page'; - -const marketsPage = new MarketsPage(); - -const mockMarkets = () => { - cy.log('Mocking markets query'); - cy.mockGQL('Markets', (req) => { - if (hasOperationName(req, 'Markets')) { - req.reply({ - body: { data: generateMarkets() }, - }); - } - }); -}; - -Then('I navigate to markets page', () => { - mockMarkets(); - marketsPage.navigateToMarkets(); - cy.wait('@Markets'); -}); - -Then('I can view markets', () => { - marketsPage.validateMarketsAreDisplayed(); -}); - -Given('I am on the markets page', () => { - mockMarkets(); - cy.visit('/markets'); - cy.wait('@Markets'); -}); - -Then('I can view markets', () => { - marketsPage.validateMarketsAreDisplayed(); -}); - -And('the market table is displayed', () => { - marketsPage.validateMarketTableDisplayed(); -}); - -When('I click on {string} market', (Expectedmarket) => { - marketsPage.clickOnMarket(Expectedmarket); -}); diff --git a/apps/trading-e2e/src/support/step_definitions/portfolio-page.step.ts b/apps/trading-e2e/src/support/step_definitions/portfolio-page.step.ts deleted file mode 100644 index acfaffb49..000000000 --- a/apps/trading-e2e/src/support/step_definitions/portfolio-page.step.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Then } from 'cypress-cucumber-preprocessor/steps'; -import PortfolioPage from '../pages/portfolio-page'; - -const portfolioPage = new PortfolioPage(); - -Then('I navigate to portfolio page', () => { - portfolioPage.navigateToPortfolio(); -}); diff --git a/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts b/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts deleted file mode 100644 index de57f231f..000000000 --- a/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; -import { hasOperationName } from '..'; -import { MarketState } from '@vegaprotocol/types'; -import { generateChart } from '../mocks/generate-chart'; -import { generateCandles } from '../mocks/generate-candles'; -import { generateTrades } from '../mocks/generate-trades'; -import { generateDealTicketQuery } from '../mocks/generate-deal-ticket-query'; -import { generateMarket } from '../mocks/generate-market'; -import { generateOrders } from '../mocks/generate-orders'; -import { generatePositions } from '../mocks/generate-positions'; -import { generateOrderBook } from '../mocks/generate-order-book'; -import { generateAccounts } from '../mocks/generate-accounts'; -import PositionsList from '../trading-windows/positions-list'; -import AccountsList from '../trading-windows/accounts-list'; -import TradesList from '../trading-windows/trades-list'; -import TradingPage from '../pages/trading-page'; -import OrdersList from '../trading-windows/orders-list'; -import OrderBookList from '../trading-windows/orderbook-list'; -import MarketPage from '../pages/markets-page'; - -const tradesList = new TradesList(); -const tradingPage = new TradingPage(); -const positionsList = new PositionsList(); -const accountList = new AccountsList(); -const ordersList = new OrdersList(); -const orderBookList = new OrderBookList(); -const marketPage = new MarketPage(); - -const mockMarket = (state: MarketState) => { - cy.mockGQL('Market', (req) => { - if (hasOperationName(req, 'Market')) { - req.reply({ - body: { - data: generateMarket({ - market: { - name: `${state.toUpperCase()} MARKET`, - }, - }), - }, - }); - } - - if (hasOperationName(req, 'Orders')) { - req.reply({ - body: { data: generateOrders() }, - }); - } - - if (hasOperationName(req, 'Accounts')) { - req.reply({ - body: { - data: generateAccounts(), - }, - }); - } - - if (hasOperationName(req, 'Positions')) { - req.reply({ - body: { data: generatePositions() }, - }); - } - - if (hasOperationName(req, 'DealTicketQuery')) { - req.reply({ - body: { data: generateDealTicketQuery({ market: { state } }) }, - }); - } - - if (hasOperationName(req, 'Trades')) { - req.reply({ - body: { data: generateTrades() }, - }); - } - - if (hasOperationName(req, 'Chart')) { - req.reply({ - body: { data: generateChart() }, - }); - } - - if (hasOperationName(req, 'MarketDepth')) { - req.reply({ - body: { data: generateOrderBook() }, - }); - } - - if (hasOperationName(req, 'Candles')) { - req.reply({ - body: { data: generateCandles() }, - }); - } - }); -}; - -Given('I am on the trading page for an active market', () => { - mockMarket(MarketState.Active); - - cy.visit('/markets/market-id'); - cy.wait('@Market'); - cy.contains('ACTIVE MARKET'); -}); - -Given('I am on the trading page for a suspended market', () => { - mockMarket(MarketState.Suspended); - - cy.visit('/markets/market-id'); - cy.wait('@Market'); - cy.contains('SUSPENDED MARKET'); -}); - -When('I click on {string} mocked market', (marketType) => { - switch (marketType) { - case 'Active': - mockMarket(MarketState.Active); - break; - case 'Suspended': - mockMarket(MarketState.Suspended); - break; - } - marketPage.clickOnMarket(marketType); -}); - -Then('trading page for {string} market is displayed', (marketType) => { - switch (marketType) { - case 'active': - cy.wait('@Market'); - cy.contains('ACTIVE MARKET'); - break; - case 'suspended': - cy.wait('@Market'); - cy.contains('SUSPENDED MARKET'); - break; - } - tradingPage.clickOnTradesTab(); - tradesList.verifyTradesListDisplayed(); -}); - -When('I click on orders tab', () => { - tradingPage.clickOnOrdersTab(); -}); - -Then('placed orders are displayed', () => { - ordersList.verifyOrdersDisplayed(); -}); - -When('I click on accounts tab', () => { - tradingPage.clickOnAccountsTab(); -}); - -Then('accounts are displayed', () => { - accountList.verifyAccountsDisplayed(); -}); - -Then('I can see account for tEURO', () => { - accountList.verifySingleAccountDisplayed( - 'General-tEURO-null', - 'tEURO', - 'General', - '—', - '1,000.00000' - ); -}); - -When('I click on positions tab', () => { - tradingPage.clickOnPositionsTab(); -}); - -Then('positions are displayed', () => { - positionsList.verifyPositionsDisplayed(); -}); - -When('I click on order book tab', () => { - tradingPage.clickOrderBookTab(); -}); - -Then('orderbook is displayed with expected orders', () => { - orderBookList.verifyOrderBookRow('826342', '0', '8.26342', '264', '1488'); - orderBookList.verifyOrderBookRow('826336', '1475', '8.26336', '0', '1675'); - orderBookList.verifyDisplayedVolume( - '826342', - false, - '18%', - orderBookList.testingVolume.AskVolume - ); - orderBookList.verifyDisplayedVolume( - '826331', - true, - '100%', - orderBookList.testingVolume.CumulativeVolume - ); - // mid level price - orderBookList.verifyOrderBookRow('826337', '0', '8.26337', '0', '200'); - orderBookList.verifyDisplayedVolume( - '826337', - true, - '6%', - orderBookList.testingVolume.CumulativeVolume - ); - orderBookList.verifyTopMidPricePosition('129'); - orderBookList.verifyBottomMidPricePosition('151'); - - // autofilled order - orderBookList.verifyOrderBookRow('826330', '0', '8.26330', '0', '3548'); - orderBookList.verifyDisplayedVolume( - '826330', - true, - '0%', - orderBookList.testingVolume.BidVolume - ); - orderBookList.verifyDisplayedVolume( - '826330', - true, - '100%', - orderBookList.testingVolume.CumulativeVolume - ); -}); - -Then('orderbook can be reduced and expanded', () => { - orderBookList.changePrecision('10'); - orderBookList.verifyOrderBookRow( - '82634', - '1868', - '8.2634', - '1488', - '1488/1868' - ); - orderBookList.verifyCumulativeAskBarPercentage('42%'); - orderBookList.verifyCumulativeBidBarPercentage('53%'); - orderBookList.changePrecision('100'); - orderBookList.verifyOrderBookRow('8263', '3568', '8.263', '1488', ''); - orderBookList.verifyDisplayedVolume( - '8263', - true, - '100%', - orderBookList.testingVolume.BidVolume - ); - orderBookList.verifyDisplayedVolume( - '8263', - false, - '42%', - orderBookList.testingVolume.AskVolume - ); - orderBookList.changePrecision('1'); - orderBookList.verifyOrderBookRow('826342', '0', '8.26342', '264', '1488'); -}); diff --git a/apps/trading-e2e/src/support/step_definitions/vega-wallet.step.ts b/apps/trading-e2e/src/support/step_definitions/vega-wallet.step.ts deleted file mode 100644 index 52590e636..000000000 --- a/apps/trading-e2e/src/support/step_definitions/vega-wallet.step.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { When, Then } from 'cypress-cucumber-preprocessor/steps'; -import VegaWallet from '../vega-wallet'; - -const vegaWallet = new VegaWallet(); - -beforeEach(() => { - // The two important values here are the signature.value and the From.Pubkey. - // From.PubKey is the first public key in the UI_Trading_Test wallet. We assert that the returned pubkey matches - // what is used to sign the transaction - // The tx.signature.value isn't currently used in any tests but the value returned is used to create IDs objects such - // as orders, if its invalid an erro will be thrown - cy.mockVegaCommandSync({ - txHash: 'test-tx-hash', - tx: { - signature: { - value: - 'd86138bba739bbc1069b3dc975d20b3a1517c2b9bdd401c70eeb1a0ecbc502ec268cf3129824841178b8b506b0b7d650c76644dbd96f524a6cb2158fb7121800', - }, - }, - }); -}); - -When('I connect to Vega Wallet', () => { - vegaWallet.openVegaWalletConnectDialog(); - vegaWallet.fillInWalletForm( - Cypress.env('TRADING_TEST_VEGA_WALLET_NAME'), - Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE') - ); - vegaWallet.clickConnectVegaWallet(); - vegaWallet.validateWalletConnected(); -}); - -When('I open wallet dialog', () => { - vegaWallet.validatePublicKeyDisplayed( - Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY') - ); // Default Test wallet pub key - vegaWallet.clickOnWalletConnectDialog(); -}); - -When('select a different public key', () => { - vegaWallet.selectPublicKey(); -}); - -When('I disconnect my Vega wallet', () => { - vegaWallet.validatePublicKeyDisplayed( - Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY') - ); - vegaWallet.clickOnWalletConnectDialog(); - vegaWallet.clickDisconnectAllKeys(); -}); - -Then('public key is switched', () => { - vegaWallet.validatePublicKeyDisplayed( - Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY2') - ); // Second public key for test wallet -}); diff --git a/apps/trading-e2e/src/support/step_definitions/withdrawals.step.ts b/apps/trading-e2e/src/support/step_definitions/withdrawals.step.ts deleted file mode 100644 index 6d44a0e8e..000000000 --- a/apps/trading-e2e/src/support/step_definitions/withdrawals.step.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; -import PortfolioPage from '../pages/portfolio-page'; -import WithdrawalsPage from '../pages/withdrawals-page'; - -const portfolioPage = new PortfolioPage(); -const withdrawalsPage = new WithdrawalsPage(); - -Given('I navigate to withdrawal page', () => { - cy.visit('/'); - portfolioPage.closeDialog(); - - // portfolioPage.navigateToPortfolio(); - // portfolioPage.navigateToWithdraw(); - // Navigation functions commented out due to button being removed and not added back in yet - cy.visit('/portfolio/withdraw'); -}); - -Given('I navigate to withdrawals page', () => { - // portfolioPage.navigateToPortfolio(); - // portfolioPage.navigateToWithdrawals(); - // Navigation functions commented out due to button being removed and not added back in yet - cy.visit('/portfolio/withdrawals'); -}); - -When('I clear ethereum address', () => { - withdrawalsPage.clearEthereumAddress(); -}); - -When('click submit', () => { - withdrawalsPage.clickSubmit(); -}); - -When('I enter an invalid ethereum address', () => { - withdrawalsPage.updateTransactionForm({ - to: '0x0dAAACaa868f87BB4666F918742141cAEAe893Fa', - }); - withdrawalsPage.clickSubmit(); -}); - -When('I select {string}', (selectedAsset) => { - withdrawalsPage.updateTransactionForm({ - asset: selectedAsset, - }); -}); - -When('ethereum address is connected Ethereum wallet', () => { - withdrawalsPage.validateTestWalletEthWalletAddress(); -}); - -When('I click Use maximum', () => { - withdrawalsPage.clickUseMaximum(); -}); - -When('I enter the following details in withdrawal form', (table) => { - withdrawalsPage.updateTransactionForm({ - asset: table.rowsHash().asset, - to: table.rowsHash().to, - amount: table.rowsHash().amount, - }); - withdrawalsPage.clickSubmit(); -}); - -When('I succesfully fill in and submit withdrawal form', () => { - withdrawalsPage.updateTransactionForm({ - asset: Cypress.env('WITHDRAWAL_ASSET_ID'), - amount: '0.1', - }); - withdrawalsPage.clickSubmit(); -}); - -Then('errors are displayed for empty fields', () => { - withdrawalsPage.verifyFormErrorDisplayed('Required', 3); -}); - -Then('error for invalid ethereum address is displayed', () => { - // Expecting empty field errors to still be displayed - withdrawalsPage.verifyFormErrorDisplayed('Invalid Ethereum address', 3); -}); - -Then('connect to Vega wallet is displayed', () => { - withdrawalsPage.validateConnectWalletText(); -}); - -Then('expected amount is {string}', (expectedAmount) => { - withdrawalsPage.validateAmount(expectedAmount); -}); - -Then('withdrawal modal is displayed', () => { - withdrawalsPage.validateConfirmWithdrawalModal(); -}); - -Then('error for below minumum amount is displayed', () => { - withdrawalsPage.verifyFormErrorDisplayed('Value is below minimum', 1); -}); - -Then('error for above maximum amount is displayed', () => { - withdrawalsPage.verifyFormErrorDisplayed('Value is above maximum', 1); -}); - -Then('history of withdrawals are displayed', () => { - const ethAddressLink = `${Cypress.env('ETHERSCAN_URL')}/address/${Cypress.env( - 'ETHEREUM_WALLET_ADDRESS' - )}`; - const etherScanLink = `${Cypress.env( - 'ETHERSCAN_URL' - )}/tx/0x0d1a5d209f468ff248326d4ae7647ad5a3667ce463341a0250118a95f3beb597`; - - withdrawalsPage.validateWithdrawalAssetDisplayed('tEURO'); - withdrawalsPage.validateWithdrawalAmountDisplayed('10,000.00000'); - withdrawalsPage.validateWithdrawalRecipientDisplayed( - '0x265C…807158', - ethAddressLink - ); - withdrawalsPage.validateWithdrawalDateDisplayed(); - withdrawalsPage.validateWithdrawalStatusDisplayed('Finalized'); - withdrawalsPage.validateEtherScanLinkDisplayed(etherScanLink); -}); diff --git a/apps/trading-e2e/src/support/trading-windows/accounts-list.ts b/apps/trading-e2e/src/support/trading-windows/accounts-list.ts deleted file mode 100644 index 89b8d95c6..000000000 --- a/apps/trading-e2e/src/support/trading-windows/accounts-list.ts +++ /dev/null @@ -1,44 +0,0 @@ -export default class AccountsList { - accountSymbolColId = 'asset.symbol'; - accountTypeColId = 'type'; - accountMarketNameColId = 'market.name'; - accountBalanceColId = 'balance'; - - verifyAccountsDisplayed() { - cy.get(`[col-id='${this.accountSymbolColId}']`).each(($accountSymbol) => { - cy.wrap($accountSymbol).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.accountTypeColId}']`).each(($accountType) => { - cy.wrap($accountType).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.accountMarketNameColId}']`).each( - ($accountMarketName) => { - cy.wrap($accountMarketName).invoke('text').should('not.be.empty'); - } - ); - cy.get(`[col-id='${this.accountBalanceColId}']`).each(($accountBalance) => { - cy.wrap($accountBalance).invoke('text').should('not.be.empty'); - }); - } - - verifySingleAccountDisplayed( - accountRowId: string, - accountSymbol: string, - accountType: string, - accountMarketName: string, - accountBalance: string - ) { - cy.get(`[row-id='${accountRowId}']`) - .find(`[col-id='${this.accountSymbolColId}']`) - .should('have.text', accountSymbol); - cy.get(`[row-id='${accountRowId}']`) - .find(`[col-id='${this.accountTypeColId}']`) - .should('have.text', accountType); - cy.get(`[row-id='${accountRowId}']`) - .find(`[col-id='${this.accountMarketNameColId}']`) - .should('have.text', accountMarketName); - cy.get(`[row-id='${accountRowId}']`) - .find(`[col-id='${this.accountBalanceColId}']`) - .should('have.text', accountBalance); - } -} diff --git a/apps/trading-e2e/src/support/trading-windows/deal-ticket.ts b/apps/trading-e2e/src/support/trading-windows/deal-ticket.ts deleted file mode 100644 index 0821ad813..000000000 --- a/apps/trading-e2e/src/support/trading-windows/deal-ticket.ts +++ /dev/null @@ -1,122 +0,0 @@ -export default class DealTicket { - marketOrderType = 'order-type-TYPE_MARKET'; - limitOrderType = 'order-type-TYPE_LIMIT'; - buyOrder = 'order-side-SIDE_BUY'; - sellOrder = 'order-side-SIDE_SELL'; - orderSizeField = 'order-size'; - orderPriceField = 'order-price'; - orderTypeDropDown = 'order-tif'; - datePickerField = 'date-picker-field'; - placeOrderBtn = 'place-order'; - placeOrderFormError = 'dealticket-error-message'; - orderDialog = 'order-wrapper'; - orderStatusHeader = 'order-status-header'; - orderTransactionHash = 'tx-hash'; - orderErrorTxt = 'error-reason'; - - placeMarketOrder(isBuy: boolean, orderSize: string, orderType: string) { - cy.get(`[data-testid=${this.placeOrderBtn}]`, { timeout: 8000 }).should( - 'be.visible' - ); - - if (isBuy == false) { - cy.getByTestId(this.sellOrder)?.click(); - } - - cy.getByTestId(this.orderSizeField)?.clear().type(orderSize); - cy.getByTestId(this.orderTypeDropDown)?.select(orderType); - } - - placeLimitOrder( - isBuy: boolean, - orderSize: string, - orderPrice: string, - orderType: string - ) { - cy.getByTestId(this.limitOrderType)?.click(); - - if (isBuy == false) { - cy.getByTestId(this.sellOrder)?.click(); - } - - cy.getByTestId(this.orderSizeField).clear().type(orderSize); - cy.getByTestId(this.orderPriceField).clear().type(orderPrice); - cy.getByTestId(this.orderTypeDropDown).select(orderType); - - if (orderType == 'GTT') { - const today = new Date(new Date().setSeconds(0)); - const futureDate = new Date(today.setMonth(today.getMonth() + 1)); // set expiry to one month from now - const formattedDate = this.formatDate(futureDate); - cy.getByTestId(this.datePickerField).click().type(formattedDate); - } - } - - verifyOrderRequestSent() { - cy.getByTestId(this.orderStatusHeader).should( - 'have.text', - 'Awaiting network confirmation' - ); - cy.getByTestId(this.orderTransactionHash) - .invoke('text') - .should('contain', 'Tx hash: test-tx-hash'); - } - - verifyOrderFailedInsufficientFunds() { - cy.get(`[data-testid=${this.orderErrorTxt}]`, { timeout: 8000 }).should( - 'have.text', - 'Reason: InsufficientAssetBalance' - ); - } - - clickPlaceOrder() { - cy.getByTestId(this.placeOrderBtn).click(); - cy.contains('Awaiting network confirmation'); - } - - verifyPlaceOrderBtnDisabled() { - cy.getByTestId(this.placeOrderBtn).should('be.disabled'); - } - - verifySubmitBtnErrorText(expectedText: string) { - cy.getByTestId('dealticket-error-message').should( - 'have.text', - expectedText - ); - } - - verifyOrderRejected(errorMsg: string) { - cy.getByTestId(this.orderStatusHeader).should( - 'have.text', - 'Order rejected by wallet' - ); - cy.getByTestId(this.orderDialog) - .find('pre') - .should('contain.text', errorMsg); - } - - reloadPageIfPublicKeyErrorDisplayed() { - cy.get('body').then(($body) => { - if ($body.find(`[data-testid=${this.placeOrderFormError}]`).length) { - cy.getByTestId(this.placeOrderFormError) - .invoke('text') - .then(($errorText) => { - if ($errorText == 'No public key selected') { - cy.reload; - } - }); - } - }); - } - - formatDate(date: Date) { - const padZero = (num: number) => num.toString().padStart(2, '0'); - - const year = date.getFullYear(); - const month = padZero(date.getMonth() + 1); - const day = padZero(date.getDate()); - const hours = padZero(date.getHours()); - const minutes = padZero(date.getMinutes()); - - return `${year}-${month}-${day}T${hours}:${minutes}`; - } -} diff --git a/apps/trading-e2e/src/support/trading-windows/orders-list.ts b/apps/trading-e2e/src/support/trading-windows/orders-list.ts deleted file mode 100644 index c4348d98e..000000000 --- a/apps/trading-e2e/src/support/trading-windows/orders-list.ts +++ /dev/null @@ -1,37 +0,0 @@ -export default class OrdersList { - orderSymbol = 'market.tradableInstrument.instrument.code'; - orderSize = 'size'; - orderType = 'type'; - orderStatus = 'status'; - orderRemaining = 'remaining'; - orderPrice = 'price'; - orderTimeInForce = 'timeInForce'; - orderCreatedAt = 'createdAt'; - - verifyOrdersDisplayed() { - cy.get(`[col-id='${this.orderSymbol}']`).each(($symbol) => { - cy.wrap($symbol).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.orderSize}']`).each(($size) => { - cy.wrap($size).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.orderType}']`).each(($type) => { - cy.wrap($type).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.orderStatus}']`).each(($status) => { - cy.wrap($status).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.orderRemaining}']`).each(($remaining) => { - cy.wrap($remaining).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.orderPrice}']`).each(($price) => { - cy.wrap($price).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.orderTimeInForce}']`).each(($timeInForce) => { - cy.wrap($timeInForce).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.orderCreatedAt}']`).each(($dateTime) => { - cy.wrap($dateTime).invoke('text').should('not.be.empty'); - }); - } -} diff --git a/apps/trading-e2e/src/support/trading-windows/positions-list.ts b/apps/trading-e2e/src/support/trading-windows/positions-list.ts deleted file mode 100644 index 0e9439160..000000000 --- a/apps/trading-e2e/src/support/trading-windows/positions-list.ts +++ /dev/null @@ -1,18 +0,0 @@ -export default class PositionsList { - positionMarketSymbol = 'market.tradableInstrument.instrument.code'; - positionOpenVolume = 'openVolume'; - positionPrices = 'flash-cell'; - - verifyPositionsDisplayed() { - cy.get(`[col-id='${this.positionMarketSymbol}']`).each(($marketSymbol) => { - cy.wrap($marketSymbol).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id='${this.positionOpenVolume}']`).each(($openVolume) => { - cy.wrap($openVolume).invoke('text').should('not.be.empty'); - }); - // includes average entry price, mark price & realised PNL - cy.getByTestId(this.positionPrices).each(($prices) => { - cy.wrap($prices).invoke('text').should('not.be.empty'); - }); - } -} diff --git a/apps/trading-e2e/src/support/trading-windows/trades-list.ts b/apps/trading-e2e/src/support/trading-windows/trades-list.ts deleted file mode 100644 index 04a8a2fdd..000000000 --- a/apps/trading-e2e/src/support/trading-windows/trades-list.ts +++ /dev/null @@ -1,23 +0,0 @@ -export default class TradesList { - colIdPrice = 'price'; - colIdSize = 'size'; - colIdCreatedAt = 'createdAt'; - - verifyTradesListDisplayed() { - cy.get(`[col-id=${this.colIdPrice}]`).each(($tradePrice) => { - cy.wrap($tradePrice).invoke('text').should('not.be.empty'); - }); - cy.get(`[col-id=${this.colIdSize}]`).each(($tradeSize) => { - cy.wrap($tradeSize).invoke('text').should('not.be.empty'); - }); - - const dateTimeRegex = - /(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm; - cy.get(`[col-id=${this.colIdCreatedAt}]`).each(($tradeDateTime, index) => { - if (index != 0) { - //ignore header - cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex); - } - }); - } -} diff --git a/apps/trading-e2e/src/support/trading.ts b/apps/trading-e2e/src/support/trading.ts new file mode 100644 index 000000000..f906805c0 --- /dev/null +++ b/apps/trading-e2e/src/support/trading.ts @@ -0,0 +1,70 @@ +import type { MarketState } from '@vegaprotocol/types'; +import { hasOperationName } from '.'; +import { generateAccounts } from './mocks/generate-accounts'; +import { generateCandles } from './mocks/generate-candles'; +import { generateChart } from './mocks/generate-chart'; +import { generateDealTicketQuery } from './mocks/generate-deal-ticket-query'; +import { generateMarket } from './mocks/generate-market'; +import { generateOrders } from './mocks/generate-orders'; +import { generatePositions } from './mocks/generate-positions'; +import { generateTrades } from './mocks/generate-trades'; + +export const mockTradingPage = (state: MarketState) => { + cy.mockGQL('Market', (req) => { + if (hasOperationName(req, 'Market')) { + req.reply({ + body: { + data: generateMarket({ + market: { + name: `${state.toUpperCase()} MARKET`, + }, + }), + }, + }); + } + + if (hasOperationName(req, 'Orders')) { + req.reply({ + body: { data: generateOrders() }, + }); + } + + if (hasOperationName(req, 'Accounts')) { + req.reply({ + body: { + data: generateAccounts(), + }, + }); + } + + if (hasOperationName(req, 'Positions')) { + req.reply({ + body: { data: generatePositions() }, + }); + } + + if (hasOperationName(req, 'DealTicketQuery')) { + req.reply({ + body: { data: generateDealTicketQuery({ market: { state } }) }, + }); + } + + if (hasOperationName(req, 'Trades')) { + req.reply({ + body: { data: generateTrades() }, + }); + } + + if (hasOperationName(req, 'Chart')) { + req.reply({ + body: { data: generateChart() }, + }); + } + + if (hasOperationName(req, 'Candles')) { + req.reply({ + body: { data: generateCandles() }, + }); + } + }); +}; diff --git a/apps/trading-e2e/src/support/vega-wallet.ts b/apps/trading-e2e/src/support/vega-wallet.ts new file mode 100644 index 000000000..c1095a11c --- /dev/null +++ b/apps/trading-e2e/src/support/vega-wallet.ts @@ -0,0 +1,13 @@ +export const connectVegaWallet = () => { + const form = 'rest-connector-form'; + const manageVegaBtn = 'manage-vega-wallet'; + const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME'); + const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE'); + + cy.getByTestId('connect-vega-wallet').click(); + cy.getByTestId('connectors-list').find('button').click(); + cy.getByTestId(form).find('#wallet').click().type(walletName); + cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase); + cy.getByTestId('rest-connector-form').find('button[type=submit]').click(); + cy.getByTestId(manageVegaBtn).should('exist'); +}; diff --git a/apps/trading-e2e/src/support/vega-wallet/index.ts b/apps/trading-e2e/src/support/vega-wallet/index.ts deleted file mode 100644 index cad94c17c..000000000 --- a/apps/trading-e2e/src/support/vega-wallet/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -export default class VegaWallet { - connectVegaBtn = 'connect-vega-wallet'; - walletConnectors = 'connectors-list'; - walletForm = 'rest-connector-form'; - selectPublicKeyBtn = 'select-keypair-button'; - disconnectAllKeysBtn = 'disconnect'; - walletInputError = 'input-wallet-error'; - walletFormError = 'form-error'; - inputError = 'input-error-text'; - connectNetworkBtn = 'connect-network'; - - openVegaWalletConnectDialog() { - this.clickOnWalletConnectDialog(); - cy.contains('Connects using REST to a running Vega wallet service'); - cy.getByTestId(this.walletConnectors).find('button').click(); - } - - fillInWalletForm(walletName: string, walletPassphrase: string) { - cy.getByTestId(this.walletForm) - .find('#wallet') - .click({ force: true }) - .type(walletName); - cy.getByTestId(this.walletForm) - .find('#passphrase') - .click({ force: true }) - .type(walletPassphrase); - } - - clickConnectVegaWallet() { - cy.getByTestId(this.walletForm) - .find('button[type=submit]') - .click({ force: true }); - } - - validateWalletNotRunningError() { - cy.getByTestId(this.walletFormError).should( - 'have.text', - 'Authentication failed' - ); - } - - validateWalletErrorFieldsDisplayed() { - cy.getByTestId(this.walletInputError).should('have.text', 'Required'); - cy.getByTestId(this.inputError).should('have.text', 'Required'); - } - - validatePublicKeyDisplayed(expectedTruncatedKey: string) { - cy.getByTestId(this.connectVegaBtn).should( - 'have.text', - expectedTruncatedKey - ); - } - - validateWalletConnected() { - cy.getByTestId(this.connectVegaBtn).should('contain.text', '…'); - } - - selectPublicKey() { - cy.getByTestId(this.selectPublicKeyBtn).first().click(); - } - - clickOnWalletConnectDialog() { - cy.getByTestId(this.connectVegaBtn).click({ force: true }); - } - - clickDisconnectAllKeys() { - cy.getByTestId(this.disconnectAllKeysBtn).click(); - } -} diff --git a/apps/trading/components/grid-tabs/grid-tabs.tsx b/apps/trading/components/grid-tabs/grid-tabs.tsx index 5556d281f..343618de1 100644 --- a/apps/trading/components/grid-tabs/grid-tabs.tsx +++ b/apps/trading/components/grid-tabs/grid-tabs.tsx @@ -47,7 +47,11 @@ export const GridTabs = ({ children }: GridTabsProps) => { {Children.map(children, (child) => { if (!isValidElement(child)) return null; return ( - + {child.props.children} ); @@ -63,6 +67,6 @@ interface GridTabProps { name: string; } -export const GridTab = ({ children }: GridTabProps) => { +export const GridTab = ({ id, children }: GridTabProps) => { return
      {children}
      ; }; diff --git a/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx b/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx index 7f3be067c..2822ea561 100644 --- a/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx +++ b/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx @@ -27,7 +27,7 @@ export const VegaWalletConnectButton = ({ Vega key: )}
    {t('Minimum')}{minLimit}
    {t('Maximum')} {maxLimit} - {t('against')} - {t('against')}
    diff --git a/libs/wallet/src/rest-connector-form.tsx b/libs/wallet/src/rest-connector-form.tsx index 097c045fd..37005a71e 100644 --- a/libs/wallet/src/rest-connector-form.tsx +++ b/libs/wallet/src/rest-connector-form.tsx @@ -60,11 +60,7 @@ export function RestConnectorForm({ autoFocus={true} /> {errors.wallet?.message && ( - + {errors.wallet.message} )} From ff8990c2578bbfb7f605aa9fb2a742d807bc67de Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Fri, 10 Jun 2022 16:12:51 -0700 Subject: [PATCH 24/27] Fix/411 asset dropdown style (#535) * fix: arrow missing from select box * fix: select box styles on firefox --- apps/explorer/tailwind.config.js | 1 + apps/simple-trading-app/tailwind.config.js | 1 + apps/stats/tailwind.config.js | 1 + apps/token/tailwind.config.js | 1 + apps/trading/tailwind.config.js | 1 + .../ui-toolkit/src/components/input/input.tsx | 46 +++---------------- .../src/components/select/select.tsx | 8 ++-- .../src/components/text-area/text-area.tsx | 12 +++-- libs/ui-toolkit/src/utils/shared.ts | 14 ++++++ libs/ui-toolkit/tailwind.config.js | 1 + 10 files changed, 39 insertions(+), 47 deletions(-) create mode 100644 libs/ui-toolkit/src/utils/shared.ts diff --git a/apps/explorer/tailwind.config.js b/apps/explorer/tailwind.config.js index 3c7b5a348..51ca67ff2 100644 --- a/apps/explorer/tailwind.config.js +++ b/apps/explorer/tailwind.config.js @@ -6,6 +6,7 @@ const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom module.exports = { content: [ join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'), + 'libs/ui-toolkit/src/utils/shared.ts', ...createGlobPatternsForDependencies(__dirname), ], darkMode: 'class', diff --git a/apps/simple-trading-app/tailwind.config.js b/apps/simple-trading-app/tailwind.config.js index 3c7b5a348..51ca67ff2 100644 --- a/apps/simple-trading-app/tailwind.config.js +++ b/apps/simple-trading-app/tailwind.config.js @@ -6,6 +6,7 @@ const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom module.exports = { content: [ join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'), + 'libs/ui-toolkit/src/utils/shared.ts', ...createGlobPatternsForDependencies(__dirname), ], darkMode: 'class', diff --git a/apps/stats/tailwind.config.js b/apps/stats/tailwind.config.js index 3c7b5a348..51ca67ff2 100644 --- a/apps/stats/tailwind.config.js +++ b/apps/stats/tailwind.config.js @@ -6,6 +6,7 @@ const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom module.exports = { content: [ join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'), + 'libs/ui-toolkit/src/utils/shared.ts', ...createGlobPatternsForDependencies(__dirname), ], darkMode: 'class', diff --git a/apps/token/tailwind.config.js b/apps/token/tailwind.config.js index fdea8f5d3..02ac51675 100644 --- a/apps/token/tailwind.config.js +++ b/apps/token/tailwind.config.js @@ -6,6 +6,7 @@ const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom module.exports = { content: [ join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'), + 'libs/ui-toolkit/src/utils/shared.ts', ...createGlobPatternsForDependencies(__dirname), ], darkMode: 'class', diff --git a/apps/trading/tailwind.config.js b/apps/trading/tailwind.config.js index 672d5fa2c..e13d55079 100644 --- a/apps/trading/tailwind.config.js +++ b/apps/trading/tailwind.config.js @@ -7,6 +7,7 @@ module.exports = { content: [ join(__dirname, 'pages/**/*.{js,ts,jsx,tsx}'), join(__dirname, 'components/**/*.{js,ts,jsx,tsx}'), + 'libs/ui-toolkit/src/utils/shared.ts', ...createGlobPatternsForDependencies(__dirname), ], darkMode: 'class', diff --git a/libs/ui-toolkit/src/components/input/input.tsx b/libs/ui-toolkit/src/components/input/input.tsx index 9fb3e6b4d..28624d85e 100644 --- a/libs/ui-toolkit/src/components/input/input.tsx +++ b/libs/ui-toolkit/src/components/input/input.tsx @@ -3,10 +3,7 @@ import { forwardRef } from 'react'; import classNames from 'classnames'; import type { IconName } from '../icon'; import { Icon } from '../icon'; -import { - includesLeftPadding, - includesRightPadding, -} from '../../utils/class-names'; +import { defaultFormElement } from '../../utils/shared'; type InputRootProps = InputHTMLAttributes & { hasError?: boolean; @@ -60,37 +57,6 @@ type AffixProps = InputPrepend | InputAppend; type InputProps = InputRootProps & AffixProps; -export const inputClassNames = ({ - hasError, - className, -}: { - hasError?: boolean; - className?: string; -}) => { - return classNames( - [ - 'appearance-none', - 'flex items-center w-full', - 'box-border', - 'border rounded-none', - 'bg-clip-padding', - 'border-black-60 dark:border-white-60', - 'bg-black-25 dark:bg-white-25', - 'text-black placeholder:text-black-60 dark:text-white dark:placeholder:text-white-60', - 'text-ui', - 'focus-visible:shadow-focus dark:focus-visible:shadow-focus-dark', - 'focus-visible:outline-0', - 'disabled:bg-black-10 disabled:dark:bg-white-10', - ], - { - 'pl-8': !includesLeftPadding(className), - 'pr-8': !includesRightPadding(className), - 'border-vega-pink dark:border-vega-pink': hasError, - }, - className - ); -}; - export const inputStyle = ({ style, disabled, @@ -165,17 +131,17 @@ export const Input = forwardRef( const hasPrepended = !!(prependIconName || prependElement); const hasAppended = !!(appendIconName || appendElement); - const inputClassName = classNames('h-28', className, { - 'pl-28': hasPrepended ?? hasAppended, + const inputClassName = classNames('appearance-none', 'h-28', className, { + 'pl-28': hasPrepended, + 'pr-28': hasAppended, + 'border-vega-pink dark:border-vega-pink': hasError, }); const input = ( ); diff --git a/libs/ui-toolkit/src/components/select/select.tsx b/libs/ui-toolkit/src/components/select/select.tsx index 8ebb0d7bd..5a30df569 100644 --- a/libs/ui-toolkit/src/components/select/select.tsx +++ b/libs/ui-toolkit/src/components/select/select.tsx @@ -1,7 +1,7 @@ import type { SelectHTMLAttributes } from 'react'; import { forwardRef } from 'react'; import classNames from 'classnames'; -import { inputClassNames } from '../input'; +import { defaultFormElement } from '../../utils/shared'; export interface SelectProps extends SelectHTMLAttributes { hasError?: boolean; @@ -11,11 +11,13 @@ export interface SelectProps extends SelectHTMLAttributes { } export const Select = forwardRef( - (props, ref) => ( + ({ className, hasError, ...props }, ref) => (