From 52183555a9baedcfc613bd07d1663cc68ddaa388 Mon Sep 17 00:00:00 2001 From: Edd Date: Fri, 10 Mar 2023 14:01:26 +0000 Subject: [PATCH 1/8] feat(explorer): improve display of multi cancellations (#3151) --- .../order-summary/order-cancellation.tsx | 26 +++++++++++++++++ .../details/batch-submission/batch-cancel.tsx | 10 ++++++- .../txs/details/tx-order-cancel.tsx | 28 ++++++++++++------- 3 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 apps/explorer/src/app/components/order-summary/order-cancellation.tsx diff --git a/apps/explorer/src/app/components/order-summary/order-cancellation.tsx b/apps/explorer/src/app/components/order-summary/order-cancellation.tsx new file mode 100644 index 000000000..25273d62e --- /dev/null +++ b/apps/explorer/src/app/components/order-summary/order-cancellation.tsx @@ -0,0 +1,26 @@ +import { t } from '@vegaprotocol/i18n'; + +interface CancelSummaryProps { + orderId?: string; + marketId?: string; +} + +/** + * Simple component for rendering a reasonable string from an order cancellation + */ +export const CancelSummary = ({ orderId, marketId }: CancelSummaryProps) => { + return {getLabel(orderId, marketId)}; +}; + +export function getLabel( + orderId: string | undefined, + marketId: string | undefined +): string { + if (!orderId && !marketId) { + return t('All orders'); + } else if (marketId && !orderId) { + return t('All in market'); + } + + return '-'; +} diff --git a/apps/explorer/src/app/components/txs/details/batch-submission/batch-cancel.tsx b/apps/explorer/src/app/components/txs/details/batch-submission/batch-cancel.tsx index 594c227df..2aacf1d6f 100644 --- a/apps/explorer/src/app/components/txs/details/batch-submission/batch-cancel.tsx +++ b/apps/explorer/src/app/components/txs/details/batch-submission/batch-cancel.tsx @@ -2,6 +2,7 @@ import type { BatchCancellationInstruction } from '../../../../routes/types/bloc import { TxOrderType } from '../../tx-order-type'; import { MarketLink } from '../../../links'; import OrderSummary from '../../../order-summary/order-summary'; +import { CancelSummary } from '../../../order-summary/order-cancellation'; interface BatchCancelProps { index: number; @@ -19,7 +20,14 @@ export const BatchCancel = ({ index, submission }: BatchCancelProps) => { - + {submission.orderId ? ( + + ) : ( + + )} diff --git a/apps/explorer/src/app/components/txs/details/tx-order-cancel.tsx b/apps/explorer/src/app/components/txs/details/tx-order-cancel.tsx index d0074b8e4..cdfa4efc8 100644 --- a/apps/explorer/src/app/components/txs/details/tx-order-cancel.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-order-cancel.tsx @@ -5,6 +5,8 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint import { TxDetailsShared } from './shared/tx-details-shared'; import { TableCell, TableRow, TableWithTbody } from '../../table'; import DeterministicOrderDetails from '../../order-details/deterministic-order-details'; +import { CancelSummary } from '../../order-summary/order-cancellation'; +import Hash from '../../links/hash'; interface TxDetailsOrderCancelProps { txData: BlockExplorerTransactionResult | undefined; @@ -24,8 +26,8 @@ export const TxDetailsOrderCancel = ({ return <>{t('Awaiting Block Explorer transaction details')}; } - const marketId: string = txData.command.orderCancellation.marketId || '-'; - const orderId: string = txData.command.orderCancellation.orderId || '-'; + const marketId: string = txData.command.orderCancellation.marketId; + const orderId: string = txData.command.orderCancellation.orderId; return ( <> @@ -38,18 +40,24 @@ export const TxDetailsOrderCancel = ({ {t('Order')} - {orderId} - - - - {t('Market')} - - + {orderId ? ( + + ) : ( + + )} + {marketId ? ( + + {t('Market')} + + + + + ) : null} - {orderId !== '-' ? : null} + {orderId ? : null} ); }; From 1098accb840d2d115edfa483dbc5f546a0a63ec7 Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Fri, 10 Mar 2023 06:01:51 -0800 Subject: [PATCH 2/8] fix(trading,explorer,governance): consistent zustand usage (#3133) --- apps/explorer/src/app/components/nav/nav.tsx | 2 +- .../src/hooks/use-pending-balances-manager.ts | 46 +++++++++--------- .../src/lib/balances/balances-store.ts | 2 +- .../src/lib/tranches/tranches-store.tsx | 4 +- apps/governance/src/stores/transactions.ts | 47 +++++++++---------- apps/trading/stores/global.ts | 2 +- libs/accounts/src/lib/transfer-dialog.tsx | 2 +- libs/apollo-client/src/lib/header-store.ts | 2 +- libs/assets/src/lib/asset-details-dialog.tsx | 2 +- libs/deposits/src/lib/deposit-dialog.tsx | 2 +- libs/environment/__mocks__/zustand.js | 18 ------- libs/environment/__mocks__/zustand.ts | 21 +++++++++ libs/environment/src/hooks/use-environment.ts | 2 +- libs/environment/tsconfig.lib.json | 3 +- .../toast/toasts-container.stories.tsx | 2 +- .../src/components/toast/use-toasts.ts | 4 +- libs/wallet/__mocks__/zustand.js | 18 ------- libs/wallet/__mocks__/zustand.ts | 21 +++++++++ .../connect-dialog/connect-dialog.spec.tsx | 23 +++++---- .../src/connect-dialog/connect-dialog.tsx | 2 +- .../wallet/src/use-vega-transaction-store.tsx | 4 +- libs/wallet/tsconfig.lib.json | 3 +- libs/web3/__mocks__/zustand.js | 27 ----------- libs/web3/__mocks__/zustand.ts | 24 ++++++++++ .../lib/use-ethereum-transaction-store.tsx | 4 +- .../use-ethereum-withdraw-approvals-store.tsx | 4 +- libs/web3/src/lib/web3-connect-store.ts | 2 +- libs/web3/tsconfig.lib.json | 3 +- libs/withdraws/src/lib/withdraw-store.ts | 2 +- libs/withdraws/src/lib/withdrawal-dialog.tsx | 2 +- 30 files changed, 152 insertions(+), 148 deletions(-) delete mode 100644 libs/environment/__mocks__/zustand.js create mode 100644 libs/environment/__mocks__/zustand.ts delete mode 100644 libs/wallet/__mocks__/zustand.js create mode 100644 libs/wallet/__mocks__/zustand.ts delete mode 100644 libs/web3/__mocks__/zustand.js create mode 100644 libs/web3/__mocks__/zustand.ts diff --git a/apps/explorer/src/app/components/nav/nav.tsx b/apps/explorer/src/app/components/nav/nav.tsx index 8b511e9a2..f54724894 100644 --- a/apps/explorer/src/app/components/nav/nav.tsx +++ b/apps/explorer/src/app/components/nav/nav.tsx @@ -21,7 +21,7 @@ type NavStore = { hide: () => void; }; -export const useNavStore = create((set, get) => ({ +export const useNavStore = create()((set, get) => ({ open: false, toggle: () => set({ open: !get().open }), hide: () => set({ open: false }), diff --git a/apps/governance/src/hooks/use-pending-balances-manager.ts b/apps/governance/src/hooks/use-pending-balances-manager.ts index 7eee9bde8..7d83116b5 100644 --- a/apps/governance/src/hooks/use-pending-balances-manager.ts +++ b/apps/governance/src/hooks/use-pending-balances-manager.ts @@ -9,26 +9,28 @@ export type PendingTxsStore = { resetPendingTxs: () => void; }; -export const usePendingBalancesStore = create((set, get) => ({ - pendingBalances: [], - addPendingTxs: (event: Event[]) => { - set({ - pendingBalances: uniqBy( - [...get().pendingBalances, ...event], - 'transactionHash' - ), - }); - }, - removePendingTx: (event: Event) => { - set({ - pendingBalances: [ - ...get().pendingBalances.filter( - ({ transactionHash }) => transactionHash !== event.transactionHash +export const usePendingBalancesStore = create()( + (set, get) => ({ + pendingBalances: [], + addPendingTxs: (event: Event[]) => { + set({ + pendingBalances: uniqBy( + [...get().pendingBalances, ...event], + 'transactionHash' ), - ], - }); - }, - resetPendingTxs: () => { - set({ pendingBalances: [] }); - }, -})); + }); + }, + removePendingTx: (event: Event) => { + set({ + pendingBalances: [ + ...get().pendingBalances.filter( + ({ transactionHash }) => transactionHash !== event.transactionHash + ), + ], + }); + }, + resetPendingTxs: () => { + set({ pendingBalances: [] }); + }, + }) +); diff --git a/apps/governance/src/lib/balances/balances-store.ts b/apps/governance/src/lib/balances/balances-store.ts index 189ae9c81..c9c4354ee 100644 --- a/apps/governance/src/lib/balances/balances-store.ts +++ b/apps/governance/src/lib/balances/balances-store.ts @@ -38,7 +38,7 @@ export interface RefreshBalances { vestingAssociatedBalance: BigNumber; } -export const useBalances = create((set) => ({ +export const useBalances = create()((set) => ({ associationBreakdown: { stakingAssociations: {}, vestingAssociations: {}, diff --git a/apps/governance/src/lib/tranches/tranches-store.tsx b/apps/governance/src/lib/tranches/tranches-store.tsx index 49c785f95..6b48ed913 100644 --- a/apps/governance/src/lib/tranches/tranches-store.tsx +++ b/apps/governance/src/lib/tranches/tranches-store.tsx @@ -1,7 +1,7 @@ import { toBigNum } from '@vegaprotocol/utils'; import type { TrancheServiceResponse } from '@vegaprotocol/smart-contracts'; import type BigNumber from 'bignumber.js'; -import create from 'zustand'; +import { create } from 'zustand'; import { ENV } from '../../config'; export interface Tranche { @@ -36,7 +36,7 @@ export type TranchesStore = { const secondsToDate = (seconds: number) => new Date(seconds * 1000); -export const useTranches = create((set) => ({ +export const useTranches = create()((set) => ({ tranches: null, loading: false, error: null, diff --git a/apps/governance/src/stores/transactions.ts b/apps/governance/src/stores/transactions.ts index 0655dbbd7..d9657e184 100644 --- a/apps/governance/src/stores/transactions.ts +++ b/apps/governance/src/stores/transactions.ts @@ -1,5 +1,4 @@ import type ethers from 'ethers'; -import type { GetState, SetState } from 'zustand'; import { create } from 'zustand'; export interface TxData { @@ -16,27 +15,25 @@ interface TransactionStore { 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), - }); - }, - }) -); +export const useTransactionStore = create()((set, get) => ({ + 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/stores/global.ts b/apps/trading/stores/global.ts index 18e11a2ac..7b799e24d 100644 --- a/apps/trading/stores/global.ts +++ b/apps/trading/stores/global.ts @@ -15,7 +15,7 @@ interface PageTitleStore { updateTitle: (title: string) => void; } -export const useGlobalStore = create((set) => ({ +export const useGlobalStore = create()((set) => ({ nodeSwitcherDialog: false, marketId: LocalStorage.getItem('marketId') || null, shouldDisplayWelcomeDialog: false, diff --git a/libs/accounts/src/lib/transfer-dialog.tsx b/libs/accounts/src/lib/transfer-dialog.tsx index 0627bddf4..2861113f1 100644 --- a/libs/accounts/src/lib/transfer-dialog.tsx +++ b/libs/accounts/src/lib/transfer-dialog.tsx @@ -11,7 +11,7 @@ interface Actions { open: (open?: boolean) => void; } -export const useTransferDialog = create((set) => ({ +export const useTransferDialog = create()((set) => ({ isOpen: false, open: (open = true) => { set(() => ({ isOpen: open })); diff --git a/libs/apollo-client/src/lib/header-store.ts b/libs/apollo-client/src/lib/header-store.ts index 5343a6b2a..ca4e53636 100644 --- a/libs/apollo-client/src/lib/header-store.ts +++ b/libs/apollo-client/src/lib/header-store.ts @@ -9,4 +9,4 @@ type HeaderStore = { [url: string]: HeaderEntry | undefined; }; -export const useHeaderStore = create(() => ({})); +export const useHeaderStore = create()(() => ({})); diff --git a/libs/assets/src/lib/asset-details-dialog.tsx b/libs/assets/src/lib/asset-details-dialog.tsx index 88efa90c0..168fc3399 100644 --- a/libs/assets/src/lib/asset-details-dialog.tsx +++ b/libs/assets/src/lib/asset-details-dialog.tsx @@ -20,7 +20,7 @@ export type AssetDetailsDialogStore = { open: (id: string, trigger?: HTMLElement | null, asJson?: boolean) => void; }; -export const useAssetDetailsDialogStore = create( +export const useAssetDetailsDialogStore = create()( (set) => ({ isOpen: false, id: '', diff --git a/libs/deposits/src/lib/deposit-dialog.tsx b/libs/deposits/src/lib/deposit-dialog.tsx index f965cc1b3..b5e4e82d6 100644 --- a/libs/deposits/src/lib/deposit-dialog.tsx +++ b/libs/deposits/src/lib/deposit-dialog.tsx @@ -15,7 +15,7 @@ interface Actions { close: () => void; } -export const useDepositDialog = create((set) => ({ +export const useDepositDialog = create()((set) => ({ isOpen: false, assetId: undefined, open: (assetId) => set(() => ({ assetId, isOpen: true })), diff --git a/libs/environment/__mocks__/zustand.js b/libs/environment/__mocks__/zustand.js deleted file mode 100644 index 08d2cd840..000000000 --- a/libs/environment/__mocks__/zustand.js +++ /dev/null @@ -1,18 +0,0 @@ -import { act } from 'react-dom/test-utils'; -const zu = jest.requireActual('zustand'); // if using jest - -// a variable to hold reset functions for all stores declared in the app -const storeResetFns = new Set(); - -// when creating a store, we get its initial state, create a reset function and add it in the set -export const create = (createState) => { - const store = zu.create(createState); - const initialState = store.getState(); - storeResetFns.add(() => store.setState(initialState, true)); - return store; -}; - -// Reset all stores after each test run -beforeEach(() => { - act(() => storeResetFns.forEach((resetFn) => resetFn())); -}); diff --git a/libs/environment/__mocks__/zustand.ts b/libs/environment/__mocks__/zustand.ts new file mode 100644 index 000000000..b42750b88 --- /dev/null +++ b/libs/environment/__mocks__/zustand.ts @@ -0,0 +1,21 @@ +import type { StateCreator } from 'zustand'; +import { act } from 'react-dom/test-utils'; +const { create: actualCreate } = jest.requireActual('zustand'); // if using jest + +// a variable to hold reset functions for all stores declared in the app +const storeResetFns = new Set<() => void>(); + +// when creating a store, we get its initial state, create a reset function and add it in the set +export const create = + () => + (createState: StateCreator) => { + const store = actualCreate(createState); + const initialState = store.getState(); + storeResetFns.add(() => store.setState(initialState, true)); + return store; + }; + +// Reset all stores after each test run +beforeEach(() => { + act(() => storeResetFns.forEach((resetFn) => resetFn())); +}); diff --git a/libs/environment/src/hooks/use-environment.ts b/libs/environment/src/hooks/use-environment.ts index 79914465b..1e60bdd51 100644 --- a/libs/environment/src/hooks/use-environment.ts +++ b/libs/environment/src/hooks/use-environment.ts @@ -34,7 +34,7 @@ export type EnvStore = Env & Actions; export const STORAGE_KEY = 'vega_url'; const SUBSCRIPTION_TIMEOUT = 3000; -export const useEnvironment = create((set, get) => ({ +export const useEnvironment = create()((set, get) => ({ ...compileEnvVars(), nodes: [], status: 'default', diff --git a/libs/environment/tsconfig.lib.json b/libs/environment/tsconfig.lib.json index b2447df1e..c04a92da1 100644 --- a/libs/environment/tsconfig.lib.json +++ b/libs/environment/tsconfig.lib.json @@ -17,7 +17,8 @@ "**/*.test.js", "**/*.spec.jsx", "**/*.test.jsx", - "jest.config.ts" + "jest.config.ts", + "__mocks__" ], "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] } diff --git a/libs/ui-toolkit/src/components/toast/toasts-container.stories.tsx b/libs/ui-toolkit/src/components/toast/toasts-container.stories.tsx index 1c1f704fd..7202af83c 100644 --- a/libs/ui-toolkit/src/components/toast/toasts-container.stories.tsx +++ b/libs/ui-toolkit/src/components/toast/toasts-container.stories.tsx @@ -77,7 +77,7 @@ const randomToast = (): Toast => { }; type PriceStore = { price: number; setPrice: (p: number) => void }; -const usePrice = create((set) => ({ +const usePrice = create()((set) => ({ price: 0, setPrice: (p) => set((state) => ({ price: p })), })); diff --git a/libs/ui-toolkit/src/components/toast/use-toasts.ts b/libs/ui-toolkit/src/components/toast/use-toasts.ts index e4f69e7e1..2295a6e4d 100644 --- a/libs/ui-toolkit/src/components/toast/use-toasts.ts +++ b/libs/ui-toolkit/src/components/toast/use-toasts.ts @@ -47,8 +47,8 @@ type Actions = { type ToastsStore = State & Actions; -export const useToasts = create( - immer((set, get) => ({ +export const useToasts = create()( + immer((set) => ({ toasts: {}, count: 0, add: (toast) => diff --git a/libs/wallet/__mocks__/zustand.js b/libs/wallet/__mocks__/zustand.js deleted file mode 100644 index 08d2cd840..000000000 --- a/libs/wallet/__mocks__/zustand.js +++ /dev/null @@ -1,18 +0,0 @@ -import { act } from 'react-dom/test-utils'; -const zu = jest.requireActual('zustand'); // if using jest - -// a variable to hold reset functions for all stores declared in the app -const storeResetFns = new Set(); - -// when creating a store, we get its initial state, create a reset function and add it in the set -export const create = (createState) => { - const store = zu.create(createState); - const initialState = store.getState(); - storeResetFns.add(() => store.setState(initialState, true)); - return store; -}; - -// Reset all stores after each test run -beforeEach(() => { - act(() => storeResetFns.forEach((resetFn) => resetFn())); -}); diff --git a/libs/wallet/__mocks__/zustand.ts b/libs/wallet/__mocks__/zustand.ts new file mode 100644 index 000000000..b42750b88 --- /dev/null +++ b/libs/wallet/__mocks__/zustand.ts @@ -0,0 +1,21 @@ +import type { StateCreator } from 'zustand'; +import { act } from 'react-dom/test-utils'; +const { create: actualCreate } = jest.requireActual('zustand'); // if using jest + +// a variable to hold reset functions for all stores declared in the app +const storeResetFns = new Set<() => void>(); + +// when creating a store, we get its initial state, create a reset function and add it in the set +export const create = + () => + (createState: StateCreator) => { + const store = actualCreate(createState); + const initialState = store.getState(); + storeResetFns.add(() => store.setState(initialState, true)); + return store; + }; + +// Reset all stores after each test run +beforeEach(() => { + act(() => storeResetFns.forEach((resetFn) => resetFn())); +}); diff --git a/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx b/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx index 04a34e4b9..f4ca36a07 100644 --- a/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx +++ b/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx @@ -8,8 +8,11 @@ import { import type { MockedResponse } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing'; import { VegaWalletProvider } from '../provider'; -import { VegaConnectDialog, CLOSE_DELAY } from './connect-dialog'; -import type { VegaWalletDialogStore } from './connect-dialog'; +import { + VegaConnectDialog, + CLOSE_DELAY, + useVegaWalletDialogStore, +} from './connect-dialog'; import type { VegaConnectDialogProps } from '..'; import { ClientErrors, @@ -24,11 +27,6 @@ import { ChainIdDocument } from '@vegaprotocol/react-helpers'; const mockUpdateDialogOpen = jest.fn(); const mockCloseVegaDialog = jest.fn(); -const mockStoreObj: Partial = { - updateVegaWalletDialog: mockUpdateDialogOpen, - closeVegaWalletDialog: mockCloseVegaDialog, - vegaWalletDialogOpen: true, -}; jest.mock('@vegaprotocol/environment'); @@ -44,11 +42,6 @@ useEnvironment.mockImplementation(() => ({ HOSTED_WALLET_URL: mockHostedWalletUrl, })); -jest.mock('zustand', () => ({ - create: () => (storeGetter: (store: VegaWalletDialogStore) => unknown) => - storeGetter(mockStoreObj as VegaWalletDialogStore), -})); - let defaultProps: VegaConnectDialogProps; const INITIAL_KEY = 'some-key'; @@ -66,6 +59,12 @@ beforeEach(() => { defaultProps = { connectors, }; + + useVegaWalletDialogStore.setState({ + updateVegaWalletDialog: mockUpdateDialogOpen, + closeVegaWalletDialog: mockCloseVegaDialog, + vegaWalletDialogOpen: true, + }); }); const mockVegaWalletUrl = 'http://mock.wallet.com'; diff --git a/libs/wallet/src/connect-dialog/connect-dialog.tsx b/libs/wallet/src/connect-dialog/connect-dialog.tsx index 131998236..8c30bdc15 100644 --- a/libs/wallet/src/connect-dialog/connect-dialog.tsx +++ b/libs/wallet/src/connect-dialog/connect-dialog.tsx @@ -37,7 +37,7 @@ export interface VegaConnectDialogProps { onChangeOpen?: (open: boolean) => void; } -export const useVegaWalletDialogStore = create( +export const useVegaWalletDialogStore = create()( (set) => ({ vegaWalletDialogOpen: false, updateVegaWalletDialog: (open: boolean) => diff --git a/libs/wallet/src/use-vega-transaction-store.tsx b/libs/wallet/src/use-vega-transaction-store.tsx index bb2a38134..42aaf1440 100644 --- a/libs/wallet/src/use-vega-transaction-store.tsx +++ b/libs/wallet/src/use-vega-transaction-store.tsx @@ -52,8 +52,8 @@ export interface VegaTransactionStore { ) => void; } -export const useVegaTransactionStore = create( - subscribeWithSelector((set, get) => ({ +export const useVegaTransactionStore = create()( + subscribeWithSelector((set, get) => ({ transactions: [] as VegaStoredTxState[], create: (body: Transaction, order?: OrderTxUpdateFieldsFragment) => { const transactions = get().transactions; diff --git a/libs/wallet/tsconfig.lib.json b/libs/wallet/tsconfig.lib.json index b2447df1e..c04a92da1 100644 --- a/libs/wallet/tsconfig.lib.json +++ b/libs/wallet/tsconfig.lib.json @@ -17,7 +17,8 @@ "**/*.test.js", "**/*.spec.jsx", "**/*.test.jsx", - "jest.config.ts" + "jest.config.ts", + "__mocks__" ], "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] } diff --git a/libs/web3/__mocks__/zustand.js b/libs/web3/__mocks__/zustand.js deleted file mode 100644 index c1e7d6119..000000000 --- a/libs/web3/__mocks__/zustand.js +++ /dev/null @@ -1,27 +0,0 @@ -import { act } from '@testing-library/react'; -const zu = jest.requireActual('zustand'); // if using jest - -// a variable to hold reset functions for all stores declared in the app -const storeResetFns = new Set(); - -// when creating a store, we get its initial state, create a reset function and add it in the set -export const create = (createState) => { - let store; - if (typeof createState === 'function') { - store = zu.create(createState); - } else { - store = (selector, equalityFn) => - zu.useStore(createState, selector, equalityFn); - Object.assign(store, createState); - } - const initialState = store.getState(); - storeResetFns.add(() => store.setState(initialState, true)); - return store; -}; - -// Reset all stores after each test run -beforeEach(() => { - act(() => storeResetFns.forEach((resetFn) => resetFn())); -}); - -export default create; diff --git a/libs/web3/__mocks__/zustand.ts b/libs/web3/__mocks__/zustand.ts new file mode 100644 index 000000000..17691a8c0 --- /dev/null +++ b/libs/web3/__mocks__/zustand.ts @@ -0,0 +1,24 @@ +import type { StateCreator } from 'zustand'; +import { act } from 'react-dom/test-utils'; +const { create: actualCreate } = jest.requireActual('zustand'); // if using jest + +// a variable to hold reset functions for all stores declared in the app +const storeResetFns = new Set<() => void>(); + +// when creating a store, we get its initial state, create a reset function and add it in the set +export const create = + () => + (createState: StateCreator) => { + const store = actualCreate(createState); + const initialState = store.getState(); + storeResetFns.add(() => store.setState(initialState, true)); + return store; + }; + +// Reset all stores after each test run +beforeEach(() => { + act(() => storeResetFns.forEach((resetFn) => resetFn())); +}); + +// also export default as web3-react internals import and use zustand as the default import +export default create; diff --git a/libs/web3/src/lib/use-ethereum-transaction-store.tsx b/libs/web3/src/lib/use-ethereum-transaction-store.tsx index 177ad7ab2..69a849212 100644 --- a/libs/web3/src/lib/use-ethereum-transaction-store.tsx +++ b/libs/web3/src/lib/use-ethereum-transaction-store.tsx @@ -55,8 +55,8 @@ export interface EthTransactionStore { delete: (index: number) => void; } -export const useEthTransactionStore = create( - subscribeWithSelector((set, get) => ({ +export const useEthTransactionStore = create()( + subscribeWithSelector((set, get) => ({ transactions: [] as EthStoredTxState[], create: ( contract: Contract | null, diff --git a/libs/web3/src/lib/use-ethereum-withdraw-approvals-store.tsx b/libs/web3/src/lib/use-ethereum-withdraw-approvals-store.tsx index 7437f4b3a..4287dda7f 100644 --- a/libs/web3/src/lib/use-ethereum-withdraw-approvals-store.tsx +++ b/libs/web3/src/lib/use-ethereum-withdraw-approvals-store.tsx @@ -49,8 +49,8 @@ export interface EthWithdrawApprovalStore { delete: (index: number) => void; } -export const useEthWithdrawApprovalsStore = create( - subscribeWithSelector((set, get) => ({ +export const useEthWithdrawApprovalsStore = create()( + subscribeWithSelector((set, get) => ({ transactions: [] as EthWithdrawalApprovalState[], create: ( withdrawal: EthWithdrawalApprovalState['withdrawal'], diff --git a/libs/web3/src/lib/web3-connect-store.ts b/libs/web3/src/lib/web3-connect-store.ts index f0b4ae4ab..2ad545f0d 100644 --- a/libs/web3/src/lib/web3-connect-store.ts +++ b/libs/web3/src/lib/web3-connect-store.ts @@ -13,7 +13,7 @@ interface Actions { close: () => void; } -export const useWeb3ConnectStore = create((set) => ({ +export const useWeb3ConnectStore = create()((set) => ({ isOpen: false, connectors: [], initialize: (connectors, desiredChainId) => { diff --git a/libs/web3/tsconfig.lib.json b/libs/web3/tsconfig.lib.json index b2447df1e..c04a92da1 100644 --- a/libs/web3/tsconfig.lib.json +++ b/libs/web3/tsconfig.lib.json @@ -17,7 +17,8 @@ "**/*.test.js", "**/*.spec.jsx", "**/*.test.jsx", - "jest.config.ts" + "jest.config.ts", + "__mocks__" ], "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] } diff --git a/libs/withdraws/src/lib/withdraw-store.ts b/libs/withdraws/src/lib/withdraw-store.ts index 7c09dc5d0..c34dcb75e 100644 --- a/libs/withdraws/src/lib/withdraw-store.ts +++ b/libs/withdraws/src/lib/withdraw-store.ts @@ -11,7 +11,7 @@ export interface WithdrawStore { update: (state: Partial) => void; } -export const useWithdrawStore = create((set) => ({ +export const useWithdrawStore = create()((set) => ({ asset: undefined, balance: new BigNumber(0), min: new BigNumber(0), diff --git a/libs/withdraws/src/lib/withdrawal-dialog.tsx b/libs/withdraws/src/lib/withdrawal-dialog.tsx index d31cf9bc6..ccdfd5059 100644 --- a/libs/withdraws/src/lib/withdrawal-dialog.tsx +++ b/libs/withdraws/src/lib/withdrawal-dialog.tsx @@ -17,7 +17,7 @@ interface Actions { close: () => void; } -export const useWithdrawalDialog = create((set) => ({ +export const useWithdrawalDialog = create()((set) => ({ isOpen: false, assetId: undefined, open: (assetId) => set(() => ({ assetId, isOpen: true })), From 327ebf349613da25d57d978ee846b5044f32c682 Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Fri, 10 Mar 2023 09:02:01 -0500 Subject: [PATCH 3/8] fix(trading): deal ticket expiry not wiped out after changing TIF from GTT (#3149) --- .../src/components/deal-ticket/deal-ticket.tsx | 9 ++++++++- libs/deal-ticket/src/constants.ts | 2 +- .../src/hooks/use-fee-deal-ticket-details.tsx | 11 ----------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx index c43fae60d..7acf78372 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -184,7 +184,9 @@ export const DealTicket = ({ type, // when changing type also update the tif to what was last used of new type timeInForce: lastTIF[type] || order.timeInForce, + expiresAt: undefined, }); + clearErrors('expiresAt'); }} market={market} marketData={marketData} @@ -232,7 +234,12 @@ export const DealTicket = ({ update({ timeInForce }); // Set tif value for the given order type, so that when switching // types we know the last used TIF for the given order type - setLastTIF((curr) => ({ ...curr, [order.type]: timeInForce })); + setLastTIF((curr) => ({ + ...curr, + [order.type]: timeInForce, + expiresAt: undefined, + })); + clearErrors('expiresAt'); }} market={market} marketData={marketData} diff --git a/libs/deal-ticket/src/constants.ts b/libs/deal-ticket/src/constants.ts index 6dfd50110..d2e0058be 100644 --- a/libs/deal-ticket/src/constants.ts +++ b/libs/deal-ticket/src/constants.ts @@ -13,7 +13,7 @@ export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t( export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance'); export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) => t( - "The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset ($s).", + "The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset (%s).", [settlementAsset] ); export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t( diff --git a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx b/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx index 34d1f1605..e9e1d94e2 100644 --- a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx +++ b/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx @@ -11,8 +11,6 @@ import { useMemo } from 'react'; import type { Market, MarketData } from '@vegaprotocol/market-list'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { - EST_CLOSEOUT_TOOLTIP_TEXT, - // EST_MARGIN_TOOLTIP_TEXT, EST_TOTAL_MARGIN_TOOLTIP_TEXT, NOTIONAL_SIZE_TOOLTIP_TEXT, MARGIN_ACCOUNT_TOOLTIP_TEXT, @@ -100,16 +98,13 @@ export interface FeeDetails { export const getFeeDetailsValues = ({ balance, assetSymbol, - estCloseOut, estimateOrder, - margin, market, notionalSize, totalMargin, }: FeeDetails) => { const assetDecimals = market.tradableInstrument.instrument.product.settlementAsset.decimals; - const quoteName = market.tradableInstrument.instrument.product.quoteName; const formatValueWithMarketDp = ( value: string | number | null | undefined ): string => { @@ -193,11 +188,5 @@ export const getFeeDetailsValues = ({ symbol: assetSymbol, labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT, }); - details.push({ - label: t('Liquidation'), - value: estCloseOut && `~${formatValueWithMarketDp(estCloseOut)}`, - symbol: market.tradableInstrument.instrument.product.quoteName, - labelDescription: EST_CLOSEOUT_TOOLTIP_TEXT(quoteName), - }); return details; }; From b3bfb908043a1af46e29af90996459fbdf18f5e2 Mon Sep 17 00:00:00 2001 From: Joe Tsang <30622993+jtsang586@users.noreply.github.com> Date: Fri, 10 Mar 2023 14:03:36 +0000 Subject: [PATCH 4/8] test(governance): new and update market proposal forms (#3131) --- .../src/fixtures/proposals/new-market.json | 42 +++---- .../fixtures/proposals/update-market-old.json | 86 ++++++++++++++ .../src/fixtures/proposals/update-market.json | 96 ++++++++++------ .../src/integration/flow/proposal-forms.cy.js | 106 ++++++++++++++++-- .../src/support/governance.functions.js | 7 +- .../components/vote-details/vote-details.tsx | 4 +- libs/cypress/src/index.ts | 2 + .../vega-wallet-submit-liquidity-provision.ts | 67 +++++++++++ libs/wallet/src/connectors/vega-connector.ts | 23 +++- 9 files changed, 364 insertions(+), 69 deletions(-) create mode 100644 apps/governance-e2e/src/fixtures/proposals/update-market-old.json create mode 100644 libs/cypress/src/lib/commands/vega-wallet-submit-liquidity-provision.ts diff --git a/apps/governance-e2e/src/fixtures/proposals/new-market.json b/apps/governance-e2e/src/fixtures/proposals/new-market.json index c5fb67c1e..b7950b4ca 100644 --- a/apps/governance-e2e/src/fixtures/proposals/new-market.json +++ b/apps/governance-e2e/src/fixtures/proposals/new-market.json @@ -22,7 +22,7 @@ "filters": [ { "key": { - "name": "prices.ETH.value", + "name": "prices.BTC.value", "type": "TYPE_INTEGER", "numberDecimalPlaces": "0" }, @@ -38,30 +38,34 @@ } }, "dataSourceSpecForTradingTermination": { - "signers": [ - { - "pubKey": { - "key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680" - } - } - ], - "filters": [ - { - "key": { - "name": "trading.terminated.ETH5", - "type": "TYPE_BOOLEAN" - }, - "conditions": [ + "external": { + "oracle": { + "signers": [ { - "operator": "OPERATOR_EQUALS", - "value": "true" + "pubKey": { + "key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680" + } + } + ], + "filters": [ + { + "key": { + "name": "trading.terminated.ETH5", + "type": "TYPE_BOOLEAN" + }, + "conditions": [ + { + "operator": "OPERATOR_EQUALS", + "value": "true" + } + ] } ] } - ] + } }, "dataSourceSpecBinding": { - "settlementPriceProperty": "prices.BTC.value", + "settlementDataProperty": "prices.BTC.value", "tradingTerminationProperty": "trading.terminated.ETH5" } } diff --git a/apps/governance-e2e/src/fixtures/proposals/update-market-old.json b/apps/governance-e2e/src/fixtures/proposals/update-market-old.json new file mode 100644 index 000000000..3045c4d06 --- /dev/null +++ b/apps/governance-e2e/src/fixtures/proposals/update-market-old.json @@ -0,0 +1,86 @@ +{ + "lpPriceRange": "11", + "instrument": { + "code": "Token.24h", + "future": { + "quoteName": "fBTC", + "dataSourceSpecForSettlementData": { + "external": { + "oracle": { + "signers": [ + { + "pubKey": { + "key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680" + } + } + ], + "filters": [ + { + "key": { + "name": "prices.BTC.value", + "type": "TYPE_INTEGER" + }, + "conditions": [ + { + "operator": "OPERATOR_GREATER_THAN", + "value": "0" + } + ] + } + ] + } + } + }, + "dataSourceSpecForTradingTermination": { + "external": { + "oracle": { + "signers": [ + { + "pubKey": { + "key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680" + } + } + ], + "filters": [ + { + "key": { + "name": "trading.terminated.ETH5", + "type": "TYPE_BOOLEAN" + }, + "conditions": [ + { + "operator": "OPERATOR_GREATER_THAN_OR_EQUAL", + "value": "1648684800000000000" + } + ] + } + ] + } + } + }, + "dataSourceSpecBinding": { + "settlementDataProperty": "prices.BTC.value", + "tradingTerminationProperty": "trading.terminated.ETH5" + } + } + }, + "metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"], + "priceMonitoringParameters": { + "triggers": [ + { + "horizon": "43200", + "probability": "0.9999999", + "auctionExtension": "600" + } + ] + }, + "logNormal": { + "tau": 0.0001140771161, + "riskAversionParameter": 0.001, + "params": { + "mu": 0, + "r": 0.016, + "sigma": 0.3 + } + } +} diff --git a/apps/governance-e2e/src/fixtures/proposals/update-market.json b/apps/governance-e2e/src/fixtures/proposals/update-market.json index dcdb91aa6..e64904f39 100644 --- a/apps/governance-e2e/src/fixtures/proposals/update-market.json +++ b/apps/governance-e2e/src/fixtures/proposals/update-market.json @@ -1,62 +1,76 @@ { + "lpPriceRange": "10", "instrument": { "code": "TEST.24h", "future": { "quoteName": "fUSDC", - "settlementDataDecimals": 5, "dataSourceSpecForSettlementData": { - "signers": [ - { - "pubKey": { - "key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680" - } - } - ], - "filters": [ - { - "key": { - "name": "prices.ETH.value", - "type": "TYPE_INTEGER" - }, - "conditions": [ + "external": { + "oracle": { + "signers": [ { - "operator": "OPERATOR_GREATER_THAN", - "value": "0" + "pubKey": { + "key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680" + } + } + ], + "filters": [ + { + "key": { + "name": "prices.ETH.value", + "type": "TYPE_INTEGER", + "numberDecimalPlaces": "0" + }, + "conditions": [ + { + "operator": "OPERATOR_GREATER_THAN", + "value": "0" + } + ] } ] } - ] + } }, "dataSourceSpecForTradingTermination": { - "signers": [ - { - "pubKey": { - "key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680" - } - } - ], - "filters": [ - { - "key": { - "name": "trading.terminated.ETH5", - "type": "TYPE_BOOLEAN" - }, - "conditions": [ + "external": { + "oracle": { + "signers": [ { - "operator": "OPERATOR_GREATER_THAN_OR_EQUAL", - "value": "1648684800000000000" + "pubKey": { + "key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680" + } + } + ], + "filters": [ + { + "key": { + "name": "trading.terminated.ETH5", + "type": "TYPE_BOOLEAN" + }, + "conditions": [ + { + "operator": "OPERATOR_EQUALS", + "value": "true" + } + ] } ] } - ] + } }, "dataSourceSpecBinding": { - "settlementPriceProperty": "prices.ETH.value", + "settlementDataProperty": "prices.ETH.value", "tradingTerminationProperty": "trading.terminated.ETH5" } } }, - "metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"], + "metadata": [ + "sector:energy", + "sector:food", + "source:docs.vega.xyz", + "test:update" + ], "priceMonitoringParameters": { "triggers": [ { @@ -66,6 +80,14 @@ } ] }, + "liquidityMonitoringParameters": { + "targetStakeParameters": { + "timeWindow": "3600", + "scalingFactor": 10 + }, + "triggeringRatio": "0.7", + "auctionExtension": "1" + }, "logNormal": { "tau": 0.0001140771161, "riskAversionParameter": 0.001, diff --git a/apps/governance-e2e/src/integration/flow/proposal-forms.cy.js b/apps/governance-e2e/src/integration/flow/proposal-forms.cy.js index 25a2f374d..2059fa77e 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-forms.cy.js +++ b/apps/governance-e2e/src/integration/flow/proposal-forms.cy.js @@ -26,6 +26,10 @@ const enactmentDeadlineError = '[data-testid="enactment-before-voting-deadline"]'; const proposalDownloadBtn = '[data-testid="proposal-download-json"]'; const feedbackError = '[data-testid="Error"]'; +const viewProposalBtn = 'view-proposal-btn'; +const liquidityVoteStatus = 'liquidity-votes-status'; +const tokenVoteStatus = 'token-votes-status'; +const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey'); const epochTimeout = Cypress.env('epochTimeout'); const proposalTimeout = { timeout: 14000 }; @@ -45,6 +49,7 @@ context( { tags: '@slow' }, function () { before('connect wallets and set approval limit', function () { + cy.createMarket(); cy.visit('/'); cy.vega_wallet_set_specified_approval_amount('1000'); }); @@ -65,7 +70,7 @@ context( // 3002-PROP-007 cy.get(newProposalDescription).type('E2E test for proposals'); - cy.get(proposalParameterSelect).find('option').should('have.length', 116); + cy.get(proposalParameterSelect).find('option').should('have.length', 117); cy.get(proposalParameterSelect).select( // 3007-PNEC-002 'governance_proposal_asset_minEnact' @@ -175,9 +180,8 @@ context( cy.get(enactmentDeadlineError).should('not.exist'); }); - // Skipping because unclear what the required json is yet for new market proposal, will update once docs have been updated - // 3003-todo-PMAN-001 - it.skip('Able to submit valid new market proposal', function () { + // 3003-PMAN-001 + it('Able to submit valid new market proposal', function () { cy.go_to_make_new_proposal(governanceProposalType.NEW_MARKET); cy.get(newProposalTitle).type('Test new market proposal'); cy.get(newProposalDescription).type('E2E test for proposals'); @@ -199,6 +203,7 @@ context( cy.get(newProposalTitle).type('Test new market proposal'); cy.get(newProposalDescription).type('E2E test for proposals'); cy.fixture('/proposals/new-market').then((newMarketProposal) => { + newMarketProposal.invalid = 'I am an invalid field'; let newMarketPayload = JSON.stringify(newMarketProposal); cy.get(newProposalTerms).type(newMarketPayload, { parseSpecialCharSequences: false, @@ -209,11 +214,66 @@ context( cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.get(feedbackError).should( 'have.text', - 'Invalid params: the transaction is malformed' + 'Invalid params: the transaction is not a valid Vega command: unknown field "invalid" in vega.NewMarket' ); }); - it.skip('Able to submit update market proposal', function () { + // Will fail if run after 'Able to submit update market proposal and vote for proposal' + // 3002-PROP-022 + it('Unable to submit update market proposal without equity-like share in the market', function () { + cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET); + cy.get(newProposalTitle).type('Test update market proposal - rejected'); + cy.get(newProposalDescription).type('E2E test for proposals'); + cy.get(proposalMarketSelect).select('Test market 1'); + cy.fixture('/proposals/update-market').then((updateMarketProposal) => { + let newUpdateMarketProposal = JSON.stringify(updateMarketProposal); + cy.get(newProposalTerms).type(newUpdateMarketProposal, { + parseSpecialCharSequences: false, + delay: 2, + }); + }); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Proposal rejected', proposalTimeout).should('be.visible'); + cy.getByTestId('dialog-content') + .find('p') + .should('have.text', 'PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE'); + cy.ensure_specified_unstaked_tokens_are_associated('1'); + }); + + // 3002-PROP-020 + it('Unable to submit update market proposal without minimum amount of tokens', function () { + cy.vega_wallet_teardown(); + cy.vega_wallet_faucet_assets_without_check( + 'fUSDC', + '1000000', + vegaWalletPublicKey + ); + cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET); + cy.get(newProposalTitle).type('Test update market proposal - rejected'); + cy.get(newProposalDescription).type('E2E test for proposals'); + cy.get(proposalMarketSelect).select('Test market 1'); + cy.fixture('/proposals/update-market').then((updateMarketProposal) => { + let newUpdateMarketProposal = JSON.stringify(updateMarketProposal); + cy.get(newProposalTerms).type(newUpdateMarketProposal, { + parseSpecialCharSequences: false, + delay: 2, + }); + }); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Transaction failed', proposalTimeout).should('be.visible'); + cy.get(feedbackError).should( + 'have.text', + 'Network error: the network blocked the transaction through the spam protection: party has insufficient associated governance tokens in their staking account to submit proposal request (ABCI code 89)' + ); + }); + + // 3001-VOTE-092 + it('Able to submit update market proposal and vote for proposal', function () { + cy.vega_wallet_faucet_assets_without_check( + 'fUSDC', + '1000000', + vegaWalletPublicKey + ); cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET); cy.get(newProposalTitle).type('Test update market proposal'); cy.get(newProposalDescription).type('E2E test for proposals'); @@ -222,6 +282,10 @@ context( cy.get('dd').eq(0).should('have.text', 'Test market 1'); cy.get('dd').eq(1).should('have.text', 'TEST.24h'); cy.get('dd').eq(2).should('not.be.empty'); + cy.get('dd').eq(2).invoke('text').as('EnactedMarketId'); + }); + cy.get('@EnactedMarketId').then((marketId) => { + cy.VegaWalletSubmitLiquidityProvision(marketId, '1'); }); cy.fixture('/proposals/update-market').then((updateMarketProposal) => { let newUpdateMarketProposal = JSON.stringify(updateMarketProposal); @@ -232,6 +296,34 @@ context( }); cy.get(newProposalSubmitButton).should('be.visible').click(); cy.wait_for_proposal_submitted(); + cy.navigate_to('proposals'); + cy.get('@EnactedMarketId').then((marketId) => { + cy.contains(marketId) + .parentsUntil(proposalListItem) + .within(() => { + cy.getByTestId(viewProposalBtn).click(); + }); + }); + cy.getByTestId(liquidityVoteStatus).should( + 'contain.text', + 'Currently expected to fail' + ); + cy.getByTestId(tokenVoteStatus).should( + 'contain.text', + 'Currently expected to fail' + ); + cy.vote_for_proposal('for'); + cy.getByTestId(liquidityVoteStatus).should( + 'contain.text', + 'Currently expected to pass' + ); + cy.getByTestId(tokenVoteStatus).should( + 'contain.text', + 'Currently expected to pass' + ); + cy.get_proposal_information_from_table('Expected to pass') + .contains('👍 by Token vote') + .should('be.visible'); }); // 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001 @@ -298,7 +390,7 @@ context( .parentsUntil(proposalListItem) .within(() => { cy.get(proposalDetails).should('contain.text', assetId); // 3001-VOTE-029 - cy.getByTestId('view-proposal-btn').click(); + cy.getByTestId(viewProposalBtn).click(); }); }); cy.get_proposal_information_from_table('Proposed enactment') // 3001-VOTE-044 diff --git a/apps/governance-e2e/src/support/governance.functions.js b/apps/governance-e2e/src/support/governance.functions.js index 3b2fba2bc..09affbf59 100644 --- a/apps/governance-e2e/src/support/governance.functions.js +++ b/apps/governance-e2e/src/support/governance.functions.js @@ -82,9 +82,10 @@ Cypress.Commands.add( Cypress.Commands.add( 'get_submitted_proposal_from_proposal_list', (proposalTitle) => { - cy.get_proposal_id_from_list(proposalTitle); - cy.get('@proposalIdText').then((proposalId) => { - return cy.get(`#${proposalId}`); + cy.get_proposal_id_from_list(proposalTitle).then(() => { + cy.get('@proposalIdText').then((proposalId) => { + return cy.get(`#${proposalId}`); + }); }); } ); diff --git a/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx b/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx index c1d781106..9a96f3c9e 100644 --- a/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx +++ b/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx @@ -56,7 +56,7 @@ export const VoteDetails = ({ {proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
-

+

@@ -105,7 +105,7 @@ export const VoteDetails = ({ )}

-

+

diff --git a/libs/cypress/src/index.ts b/libs/cypress/src/index.ts index 99ad4811c..47d6f6aac 100644 --- a/libs/cypress/src/index.ts +++ b/libs/cypress/src/index.ts @@ -19,6 +19,7 @@ import { addCreateMarket } from './lib/commands/create-market'; import { addConnectPublicKey } from './lib/commands/add-connect-public-key'; import { addVegaWalletSubmitProposal } from './lib/commands/vega-wallet-submit-proposal'; import { addGetNodes } from './lib/commands/get-nodes'; +import { addVegaWalletSubmitLiquidityProvision } from './lib/commands/vega-wallet-submit-liquidity-provision'; addGetTestIdcommand(); addSlackCommand(); @@ -39,6 +40,7 @@ addMockTransactionResponse(); addCreateMarket(); addConnectPublicKey(); addVegaWalletSubmitProposal(); +addVegaWalletSubmitLiquidityProvision(); export { mockConnectWallet } from './lib/commands/vega-wallet-connect'; export type { onMessage } from './lib/mock-ws'; diff --git a/libs/cypress/src/lib/commands/vega-wallet-submit-liquidity-provision.ts b/libs/cypress/src/lib/commands/vega-wallet-submit-liquidity-provision.ts new file mode 100644 index 000000000..fcd61e294 --- /dev/null +++ b/libs/cypress/src/lib/commands/vega-wallet-submit-liquidity-provision.ts @@ -0,0 +1,67 @@ +import { PeggedReference } from '@vegaprotocol/types'; +import type { LiquidityProvisionSubmission } from '@vegaprotocol/wallet'; +import { createWalletClient, sendVegaTx } from '../capsule/wallet-client'; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + VegaWalletSubmitLiquidityProvision( + marketId: string, + amount: string + ): void; + } + } +} + +export const addVegaWalletSubmitLiquidityProvision = () => { + Cypress.Commands.add( + 'VegaWalletSubmitLiquidityProvision', + (marketId, amount) => { + const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL'); + const token = Cypress.env('VEGA_WALLET_API_TOKEN'); + const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY'); + + const liquidityProvisionTx: LiquidityProvisionSubmission = { + liquidityProvisionSubmission: { + marketId: marketId, + commitmentAmount: amount, + fee: '0.001', + buys: [ + { + offset: '10', + proportion: '1', + reference: PeggedReference.PEGGED_REFERENCE_MID, + }, + { + offset: '12', + proportion: '2', + reference: PeggedReference.PEGGED_REFERENCE_MID, + }, + ], + sells: [ + { + offset: '10', + proportion: '2', + reference: PeggedReference.PEGGED_REFERENCE_MID, + }, + { + offset: '12', + proportion: '2', + reference: PeggedReference.PEGGED_REFERENCE_MID, + }, + ], + }, + pubKey: vegaPubKey, + propagate: true, + }; + + createWalletClient(vegaWalletUrl, token); + + cy.highlight('Submitting liquidity provision'); + + sendVegaTx(vegaPubKey, liquidityProvisionTx); + } + ); +}; diff --git a/libs/wallet/src/connectors/vega-connector.ts b/libs/wallet/src/connectors/vega-connector.ts index 4b394df8f..07ade069c 100644 --- a/libs/wallet/src/connectors/vega-connector.ts +++ b/libs/wallet/src/connectors/vega-connector.ts @@ -1,5 +1,25 @@ import { WalletClientError } from '@vegaprotocol/wallet-client'; import type * as Schema from '@vegaprotocol/types'; +import type { PeggedReference } from '@vegaprotocol/types'; + +export interface LiquidityProvisionSubmission { + liquidityProvisionSubmission: LiquidityProvisionBody; + pubKey: string; + propagate: boolean; +} +export interface LiquidityProvisionBody { + marketId: string; + commitmentAmount: string; + fee: string; + buys?: PeggedOrders[]; + sells?: PeggedOrders[]; +} + +export interface PeggedOrders { + offset: string; + proportion: string; + reference: PeggedReference; +} export interface DelegateSubmissionBody { delegateSubmission: { @@ -336,7 +356,8 @@ export type Transaction = | OrderAmendmentBody | ProposalSubmissionBody | BatchMarketInstructionSubmissionBody - | TransferBody; + | TransferBody + | LiquidityProvisionSubmission; export const isWithdrawTransaction = ( transaction: Transaction From f5ad2de5a5e583615b8deb055e5dca6dbebdd372 Mon Sep 17 00:00:00 2001 From: daro-maj <119658839+daro-maj@users.noreply.github.com> Date: Fri, 10 Mar 2023 15:37:49 +0100 Subject: [PATCH 5/8] test(trading): skip send report to artifact if tests are passed (#3154) --- .github/workflows/cypress-run.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cypress-run.yml b/.github/workflows/cypress-run.yml index 36696f31b..4c5481b76 100644 --- a/.github/workflows/cypress-run.yml +++ b/.github/workflows/cypress-run.yml @@ -87,7 +87,7 @@ jobs: run: ls -alsh /home/runner/.vegacapsule/testnet/logs/ - uses: actions/upload-artifact@v3 - if: ${{ always() }} + if: ${{ failure() }} with: name: logs-${{ matrix.project }} path: /home/runner/.vegacapsule/testnet/logs From b68136ba3f798de96fa1e2860c03d961bf9a83a0 Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Fri, 10 Mar 2023 06:43:32 -0800 Subject: [PATCH 6/8] chore(deposits): update faucet feedback i18n (#3132) Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com> Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> --- libs/deposits/src/lib/deposit-form.tsx | 2 +- libs/deposits/src/lib/faucet-notification.tsx | 45 ++++++++++++------- libs/deposits/src/lib/get-faucet-error.ts | 24 ++++++++++ 3 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 libs/deposits/src/lib/get-faucet-error.ts diff --git a/libs/deposits/src/lib/deposit-form.tsx b/libs/deposits/src/lib/deposit-form.tsx index 8b466d9d3..bf6f36e29 100644 --- a/libs/deposits/src/lib/deposit-form.tsx +++ b/libs/deposits/src/lib/deposit-form.tsx @@ -392,7 +392,7 @@ const FormButton = ({ approved, selectedAsset }: FormButtonProps) => { data-testid="deposit-submit" variant={isActive ? 'primary' : 'default'} fill={true} - disabled={invalidChain || (selectedAsset && !approved)} + disabled={invalidChain} > {t('Deposit')} diff --git a/libs/deposits/src/lib/faucet-notification.tsx b/libs/deposits/src/lib/faucet-notification.tsx index 9be1e4641..300bff7fd 100644 --- a/libs/deposits/src/lib/faucet-notification.tsx +++ b/libs/deposits/src/lib/faucet-notification.tsx @@ -3,6 +3,7 @@ import { useEnvironment } from '@vegaprotocol/environment'; import { t } from '@vegaprotocol/i18n'; import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit'; import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3'; +import { getFaucetError } from './get-faucet-error'; interface FaucetNotificationProps { isActive: boolean; @@ -36,13 +37,13 @@ export const FaucetNotification = ({ } if (tx.status === EthTxStatus.Error) { + const errorMessage = getFaucetError(tx.error, selectedAsset.symbol); return (

); @@ -55,7 +56,7 @@ export const FaucetNotification = ({ intent={Intent.Warning} testId="faucet-requested" message={t( - `Go to your Ethereum wallet and approve the faucet transaction for ${selectedAsset?.symbol}` + `Confirm the transaction in your Ethereum wallet to use the ${selectedAsset?.symbol} faucet` )} /> @@ -69,14 +70,21 @@ export const FaucetNotification = ({ intent={Intent.Primary} testId="faucet-pending" message={ -

- {t('Faucet pending...')}{' '} + <> +

+ {t( + 'Your request for funds from the %s faucet is being confirmed by the Ethereum network', + selectedAsset.symbol + )}{' '} +

{tx.txHash && ( - - {t('View on Etherscan')} - +

+ + {t('View on Etherscan')} + +

)} -

+ } /> @@ -90,14 +98,21 @@ export const FaucetNotification = ({ intent={Intent.Success} testId="faucet-confirmed" message={ -

- {t('Faucet successful')}{' '} + <> +

+ {t( + '%s has been deposited in your Ethereum wallet', + selectedAsset.symbol + )}{' '} +

{tx.txHash && ( - - {t('View on Etherscan')} - +

+ + {t('View on Etherscan')} + +

)} -

+ } /> diff --git a/libs/deposits/src/lib/get-faucet-error.ts b/libs/deposits/src/lib/get-faucet-error.ts new file mode 100644 index 000000000..40121c747 --- /dev/null +++ b/libs/deposits/src/lib/get-faucet-error.ts @@ -0,0 +1,24 @@ +import { t } from '@vegaprotocol/i18n'; +import type { TxError } from '@vegaprotocol/web3'; + +export const getFaucetError = (error: TxError | null, symbol: string) => { + const reasonMap: { + [reason: string]: string; + } = { + 'faucet not enabled': t( + 'The %s faucet is not available at this time', + symbol + ), + 'must wait faucetCallLimit between faucet calls': t( + 'You have exceeded the maximum number of faucet attempts allowed' + ), + 'user rejected transaction': t( + 'The faucet transaction was rejected by the connected Ethereum wallet' + ), + }; + // render a customized failure message from the map above or fallback + // to a non generic error message + return error && 'reason' in error && reasonMap[error.reason] + ? reasonMap[error.reason] + : t('Faucet of %s failed', symbol); +}; From 9d346d7846404e4acc372792b02d51aafc4172ab Mon Sep 17 00:00:00 2001 From: Art Date: Fri, 10 Mar 2023 16:46:51 +0100 Subject: [PATCH 7/8] feat(ui-toolkit): navigation (#3069) --- .../explorer-e2e/src/integration/blocks.cy.js | 20 +- .../src/integration/network.cy.js | 62 +-- apps/explorer/src/app/app.tsx | 56 +-- .../src/app/components/footer/footer.tsx | 2 +- .../src/app/components/header/header.spec.tsx | 6 +- .../src/app/components/header/header.tsx | 131 ++++-- .../explorer/src/app/components/nav/index.tsx | 1 - apps/explorer/src/app/components/nav/nav.tsx | 181 -------- .../src/app/components/search/search.tsx | 134 ++++-- .../explorer/src/app/routes/router-config.tsx | 29 +- apps/explorer/src/app/setup-tests.ts | 10 + .../src/integration/flow/staking-flow.cy.js | 7 +- .../src/integration/view/home.cy.js | 52 +-- .../src/integration/view/pages.cy.js | 284 +++++++++++++ .../src/integration/view/proposal.cy.js | 68 --- .../src/integration/view/pubkey-view.cy.js | 2 +- .../src/integration/view/rewards.cy.js | 35 -- .../src/integration/view/token.cy.js | 3 +- .../src/integration/view/tranches.cy.js | 8 +- .../src/integration/view/validators.cy.js | 27 +- .../src/integration/view/vesting.cy.js | 29 +- .../src/integration/view/wallet-eth.cy.js | 19 +- .../src/integration/view/wallet-vega.cy.js | 203 +++------ .../src/integration/view/withdraw.cy.js | 27 -- .../src/support/common.functions.js | 23 +- .../src/support/staking.functions.js | 6 +- .../src/support/wallet-eth.functions.js | 5 +- .../src/support/wallet-teardown.functions.js | 4 +- .../src/components/nav/nav-draw.tsx | 126 ------ .../src/components/nav/nav-dropdown.tsx | 50 --- .../src/components/nav/nav-link.tsx | 57 --- apps/governance/src/components/nav/nav.css | 25 -- .../src/components/nav/nav.spec.tsx | 56 --- apps/governance/src/components/nav/nav.tsx | 150 +++---- .../page-templates/template-sidebar.tsx | 2 +- .../contexts/app-state/app-state-context.ts | 3 - .../contexts/app-state/app-state-provider.tsx | 5 - .../components/list-asset/list-asset.spec.tsx | 1 - .../hooks/use-vote-information.spec.ts | 1 - apps/governance/src/routes/routes.ts | 1 + apps/trading-e2e/src/integration/navbar.cy.ts | 8 +- apps/trading/components/navbar/navbar.tsx | 267 ++++-------- .../vega-wallet-connect-button.tsx | 2 +- apps/trading/pages/_app.page.tsx | 6 +- .../lib/commands/add-connect-public-key.ts | 2 +- .../src/lib/commands/vega-wallet-connect.ts | 2 +- .../network-switcher/network-switcher.tsx | 23 +- libs/tailwindcss-config/src/theme.js | 1 + .../src/components/drawer/drawer.stories.tsx | 2 +- .../src/components/drawer/drawer.tsx | 3 +- .../dropdown-menu/dropdown-menu.tsx | 2 +- libs/ui-toolkit/src/components/index.ts | 15 +- .../components/input-error/input-error.tsx | 3 +- .../src/components/navigation/index.ts | 3 + .../navigation/navigation-drawer.tsx | 136 ++++++ .../components/navigation/navigation-utils.ts | 97 +++++ .../navigation/navigation.stories.tsx | 251 +++++++++++ .../src/components/navigation/navigation.tsx | 388 ++++++++++++++++++ .../src/components/theme-switcher/icons.tsx | 12 +- .../src/components/vega-logo/vega-logo.tsx | 9 +- package.json | 1 + yarn.lock | 21 + 62 files changed, 1778 insertions(+), 1387 deletions(-) delete mode 100644 apps/explorer/src/app/components/nav/index.tsx delete mode 100644 apps/explorer/src/app/components/nav/nav.tsx create mode 100644 apps/governance-e2e/src/integration/view/pages.cy.js delete mode 100644 apps/governance-e2e/src/integration/view/proposal.cy.js delete mode 100644 apps/governance-e2e/src/integration/view/rewards.cy.js delete mode 100644 apps/governance-e2e/src/integration/view/withdraw.cy.js delete mode 100644 apps/governance/src/components/nav/nav-draw.tsx delete mode 100644 apps/governance/src/components/nav/nav-dropdown.tsx delete mode 100644 apps/governance/src/components/nav/nav-link.tsx delete mode 100644 apps/governance/src/components/nav/nav.css delete mode 100644 apps/governance/src/components/nav/nav.spec.tsx create mode 100644 libs/ui-toolkit/src/components/navigation/index.ts create mode 100644 libs/ui-toolkit/src/components/navigation/navigation-drawer.tsx create mode 100644 libs/ui-toolkit/src/components/navigation/navigation-utils.ts create mode 100644 libs/ui-toolkit/src/components/navigation/navigation.stories.tsx create mode 100644 libs/ui-toolkit/src/components/navigation/navigation.tsx diff --git a/apps/explorer-e2e/src/integration/blocks.cy.js b/apps/explorer-e2e/src/integration/blocks.cy.js index aeb680b73..db664b1da 100644 --- a/apps/explorer-e2e/src/integration/blocks.cy.js +++ b/apps/explorer-e2e/src/integration/blocks.cy.js @@ -1,33 +1,27 @@ context('Blocks page', { tags: '@regression' }, function () { - before('visit token home page', function () { - cy.visit('/'); - }); - describe('Verify elements on page', function () { - const blockNavigation = 'a[href="/blocks"]'; + beforeEach(() => { + cy.visit('/blocks'); + }); const blockHeight = '[data-testid="block-height"]'; const blockTime = '[data-testid="block-time"]'; const blockHeader = '[data-testid="block-header"]'; const previousBlockBtn = '[data-testid="previous-block"]'; const infiniteScrollWrapper = '[data-testid="infinite-scroll-wrapper"]'; - beforeEach('navigate to blocks page', function () { - cy.get(blockNavigation).click(); - }); - it('Blocks page is displayed', function () { validateBlocksDisplayed(); }); it('Blocks page is displayed on mobile', function () { - cy.common_switch_to_mobile_and_click_toggle(); - cy.get(blockNavigation).click(); + cy.switchToMobile(); validateBlocksDisplayed(); }); it('Block validator page is displayed', function () { waitForBlocksResponse(); - cy.get(blockHeight).eq(0).click(); + cy.get(blockHeight).eq(0).find('a').click({ force: true }); + cy.get('[data-testid="block-validator"]').should('not.be.empty'); cy.get(blockTime).should('not.be.empty'); //TODO: Add assertion for transactions when txs are added @@ -35,7 +29,7 @@ context('Blocks page', { tags: '@regression' }, function () { it('Navigate to previous block', function () { waitForBlocksResponse(); - cy.get(blockHeight).eq(0).click(); + cy.get(blockHeight).eq(0).find('a').click({ force: true }); cy.get(blockHeader) .invoke('text') .then(($blockHeaderTxt) => { diff --git a/apps/explorer-e2e/src/integration/network.cy.js b/apps/explorer-e2e/src/integration/network.cy.js index cea6b65f9..2de12f213 100644 --- a/apps/explorer-e2e/src/integration/network.cy.js +++ b/apps/explorer-e2e/src/integration/network.cy.js @@ -2,17 +2,14 @@ context('Network parameters page', { tags: '@smoke' }, function () { before('navigate to network parameter page', function () { cy.fixture('net_parameter_format_lookup').as('networkParameterFormat'); }); - describe('Verify elements on page', function () { - const networkParametersNavigation = 'a[href="/network-parameters"]'; + beforeEach(() => { + cy.visit('/network-parameters'); + }); + const networkParametersHeader = '[data-testid="network-param-header"]'; const tableRows = '[data-testid="key-value-table-row"]'; - before('navigate to network parameter page', function () { - cy.visit('/'); - cy.get(networkParametersNavigation).click(); - }); - it('should show network parameter heading at top of page', function () { cy.get(networkParametersHeader) .should('have.text', 'Network Parameters') @@ -201,55 +198,8 @@ context('Network parameters page', { tags: '@smoke' }, function () { }); }); - it('should be able to switch network parameter page - between light and dark mode', function () { - const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)'; - const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)'; - const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)'; - const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)'; - const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)'; - const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)'; - const themeSwitcher = '[data-testid="theme-switcher"]'; - const jsonFields = '.hljs'; - const sideMenuBackground = '.absolute'; - - // Engage dark mode if not already set - cy.get(sideMenuBackground) - .should('have.css', 'background-color') - .then((background_color) => { - if (background_color.includes(whiteThemeSideMenuBackgroundColor)) - cy.get(themeSwitcher).click(); - }); - - // Engage white mode - cy.get(themeSwitcher).click(); - - // White Mode - cy.get(networkParametersNavigation) - .should('have.css', 'background-color') - .and('include', whiteThemeSelectedMenuOptionColor); - cy.get(jsonFields) - .should('have.css', 'background-color') - .and('include', whiteThemeJsonFieldBackColor); - cy.get(sideMenuBackground) - .should('have.css', 'background-color') - .and('include', whiteThemeSideMenuBackgroundColor); - - // Dark Mode - cy.get(themeSwitcher).click(); - cy.get(networkParametersNavigation) - .should('have.css', 'background-color') - .and('include', darkThemeSelectedMenuOptionColor); - cy.get(jsonFields) - .should('have.css', 'background-color') - .and('include', darkThemeJsonFieldBackColor); - cy.get(sideMenuBackground) - .should('have.css', 'background-color') - .and('include', darkThemeSideMenuBackgroundColor); - }); - - it.skip('should be able to see network parameters - on mobile', function () { - cy.common_switch_to_mobile_and_click_toggle(); - cy.get(networkParametersNavigation).click(); + it('should be able to see network parameters - on mobile', function () { + cy.switchToMobile(); cy.get_network_parameters().then((network_parameters) => { network_parameters = Object.entries(network_parameters); network_parameters.forEach((network_parameter) => { diff --git a/apps/explorer/src/app/app.tsx b/apps/explorer/src/app/app.tsx index 1f292caed..8aac5da50 100644 --- a/apps/explorer/src/app/app.tsx +++ b/apps/explorer/src/app/app.tsx @@ -1,6 +1,4 @@ -import classnames from 'classnames'; import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment'; -import { Nav } from './components/nav'; import { Header } from './components/header'; import { Main } from './components/main'; import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider'; @@ -11,6 +9,7 @@ import { useAssetDetailsDialogStore, } from '@vegaprotocol/assets'; import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client'; +import classNames from 'classnames'; const DialogsContainer = () => { const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore(); @@ -25,35 +24,42 @@ const DialogsContainer = () => { ); }; -function App() { - const layoutClasses = classnames( - 'grid grid-rows-[auto_1fr_auto] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)_auto] md:grid-cols-[300px_1fr]', - 'min-h-[100vh] mx-auto my-0', - 'border-neutral-700 dark:border-neutral-300 lg:border-l lg:border-r', - 'bg-white dark:bg-black', - 'antialiased text-black dark:text-white', - 'overflow-hidden relative' - ); +const MainnetSimAd = () => ( + +
+ Mainnet sim 2 is live! + + Come help stress test the network + +
+
+); +function App() { return ( - -
- Mainnet sim 2 is live! - - Come help stress test the network - +
+
+
+ +
+
+
+
+
+
- - -
-
-
- diff --git a/apps/explorer/src/app/components/footer/footer.tsx b/apps/explorer/src/app/components/footer/footer.tsx index ce817c809..ab1b5e755 100644 --- a/apps/explorer/src/app/components/footer/footer.tsx +++ b/apps/explorer/src/app/components/footer/footer.tsx @@ -16,7 +16,7 @@ export const Footer = () => { return ( <> -