diff --git a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-container.tsx b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-container.tsx
index aebd8a356..d2e71566a 100644
--- a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-container.tsx
+++ b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-container.tsx
@@ -1,5 +1,4 @@
import { useParams } from 'react-router-dom';
-import { DealTicketManager } from '@vegaprotocol/deal-ticket';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
@@ -50,10 +49,10 @@ export const DealTicketContainer = () => {
);
const container = (
-
+ <>
{loading ? loader : balance}
-
+ >
);
return (
diff --git a/apps/token/src/hooks/use-animate-value.ts b/apps/token/src/hooks/use-animate-value.ts
index c24a862b0..e2a769081 100644
--- a/apps/token/src/hooks/use-animate-value.ts
+++ b/apps/token/src/hooks/use-animate-value.ts
@@ -1,8 +1,8 @@
import React from 'react';
-import { usePrevious } from './use-previous';
import type { BigNumber } from '../lib/bignumber';
import { theme } from '@vegaprotocol/tailwindcss-config';
import colors from 'tailwindcss/colors';
+import { usePrevious } from '@vegaprotocol/react-helpers';
const customColors = theme.colors;
const FLASH_DURATION = 1200; // Duration of flash animation in milliseconds
diff --git a/apps/trading-e2e/.env b/apps/trading-e2e/.env
index d6f7eb7d7..5482d94ac 100644
--- a/apps/trading-e2e/.env
+++ b/apps/trading-e2e/.env
@@ -4,7 +4,7 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=''
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_ENV=CUSTOM
-NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
+NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"}
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_URL=http://localhost:3028/query
@@ -13,7 +13,7 @@ NX_VEGA_WALLET_URL=http://localhost:1789
# Expose some env vars to cypress environment for market setup
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
-CYPRESS_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
+CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
diff --git a/apps/trading-e2e/.env.capsule b/apps/trading-e2e/.env.capsule
index d6f7eb7d7..5482d94ac 100644
--- a/apps/trading-e2e/.env.capsule
+++ b/apps/trading-e2e/.env.capsule
@@ -4,7 +4,7 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=''
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_ENV=CUSTOM
-NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
+NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"}
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_URL=http://localhost:3028/query
@@ -13,7 +13,7 @@ NX_VEGA_WALLET_URL=http://localhost:1789
# Expose some env vars to cypress environment for market setup
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
-CYPRESS_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
+CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
diff --git a/apps/trading-e2e/src/integration/trading-orders.cy.ts b/apps/trading-e2e/src/integration/trading-orders.cy.ts
index af5bbff23..bf0a1bbcc 100644
--- a/apps/trading-e2e/src/integration/trading-orders.cy.ts
+++ b/apps/trading-e2e/src/integration/trading-orders.cy.ts
@@ -88,7 +88,7 @@ describe('orders list', { tags: '@smoke' }, () => {
cy.get(`[row-id="${partiallyFilledId}"]`).within(() => {
cy.get(`[col-id='${orderStatus}']`).should(
'have.text',
- 'PartiallyFilled'
+ 'Partially Filled'
);
cy.get(`[col-id='${orderRemaining}']`).should('have.text', '7/10');
cy.getByTestId(cancelOrderBtn).should('not.exist');
@@ -190,7 +190,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
});
cy.getByTestId(`order-status-${orderId}`).should(
'have.text',
- 'PartiallyFilled'
+ 'Partially Filled'
);
cy.getByTestId(`order-status-${orderId}`)
.parentsUntil(`.ag-row`)
diff --git a/apps/trading-e2e/src/support/order-validation.ts b/apps/trading-e2e/src/support/order-validation.ts
index c270de7a3..436ddee5e 100644
--- a/apps/trading-e2e/src/support/order-validation.ts
+++ b/apps/trading-e2e/src/support/order-validation.ts
@@ -21,6 +21,7 @@ export const testOrderSubmission = (
orderSubmission: expectedOrder,
};
vegaWalletTransaction(transaction);
+ verifyToast();
};
export const testOrderAmendment = (
@@ -36,6 +37,7 @@ export const testOrderAmendment = (
orderAmendment: expectedOrder,
};
vegaWalletTransaction(transaction);
+ verifyToast();
};
export const testOrderCancellation = (
@@ -51,11 +53,10 @@ export const testOrderCancellation = (
orderCancellation: expectedOrder,
};
vegaWalletTransaction(transaction);
+ verifyToast();
};
const vegaWalletTransaction = (transaction: Transaction) => {
- const dialogTitle = 'dialog-title';
- const orderTransactionHash = 'tx-block-explorer';
cy.wait('@VegaWalletTransaction')
.its('request.body.params')
.should('deep.equal', {
@@ -65,12 +66,13 @@ const vegaWalletTransaction = (transaction: Transaction) => {
sendingMode: 'TYPE_SYNC',
transaction,
});
- cy.getByTestId(dialogTitle).should(
- 'have.text',
- 'Awaiting network confirmation'
- );
- cy.getByTestId(orderTransactionHash)
- .invoke('attr', 'href')
- .should('include', `${Cypress.env('EXPLORER_URL')}/txs/0xtest-tx-hash`);
- cy.getByTestId('dialog-close').click();
+};
+
+const verifyToast = () => {
+ cy.getByTestId('toast').should('contain.text', 'Awaiting confirmation');
+ cy.getByTestId('toast')
+ .find('a')
+ .invoke('attr', 'href')
+ .should('include', `${Cypress.env('EXPLORER_URL')}/txs/test-tx-hash`);
+ cy.getByTestId('toast-close').click();
};
diff --git a/apps/trading/lib/hooks/use-ethereum-transaction-toasts.tsx b/apps/trading/lib/hooks/use-ethereum-transaction-toasts.tsx
new file mode 100644
index 000000000..ef5b960de
--- /dev/null
+++ b/apps/trading/lib/hooks/use-ethereum-transaction-toasts.tsx
@@ -0,0 +1,194 @@
+import { useAssetsDataProvider } from '@vegaprotocol/assets';
+import { ETHERSCAN_TX, useEtherscanLink } from '@vegaprotocol/environment';
+import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers';
+import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
+import { ExternalLink, Intent, ProgressBar } from '@vegaprotocol/ui-toolkit';
+import { useCallback, useMemo } from 'react';
+import compact from 'lodash/compact';
+import type { EthStoredTxState } from '@vegaprotocol/web3';
+import {
+ EthTxStatus,
+ isEthereumError,
+ TransactionContent,
+ useEthTransactionStore,
+} from '@vegaprotocol/web3';
+
+const intentMap: { [s in EthTxStatus]: Intent } = {
+ Default: Intent.Primary,
+ Requested: Intent.Warning,
+ Pending: Intent.Warning,
+ Error: Intent.Danger,
+ Complete: Intent.Success,
+ Confirmed: Intent.Success,
+};
+
+const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
+ const { data } = useAssetsDataProvider();
+ if (!data) return null;
+
+ const ETH_WITHDRAW =
+ tx.methodName === 'withdraw_asset' && tx.args.length > 2 && tx.assetId;
+ const ETH_DEPOSIT =
+ tx.methodName === 'deposit_asset' && tx.args.length > 2 && tx.assetId;
+
+ let label = '';
+ if (ETH_WITHDRAW) label = t('Withdraw');
+ if (ETH_DEPOSIT) label = t('Deposit');
+
+ if (ETH_WITHDRAW || ETH_DEPOSIT) {
+ const asset = data.find((a) => a.id === tx.assetId);
+
+ if (asset) {
+ const num = formatNumber(
+ toBigNum(tx.args[1], asset.decimals),
+ asset.decimals
+ );
+ const details = (
+
+
+ {label} {num} {asset.symbol}
+
+
+ );
+ return (
+ <>
+ {details}
+ {tx.requiresConfirmation &&
+ [EthTxStatus.Pending].includes(tx.status) && (
+
+
+ {t('Awaiting confirmations')}{' '}
+ {`(${tx.confirmations}/${tx.requiredConfirmations})`}
+
+
+
+ )}
+ >
+ );
+ }
+ }
+
+ return null;
+};
+
+type EthTxToastContentProps = {
+ tx: EthStoredTxState;
+};
+
+const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
+ return (
+
+
{t('Action required')}
+
+ {t(
+ 'Please go to your wallet application and approve or reject the transaction.'
+ )}
+
+
+
+ );
+};
+
+const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
+ const etherscanLink = useEtherscanLink();
+ return (
+
+
{t('Awaiting confirmation')}
+
{t('Please wait for your transaction to be confirmed')}
+ {tx.txHash && (
+
+
+ {t('View on Etherscan')}
+
+
+ )}
+
+
+ );
+};
+
+const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
+ let errorMessage = '';
+
+ if (isEthereumError(tx.error)) {
+ errorMessage = tx.error.reason;
+ } else if (tx.error instanceof Error) {
+ errorMessage = tx.error.message;
+ }
+ return (
+
+
{t('Error occurred')}
+
{errorMessage}
+
+
+ );
+};
+
+const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
+ const etherscanLink = useEtherscanLink();
+ return (
+
+
{t('Transaction completed')}
+
{t('Your transaction has been completed')}
+ {tx.txHash && (
+
+
+ {t('View on Etherscan')}
+
+
+ )}
+
+
+ );
+};
+
+export const useEthereumTransactionToasts = () => {
+ const ethTransactions = useEthTransactionStore((state) =>
+ state.transactions.filter((transaction) => transaction?.dialogOpen)
+ );
+ const dismissEthTransaction = useEthTransactionStore(
+ (state) => state.dismiss
+ );
+ const fromEthTransaction = useCallback(
+ (tx: EthStoredTxState): Toast => {
+ let content: ToastContent = ;
+ if (tx.status === EthTxStatus.Requested) {
+ content = ;
+ }
+ if (tx.status === EthTxStatus.Pending) {
+ content = ;
+ }
+ if (
+ tx.status === EthTxStatus.Confirmed ||
+ tx.status === EthTxStatus.Complete
+ ) {
+ content = ;
+ }
+ if (tx.status === EthTxStatus.Error) {
+ content = ;
+ }
+
+ return {
+ id: `eth-${tx.id}`,
+ intent: intentMap[tx.status],
+ onClose: () => dismissEthTransaction(tx.id),
+ loader: tx.status === EthTxStatus.Pending,
+ content,
+ };
+ },
+ [dismissEthTransaction]
+ );
+
+ return useMemo(() => {
+ return [...compact(ethTransactions).map(fromEthTransaction)];
+ }, [ethTransactions, fromEthTransaction]);
+};
diff --git a/apps/trading/lib/hooks/use-ethereum-withdraw-approval-toasts.tsx b/apps/trading/lib/hooks/use-ethereum-withdraw-approval-toasts.tsx
new file mode 100644
index 000000000..b3f7383df
--- /dev/null
+++ b/apps/trading/lib/hooks/use-ethereum-withdraw-approval-toasts.tsx
@@ -0,0 +1,78 @@
+import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers';
+import type { Toast } from '@vegaprotocol/ui-toolkit';
+import { Intent } from '@vegaprotocol/ui-toolkit';
+import { ApprovalStatus, VerificationStatus } from '@vegaprotocol/withdraws';
+import { useCallback, useMemo } from 'react';
+import compact from 'lodash/compact';
+import type { EthWithdrawalApprovalState } from '@vegaprotocol/web3';
+import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
+
+const intentMap: { [s in ApprovalStatus]: Intent } = {
+ Pending: Intent.Warning,
+ Error: Intent.Danger,
+ Idle: Intent.None,
+ Delayed: Intent.Warning,
+ Ready: Intent.Success,
+};
+
+const EthWithdrawalApprovalToastContent = ({
+ tx,
+}: {
+ tx: EthWithdrawalApprovalState;
+}) => {
+ let title = '';
+ if (tx.status === ApprovalStatus.Error) {
+ title = t('Error occurred');
+ }
+ if (tx.status === ApprovalStatus.Pending) {
+ title = t('Pending approval');
+ }
+ if (tx.status === ApprovalStatus.Delayed) {
+ title = t('Delayed');
+ }
+ const num = formatNumber(
+ toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals),
+ tx.withdrawal.asset.decimals
+ );
+ const details = (
+
+
+ {t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
+
+
+ );
+ return (
+
+ {title.length > 0 &&
{title}
}
+
+ {details}
+
+ );
+};
+
+export const useEthereumWithdrawApprovalsToasts = () => {
+ const { withdrawApprovals, dismissWithdrawApproval } =
+ useEthWithdrawApprovalsStore((state) => ({
+ withdrawApprovals: state.transactions.filter(
+ (transaction) => transaction?.dialogOpen
+ ),
+ dismissWithdrawApproval: state.dismiss,
+ }));
+
+ const fromWithdrawalApproval = useCallback(
+ (tx: EthWithdrawalApprovalState): Toast => ({
+ id: `withdrawal-${tx.id}`,
+ intent: intentMap[tx.status],
+ onClose: () => dismissWithdrawApproval(tx.id),
+ loader: tx.status === ApprovalStatus.Pending,
+ content: ,
+ }),
+ [dismissWithdrawApproval]
+ );
+
+ const toasts = useMemo(() => {
+ return [...compact(withdrawApprovals).map(fromWithdrawalApproval)];
+ }, [fromWithdrawalApproval, withdrawApprovals]);
+
+ return toasts;
+};
diff --git a/apps/trading/lib/hooks/use-vega-transaction-toasts.spec.tsx b/apps/trading/lib/hooks/use-vega-transaction-toasts.spec.tsx
new file mode 100644
index 000000000..6a4d9e3fe
--- /dev/null
+++ b/apps/trading/lib/hooks/use-vega-transaction-toasts.spec.tsx
@@ -0,0 +1,312 @@
+import { render } from '@testing-library/react';
+import {
+ OrderStatus,
+ OrderTimeInForce,
+ OrderType,
+ Side,
+} from '@vegaprotocol/types';
+import type { VegaStoredTxState } from '@vegaprotocol/wallet';
+import { VegaTxStatus } from '@vegaprotocol/wallet';
+import { VegaTransactionDetails } from './use-vega-transaction-toasts';
+
+jest.mock('@vegaprotocol/assets', () => {
+ const A1 = {
+ decimals: 2,
+ id: 'asset-1',
+ name: 'A1',
+ quantum: '10',
+ symbol: '$A',
+ };
+ return {
+ ...jest.requireActual('@vegaprotocol/assets'),
+ useAssetsDataProvider: jest.fn(() => ({ data: [A1] })),
+ };
+});
+
+jest.mock('@vegaprotocol/market-list', () => {
+ const M1 = {
+ id: 'market-1',
+ decimalPlaces: 2,
+ positionDecimalPlaces: 2,
+ tradableInstrument: {
+ instrument: {
+ id: 'M1',
+ name: 'M1',
+ code: 'M1',
+ product: {
+ quoteName: '',
+ settlementAsset: {
+ id: 'asset-1',
+ symbol: '$A',
+ decimals: 2,
+ },
+ },
+ },
+ },
+ };
+ return {
+ ...jest.requireActual('@vegaprotocol/market-list'),
+ useMarketList: jest.fn(() => ({ data: [M1] })),
+ };
+});
+
+jest.mock('@vegaprotocol/orders', () => {
+ return {
+ ...jest.requireActual('@vegaprotocol/orders'),
+ useOrderByIdQuery: jest.fn(({ variables: { orderId } }) => {
+ if (orderId === '0') {
+ return {
+ data: {
+ orderByID: {
+ id: '0',
+ side: 'SIDE_BUY',
+ size: '10',
+ timeInForce: 'TIME_IN_FORCE_FOK',
+ type: 'TYPE_MARKET',
+ price: '1234',
+ createdAt: new Date(),
+ status: 'STATUS_ACTIVE',
+ market: { id: 'market-1' },
+ },
+ },
+ };
+ } else {
+ return { data: undefined };
+ }
+ }),
+ };
+});
+
+const unsupportedTransaction: VegaStoredTxState = {
+ id: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ body: { delegateSubmission: { nodeId: '1', amount: '0' } },
+ status: VegaTxStatus.Default,
+ error: null,
+ txHash: null,
+ signature: null,
+ dialogOpen: false,
+};
+
+const withdraw: VegaStoredTxState = {
+ id: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ body: {
+ withdrawSubmission: {
+ amount: '1234',
+ asset: 'asset-1',
+ ext: { erc20: { receiverAddress: '0x0' } },
+ },
+ },
+ status: VegaTxStatus.Default,
+ error: null,
+ txHash: null,
+ signature: null,
+ dialogOpen: false,
+};
+
+const submitOrder: VegaStoredTxState = {
+ id: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ body: {
+ orderSubmission: {
+ marketId: 'market-1',
+ side: Side.SIDE_BUY,
+ size: '10',
+ timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
+ type: OrderType.TYPE_MARKET,
+ price: '1234',
+ },
+ },
+ status: VegaTxStatus.Default,
+ error: null,
+ txHash: null,
+ signature: null,
+ dialogOpen: false,
+ order: {
+ id: '0',
+ side: Side.SIDE_BUY,
+ size: '10',
+ timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
+ type: OrderType.TYPE_MARKET,
+ price: '1234',
+ createdAt: new Date(),
+ market: {
+ id: 'market-1',
+ decimalPlaces: 2,
+ positionDecimalPlaces: 2,
+ tradableInstrument: {
+ instrument: {
+ code: 'M1',
+ name: 'M1',
+ product: {
+ settlementAsset: {
+ decimals: 2,
+ symbol: '$A',
+ },
+ },
+ },
+ },
+ },
+ status: OrderStatus.STATUS_ACTIVE,
+ },
+};
+
+const editOrder: VegaStoredTxState = {
+ id: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ body: {
+ orderAmendment: {
+ marketId: 'market-1',
+ orderId: '0',
+ timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
+ price: '1000',
+ sizeDelta: 1,
+ },
+ },
+ status: VegaTxStatus.Default,
+ error: null,
+ txHash: null,
+ signature: null,
+ dialogOpen: false,
+ order: {
+ id: '0',
+ side: Side.SIDE_BUY,
+ size: '10',
+ timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
+ type: OrderType.TYPE_MARKET,
+ price: '1234',
+ createdAt: new Date(),
+ market: {
+ id: 'market-1',
+ decimalPlaces: 2,
+ positionDecimalPlaces: 2,
+ tradableInstrument: {
+ instrument: {
+ code: 'M1',
+ name: 'M1',
+ product: {
+ settlementAsset: {
+ decimals: 2,
+ symbol: '$A',
+ },
+ },
+ },
+ },
+ },
+ status: OrderStatus.STATUS_ACTIVE,
+ },
+};
+
+const cancelOrder: VegaStoredTxState = {
+ id: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ body: {
+ orderCancellation: {
+ marketId: 'market-1',
+ orderId: '0',
+ },
+ },
+ status: VegaTxStatus.Default,
+ error: null,
+ txHash: null,
+ signature: null,
+ dialogOpen: false,
+};
+
+const cancelAll: VegaStoredTxState = {
+ id: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ body: {
+ orderCancellation: {
+ marketId: undefined,
+ orderId: undefined,
+ },
+ },
+ status: VegaTxStatus.Default,
+ error: null,
+ txHash: null,
+ signature: null,
+ dialogOpen: false,
+};
+
+const closePosition: VegaStoredTxState = {
+ id: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ body: {
+ batchMarketInstructions: {
+ cancellations: [{ marketId: 'market-1', orderId: '' }],
+ submissions: [
+ {
+ marketId: 'market-1',
+ side: Side.SIDE_BUY,
+ size: '10',
+ timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
+ type: OrderType.TYPE_MARKET,
+ price: '1234',
+ },
+ ],
+ },
+ },
+ status: VegaTxStatus.Default,
+ error: null,
+ txHash: null,
+ signature: null,
+ dialogOpen: false,
+};
+
+const batch: VegaStoredTxState = {
+ id: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ body: {
+ batchMarketInstructions: {
+ submissions: [
+ {
+ marketId: 'market-1',
+ side: Side.SIDE_BUY,
+ size: '10',
+ timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
+ type: OrderType.TYPE_MARKET,
+ price: '1234',
+ },
+ ],
+ },
+ },
+ status: VegaTxStatus.Default,
+ error: null,
+ txHash: null,
+ signature: null,
+ dialogOpen: false,
+};
+
+describe('VegaTransactionDetails', () => {
+ it('does not display details if tx type cannot be determined', () => {
+ const { queryByTestId } = render(
+
+ );
+ expect(queryByTestId('vega-tx-details')).toBeNull();
+ });
+ it.each([
+ { tx: withdraw, details: 'Withdraw 12.34 $A' },
+ { tx: submitOrder, details: 'Submit order - activeM1+0.10 @ 12.34 $A' },
+ {
+ tx: editOrder,
+ details: 'Edit order - activeM1+0.10 @ 12.34 $A+0.11 @ 10.00 $A',
+ },
+ { tx: cancelOrder, details: 'Cancel orderM1+0.10 @ 12.34 $A' },
+ { tx: cancelAll, details: 'Cancel all orders' },
+ { tx: closePosition, details: 'Close position for M1' },
+ { tx: batch, details: 'Batch market instruction' },
+ ])('display details for transaction', ({ tx, details }) => {
+ const { queryByTestId } = render();
+ expect(queryByTestId('vega-tx-details')?.textContent).toEqual(details);
+ });
+});
diff --git a/apps/trading/lib/hooks/use-vega-transaction-toasts.tsx b/apps/trading/lib/hooks/use-vega-transaction-toasts.tsx
new file mode 100644
index 000000000..32169978d
--- /dev/null
+++ b/apps/trading/lib/hooks/use-vega-transaction-toasts.tsx
@@ -0,0 +1,567 @@
+import type { ReactNode } from 'react';
+import { useCallback, useMemo } from 'react';
+import compact from 'lodash/compact';
+import type {
+ BatchMarketInstructionSubmissionBody,
+ OrderAmendment,
+ OrderBusEventFieldsFragment,
+ OrderCancellationBody,
+ OrderSubmission,
+ VegaStoredTxState,
+ WithdrawalBusEventFieldsFragment,
+} from '@vegaprotocol/wallet';
+import { isBatchMarketInstructionsTransaction } from '@vegaprotocol/wallet';
+import {
+ ClientErrors,
+ useReconnectVegaWallet,
+ WalletError,
+} from '@vegaprotocol/wallet';
+import {
+ isOrderAmendmentTransaction,
+ isOrderCancellationTransaction,
+ isOrderSubmissionTransaction,
+ isWithdrawTransaction,
+ useVegaTransactionStore,
+ VegaTxStatus,
+} from '@vegaprotocol/wallet';
+import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
+import { Button, ExternalLink, Intent } from '@vegaprotocol/ui-toolkit';
+import {
+ addDecimalsFormatNumber,
+ formatNumber,
+ Size,
+ t,
+ toBigNum,
+} from '@vegaprotocol/react-helpers';
+import { useAssetsDataProvider } from '@vegaprotocol/assets';
+import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
+import { DApp, EXPLORER_TX, useLinks } from '@vegaprotocol/environment';
+import { getRejectionReason, useOrderByIdQuery } from '@vegaprotocol/orders';
+import { useMarketList } from '@vegaprotocol/market-list';
+import first from 'lodash/first';
+import type { Side } from '@vegaprotocol/types';
+import { OrderStatusMapping } from '@vegaprotocol/types';
+
+const intentMap: { [s in VegaTxStatus]: Intent } = {
+ Default: Intent.Primary,
+ Requested: Intent.Warning,
+ Pending: Intent.Warning,
+ Error: Intent.Danger,
+ Complete: Intent.Success,
+};
+
+const isClosePositionTransaction = (tx: VegaStoredTxState) => {
+ if (isBatchMarketInstructionsTransaction(tx.body)) {
+ const amendments =
+ tx.body.batchMarketInstructions.amendments &&
+ tx.body.batchMarketInstructions.amendments?.length > 0;
+
+ const cancellation =
+ tx.body.batchMarketInstructions.cancellations?.length === 1 &&
+ tx.body.batchMarketInstructions.cancellations[0].orderId === '' &&
+ tx.body.batchMarketInstructions.cancellations[0];
+
+ const submission =
+ cancellation &&
+ tx.body.batchMarketInstructions.submissions?.length === 1 &&
+ tx.body.batchMarketInstructions.submissions[0].marketId ===
+ cancellation.marketId;
+
+ return !amendments && cancellation && submission;
+ }
+ return false;
+};
+
+const isTransactionTypeSupported = (tx: VegaStoredTxState) => {
+ const withdraw = isWithdrawTransaction(tx.body);
+ const submitOrder = isOrderSubmissionTransaction(tx.body);
+ const cancelOrder = isOrderCancellationTransaction(tx.body);
+ const editOrder = isOrderAmendmentTransaction(tx.body);
+ const batchMarketInstructions = isBatchMarketInstructionsTransaction(tx.body);
+ return (
+ withdraw ||
+ submitOrder ||
+ cancelOrder ||
+ editOrder ||
+ batchMarketInstructions
+ );
+};
+
+const Details = ({
+ children,
+ title = '',
+}: {
+ children: ReactNode;
+ title?: string;
+}) => (
+
+);
+
+type SizeAtPriceProps = {
+ side: Side;
+ size: string;
+ price: string | undefined;
+ meta: { positionDecimalPlaces: number; decimalPlaces: number; asset: string };
+};
+const SizeAtPrice = ({ side, size, price, meta }: SizeAtPriceProps) => {
+ return (
+ <>
+ {' '}
+ {price && price !== '0' && meta.decimalPlaces
+ ? `@ ${addDecimalsFormatNumber(price, meta.decimalPlaces)} ${
+ meta.asset
+ }`
+ : `@ ~ ${meta.asset}`}
+ >
+ );
+};
+
+const SubmitOrderDetails = ({
+ data,
+ order,
+}: {
+ data: OrderSubmission;
+ order?: OrderBusEventFieldsFragment;
+}) => {
+ const { data: markets } = useMarketList();
+ const market = order
+ ? order.market
+ : markets?.find((m) => m.id === data.marketId);
+ if (!market) return null;
+
+ const price = order ? order.price : data.price;
+ const size = order ? order.size : data.size;
+ const side = order ? order.side : data.side;
+
+ return (
+
+
+ {order
+ ? t(
+ `Submit order - ${OrderStatusMapping[order.status].toLowerCase()}`
+ )
+ : t('Submit order')}
+
+ {market?.tradableInstrument.instrument.code}
+
+
+
+ {order && order.rejectionReason && (
+ {getRejectionReason(order)}
+ )}
+
+ );
+};
+
+const EditOrderDetails = ({
+ data,
+ order,
+}: {
+ data: OrderAmendment;
+ order?: OrderBusEventFieldsFragment;
+}) => {
+ const { data: orderById } = useOrderByIdQuery({
+ variables: { orderId: data.orderId },
+ });
+ const { data: markets } = useMarketList();
+
+ const originalOrder = orderById?.orderByID;
+ if (!originalOrder) return null;
+ const market = markets?.find((m) => m.id === originalOrder.market.id);
+ if (!market) return null;
+
+ const original = (
+
+ );
+
+ const edited = (
+
+ );
+
+ return (
+
+
+ {order
+ ? t(`Edit order - ${OrderStatusMapping[order.status].toLowerCase()}`)
+ : t('Edit order')}
+
+ {market?.tradableInstrument.instrument.code}
+
+ {original}
+
+ {edited}
+ {order && order.rejectionReason && (
+ {getRejectionReason(order)}
+ )}
+
+ );
+};
+
+const CancelOrderDetails = ({
+ orderId,
+ order,
+}: {
+ orderId: string;
+ order?: OrderBusEventFieldsFragment;
+}) => {
+ const { data: orderById } = useOrderByIdQuery({
+ variables: { orderId },
+ });
+ const { data: markets } = useMarketList();
+
+ const originalOrder = orderById?.orderByID;
+ if (!originalOrder) return null;
+ const market = markets?.find((m) => m.id === originalOrder.market.id);
+ if (!market) return null;
+
+ const original = (
+
+ );
+ return (
+
+
+ {order
+ ? t(
+ `Cancel order - ${OrderStatusMapping[order.status].toLowerCase()}`
+ )
+ : t('Cancel order')}
+
+ {market?.tradableInstrument.instrument.code}
+
+ {original}
+
+ {order && order.rejectionReason && (
+ {getRejectionReason(order)}
+ )}
+
+ );
+};
+
+export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
+ const { data: assets } = useAssetsDataProvider();
+ const { data: markets } = useMarketList();
+
+ if (isWithdrawTransaction(tx.body)) {
+ const transactionDetails = tx.body;
+ const asset = assets?.find(
+ (a) => a.id === transactionDetails.withdrawSubmission.asset
+ );
+ if (asset) {
+ const num = formatNumber(
+ toBigNum(transactionDetails.withdrawSubmission.amount, asset.decimals),
+ asset.decimals
+ );
+ return (
+
+ {t('Withdraw')} {num} {asset.symbol}
+
+ );
+ }
+ }
+
+ if (isOrderSubmissionTransaction(tx.body)) {
+ return (
+
+ );
+ }
+
+ if (isOrderCancellationTransaction(tx.body)) {
+ // CANCEL ALL (from Portfolio)
+ if (
+ tx.body.orderCancellation.marketId === undefined &&
+ tx.body.orderCancellation.orderId === undefined
+ ) {
+ return {t('Cancel all orders')} ;
+ }
+
+ // CANCEL
+ if (
+ tx.body.orderCancellation.orderId &&
+ tx.body.orderCancellation.marketId
+ ) {
+ return (
+
+ );
+ }
+
+ // CANCEL ALL (from Trading)
+ if (tx.body.orderCancellation.marketId) {
+ const marketName = markets?.find(
+ (m) =>
+ m.id === (tx.body as OrderCancellationBody).orderCancellation.marketId
+ )?.tradableInstrument.instrument.code;
+ return (
+
+ {marketName
+ ? `${t('Cancel all orders for')} ${marketName}`
+ : t('Cancel all orders')}
+
+ );
+ }
+ }
+
+ if (isOrderAmendmentTransaction(tx.body)) {
+ return (
+
+ );
+ }
+
+ if (isClosePositionTransaction(tx)) {
+ const transaction = tx.body as BatchMarketInstructionSubmissionBody;
+ const marketId = first(
+ transaction.batchMarketInstructions.cancellations
+ )?.marketId;
+ const market = marketId && markets?.find((m) => m.id === marketId);
+ if (market) {
+ return (
+
+ {t('Close position for')} {market.tradableInstrument.instrument.code}
+
+ );
+ }
+ }
+
+ if (isBatchMarketInstructionsTransaction(tx.body)) {
+ return {t('Batch market instruction')} ;
+ }
+
+ return null;
+};
+
+type VegaTxToastContentProps = { tx: VegaStoredTxState };
+
+const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => (
+
+
{t('Action required')}
+
+ {t(
+ 'Please go to your Vega wallet application and approve or reject the transaction.'
+ )}
+
+
+
+);
+
+const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
+ const explorerLink = useLinks(DApp.Explorer);
+ return (
+
+
{t('Awaiting confirmation')}
+
{t('Please wait for your transaction to be confirmed')}
+ {tx.txHash && (
+
+
+ {t('View in block explorer')}
+
+
+ )}
+
+
+ );
+};
+
+const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
+ const { createEthWithdrawalApproval } = useEthWithdrawApprovalsStore(
+ (state) => ({
+ createEthWithdrawalApproval: state.create,
+ })
+ );
+ const explorerLink = useLinks(DApp.Explorer);
+ if (isWithdrawTransaction(tx.body)) {
+ const completeWithdrawalButton = tx.withdrawal && (
+
+
+
+ );
+ return (
+
+
{t('Funds unlocked')}
+
{t('Your funds have been unlocked for withdrawal')}
+ {tx.txHash && (
+
+
+ {t('View in block explorer')}
+
+
+ )}
+
+ {completeWithdrawalButton}
+
+ );
+ }
+
+ return (
+
+
{t('Confirmed')}
+
{t('Your transaction has been confirmed ')}
+ {tx.txHash && (
+
+
+ {t('View in block explorer')}
+
+
+ )}
+
+
+ );
+};
+
+const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
+ let label = t('Error occurred');
+ let errorMessage = `${tx.error?.message} ${
+ tx.error instanceof WalletError && tx.error?.data
+ ? `: ${tx.error?.data}`
+ : ''
+ }`;
+ const reconnectVegaWallet = useReconnectVegaWallet();
+
+ const orderRejection = tx.order && getRejectionReason(tx.order);
+ const walletNoConnectionCodes = [
+ ClientErrors.NO_SERVICE.code,
+ ClientErrors.NO_CLIENT.code,
+ ];
+ const walletError =
+ tx.error instanceof WalletError &&
+ walletNoConnectionCodes.includes(tx.error.code);
+ if (orderRejection) {
+ label = t('Order rejected');
+ errorMessage = orderRejection;
+ }
+ if (walletError) {
+ label = t('Wallet disconnected');
+ errorMessage = t('The connection to your Vega Wallet has been lost.');
+ }
+
+ return (
+
+
{label}
+
{errorMessage}
+ {walletError && (
+
+ )}
+
+
+ );
+};
+
+export const useVegaTransactionToasts = () => {
+ const vegaTransactions = useVegaTransactionStore((state) =>
+ state.transactions.filter((transaction) => transaction?.dialogOpen)
+ );
+ const dismissVegaTransaction = useVegaTransactionStore(
+ (state) => state.dismiss
+ );
+
+ const fromVegaTransaction = useCallback(
+ (tx: VegaStoredTxState): Toast => {
+ let content: ToastContent;
+ if (tx.status === VegaTxStatus.Requested) {
+ content = ;
+ }
+ if (tx.status === VegaTxStatus.Pending) {
+ content = ;
+ }
+ if (tx.status === VegaTxStatus.Complete) {
+ content = ;
+ }
+ if (tx.status === VegaTxStatus.Error) {
+ content = ;
+ }
+ return {
+ id: `vega-${tx.id}`,
+ intent: intentMap[tx.status],
+ onClose: () => dismissVegaTransaction(tx.id),
+ loader: tx.status === VegaTxStatus.Pending,
+ content,
+ };
+ },
+ [dismissVegaTransaction]
+ );
+
+ const toasts = useMemo(() => {
+ return [
+ ...compact(vegaTransactions)
+ .filter((tx) => isTransactionTypeSupported(tx))
+ .map(fromVegaTransaction),
+ ];
+ }, [fromVegaTransaction, vegaTransactions]);
+
+ return toasts;
+};
diff --git a/apps/trading/pages/toasts-manager.tsx b/apps/trading/pages/toasts-manager.tsx
index ada6f8303..7e8f8d750 100644
--- a/apps/trading/pages/toasts-manager.tsx
+++ b/apps/trading/pages/toasts-manager.tsx
@@ -1,473 +1,32 @@
-import {
- Button,
- ExternalLink,
- Intent,
- ProgressBar,
- ToastsContainer,
-} from '@vegaprotocol/ui-toolkit';
-import { useCallback, useMemo } from 'react';
-import {
- useEthTransactionStore,
- useEthWithdrawApprovalsStore,
- TransactionContent,
- EthTxStatus,
- isEthereumError,
- ApprovalStatus,
-} from '@vegaprotocol/web3';
-import {
- isWithdrawTransaction,
- useVegaTransactionStore,
- VegaTxStatus,
- WalletError,
-} from '@vegaprotocol/wallet';
-import { VegaTransaction } from '../components/vega-transaction';
-import { VerificationStatus } from '@vegaprotocol/withdraws';
-import compact from 'lodash/compact';
+import { ToastsContainer } from '@vegaprotocol/ui-toolkit';
+import { useMemo } from 'react';
import sortBy from 'lodash/sortBy';
-import type {
- EthStoredTxState,
- EthWithdrawalApprovalState,
-} from '@vegaprotocol/web3';
-import type { Toast } from '@vegaprotocol/ui-toolkit';
-import type {
- VegaStoredTxState,
- WithdrawSubmissionBody,
- WithdrawalBusEventFieldsFragment,
-} from '@vegaprotocol/wallet';
-import type { Asset } from '@vegaprotocol/assets';
-import { useAssetsDataProvider } from '@vegaprotocol/assets';
-import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers';
-import {
- DApp,
- ETHERSCAN_TX,
- EXPLORER_TX,
- useEtherscanLink,
- useLinks,
-} from '@vegaprotocol/environment';
-import { prepend0x } from '@vegaprotocol/smart-contracts';
import { useUpdateNetworkParametersToasts } from '@vegaprotocol/governance';
-const intentMap = {
- Default: Intent.Primary,
- Requested: Intent.Warning,
- Pending: Intent.Warning,
- Error: Intent.Danger,
- Complete: Intent.Success,
- Confirmed: Intent.Success,
- Idle: Intent.None,
- Delayed: Intent.Warning,
- Ready: Intent.Success,
-};
-
-const TransactionDetails = ({
- label,
- amount,
- asset,
-}: {
- label: string;
- amount: string;
- asset: Pick;
-}) => {
- const num = formatNumber(toBigNum(amount, asset.decimals), asset.decimals);
- return (
-
-
- {label} {num} {asset.symbol}
-
-
- );
-};
-
-const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
- const { data } = useAssetsDataProvider();
- if (!data) return null;
-
- const VEGA_WITHDRAW = isWithdrawTransaction(tx.body);
- if (VEGA_WITHDRAW) {
- const transactionDetails = tx.body as WithdrawSubmissionBody;
- const asset = data?.find(
- (a) => a.id === transactionDetails.withdrawSubmission.asset
- );
- if (asset) {
- return (
-
- );
- }
- }
-
- return null;
-};
-
-const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
- const { data } = useAssetsDataProvider();
- if (!data) return null;
-
- const ETH_WITHDRAW =
- tx.methodName === 'withdraw_asset' && tx.args.length > 2 && tx.asset;
- if (ETH_WITHDRAW) {
- const asset = data.find((a) => a.id === tx.asset);
-
- if (asset) {
- return (
- <>
-
- {tx.requiresConfirmation && (
-
-
- {t('Awaiting confirmations')}{' '}
- {`(${tx.confirmations}/${tx.requiredConfirmations})`}
-
-
-
- )}
- >
- );
- }
- }
-
- return null;
-};
+import { useVegaTransactionToasts } from '../lib/hooks/use-vega-transaction-toasts';
+import { useEthereumTransactionToasts } from '../lib/hooks/use-ethereum-transaction-toasts';
+import { useEthereumWithdrawApprovalsToasts } from '../lib/hooks/use-ethereum-withdraw-approval-toasts';
export const ToastsManager = () => {
const updateNetworkParametersToasts = useUpdateNetworkParametersToasts();
- const vegaTransactions = useVegaTransactionStore((state) =>
- state.transactions.filter((transaction) => transaction?.dialogOpen)
- );
- const dismissVegaTransaction = useVegaTransactionStore(
- (state) => state.dismiss
- );
- const ethTransactions = useEthTransactionStore((state) =>
- state.transactions.filter((transaction) => transaction?.dialogOpen)
- );
- const dismissEthTransaction = useEthTransactionStore(
- (state) => state.dismiss
- );
- const { withdrawApprovals, createEthWithdrawalApproval } =
- useEthWithdrawApprovalsStore((state) => ({
- withdrawApprovals: state.transactions.filter(
- (transaction) => transaction?.dialogOpen
- ),
- createEthWithdrawalApproval: state.create,
- }));
- const dismissWithdrawApproval = useEthWithdrawApprovalsStore(
- (state) => state.dismiss
- );
- const explorerLink = useLinks(DApp.Explorer);
- const etherscanLink = useEtherscanLink();
-
- const fromVegaTransaction = useCallback(
- (tx: VegaStoredTxState): Toast => {
- let toast: Partial = {};
- const defaultValues = {
- id: `vega-${tx.id}`,
- intent: intentMap[tx.status],
- render: () => {
- return ;
- },
- onClose: () => dismissVegaTransaction(tx.id),
- };
- if (tx.status === VegaTxStatus.Requested) {
- toast = {
- render: () => {
- return (
-
-
{t('Action required')}
-
- {t(
- 'Please go to your Vega wallet application and approve or reject the transaction.'
- )}
-
-
-
- );
- },
- };
- }
- if (tx.status === VegaTxStatus.Pending) {
- toast = {
- render: () => {
- return (
-
-
{t('Awaiting confirmation')}
-
{t('Please wait for your transaction to be confirmed')}
- {tx.txHash && (
-
-
- {t('View in block explorer')}
-
-
- )}
-
-
- );
- },
- loader: true,
- };
- }
- if (tx.status === VegaTxStatus.Complete) {
- toast = {
- render: () => {
- if (isWithdrawTransaction(tx.body)) {
- const completeWithdrawalButton = tx.withdrawal && (
-
-
-
- );
- return (
-
-
{t('Funds unlocked')}
-
{t('Your funds have been unlocked for withdrawal')}
- {tx.txHash && (
-
-
- {t('View in block explorer')}
-
-
- )}
-
- {completeWithdrawalButton}
-
- );
- }
-
- return (
-
-
{t('Confirmed')}
-
{t('Your transaction has been confirmed ')}
- {tx.txHash && (
-
-
- {t('View in block explorer')}
-
-
- )}
-
-
- );
- },
- };
- }
- if (tx.status === VegaTxStatus.Error) {
- toast = {
- render: () => {
- const error = `${tx.error?.message} ${
- tx.error instanceof WalletError
- ? tx.error?.data
- ? `: ${tx.error?.data}`
- : ''
- : ''
- }`;
- return (
-
-
{t('Error occurred')}
-
{error}
-
-
- );
- },
- };
- }
- return {
- ...defaultValues,
- ...toast,
- };
- },
- [createEthWithdrawalApproval, dismissVegaTransaction, explorerLink]
- );
-
- const fromEthTransaction = useCallback(
- (tx: EthStoredTxState): Toast => {
- let toast: Partial = {};
- const defaultValues = {
- id: `eth-${tx.id}`,
- intent: intentMap[tx.status],
- render: () => {
- return ;
- },
- onClose: () => dismissEthTransaction(tx.id),
- };
- if (tx.status === EthTxStatus.Requested) {
- toast = {
- render: () => {
- return (
-
-
{t('Action required')}
-
- {t(
- 'Please go to your wallet application and approve or reject the transaction.'
- )}
-
-
-
- );
- },
- };
- }
- if (tx.status === EthTxStatus.Pending) {
- toast = {
- render: () => {
- return (
-
-
{t('Awaiting confirmation')}
-
{t('Please wait for your transaction to be confirmed')}
- {tx.txHash && (
-
-
- {t('View on Etherscan')}
-
-
- )}
-
-
- );
- },
- loader: true,
- };
- }
- if (tx.status === EthTxStatus.Confirmed) {
- toast = {
- render: () => {
- return (
-
-
{t('Transaction completed')}
-
{t('Your transaction has been completed')}
- {tx.txHash && (
-
-
- {t('View on Etherscan')}
-
-
- )}
-
-
- );
- },
- };
- }
- if (tx.status === EthTxStatus.Error) {
- toast = {
- render: () => {
- let errorMessage = '';
-
- if (isEthereumError(tx.error)) {
- errorMessage = tx.error.reason;
- } else if (tx.error instanceof Error) {
- errorMessage = tx.error.message;
- }
- return (
-
-
{t('Error occurred')}
-
{errorMessage}
-
-
- );
- },
- };
- }
- return {
- ...defaultValues,
- ...toast,
- };
- },
- [dismissEthTransaction, etherscanLink]
- );
-
- const fromWithdrawalApproval = useCallback(
- (tx: EthWithdrawalApprovalState): Toast => ({
- id: `withdrawal-${tx.id}`,
- intent: intentMap[tx.status],
- render: () => {
- let title = '';
- if (tx.status === ApprovalStatus.Error) {
- title = t('Error occurred');
- }
- if (tx.status === ApprovalStatus.Pending) {
- title = t('Pending approval');
- }
- if (tx.status === ApprovalStatus.Delayed) {
- title = t('Delayed');
- }
- return (
-
- {title.length > 0 &&
{title}
}
-
-
-
- );
- },
- onClose: () => dismissWithdrawApproval(tx.id),
-
- loader: tx.status === ApprovalStatus.Pending,
- }),
- [dismissWithdrawApproval]
- );
+ const vegaTransactionToasts = useVegaTransactionToasts();
+ const ethTransactionToasts = useEthereumTransactionToasts();
+ const withdrawApprovalToasts = useEthereumWithdrawApprovalsToasts();
const toasts = useMemo(() => {
return sortBy(
[
- ...compact(vegaTransactions).map(fromVegaTransaction),
- ...compact(ethTransactions).map(fromEthTransaction),
- ...compact(withdrawApprovals).map(fromWithdrawalApproval),
+ ...vegaTransactionToasts,
+ ...ethTransactionToasts,
+ ...withdrawApprovalToasts,
...updateNetworkParametersToasts,
],
['createdBy']
);
}, [
- vegaTransactions,
- fromVegaTransaction,
- ethTransactions,
- fromEthTransaction,
- withdrawApprovals,
- fromWithdrawalApproval,
+ vegaTransactionToasts,
+ ethTransactionToasts,
+ withdrawApprovalToasts,
updateNetworkParametersToasts,
]);
diff --git a/libs/accounts/src/lib/use-account-balance.tsx b/libs/accounts/src/lib/use-account-balance.tsx
index 02538c7d2..1fb706996 100644
--- a/libs/accounts/src/lib/use-account-balance.tsx
+++ b/libs/accounts/src/lib/use-account-balance.tsx
@@ -5,7 +5,7 @@ import { accountsDataProvider } from './accounts-data-provider';
import type { Account } from './accounts-data-provider';
import { getSettlementAccount } from './get-settlement-account';
-export const useAccountBalance = (assetId: string) => {
+export const useAccountBalance = (assetId?: string) => {
const { pubKey } = useVegaWallet();
const [accountBalance, setAccountBalance] = useState('');
const [accountDecimals, setAccountDecimals] = useState(null);
@@ -14,7 +14,9 @@ export const useAccountBalance = (assetId: string) => {
}, [pubKey]);
const update = useCallback(
({ data }: { data: Account[] | null }) => {
- const account = getSettlementAccount({ accounts: data, assetId });
+ const account = assetId
+ ? getSettlementAccount({ accounts: data, assetId })
+ : undefined;
if (accountBalance !== account?.balance) {
setAccountBalance(account?.balance || '');
}
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-button.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-button.tsx
index d7cea7c8a..d6ea6b82b 100644
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-button.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-button.tsx
@@ -4,31 +4,25 @@ import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
interface Props {
- transactionStatus: 'default' | 'pending';
disabled: boolean;
variant: ButtonVariant;
}
-export const DealTicketButton = ({
- transactionStatus,
- disabled,
- variant,
-}: Props) => {
+export const DealTicketButton = ({ disabled, variant }: Props) => {
const { pubKey } = useVegaWallet();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
- const isPending = transactionStatus === 'pending';
return pubKey ? (
) : (
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx
index 11c0a2c63..42407cd85 100644
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx
@@ -5,8 +5,9 @@ import type {
MarketDataUpdateFieldsFragment,
MarketDealTicket,
} from '@vegaprotocol/market-list';
+import { useVegaTransactionStore } from '@vegaprotocol/wallet';
import { marketDealTicketProvider } from '@vegaprotocol/market-list';
-import { DealTicketManager } from './deal-ticket-manager';
+import { DealTicket } from './deal-ticket';
export interface DealTicketContainerProps {
marketId: string;
@@ -27,6 +28,7 @@ export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => {
variables,
skip: !marketId,
});
+ const create = useVegaTransactionStore((state) => state.create);
return (
@@ -35,7 +37,10 @@ export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => {
error={error}
>
{data ? (
-
+ create({ orderSubmission })}
+ />
) : (
{t('Could not load market')}
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx
deleted file mode 100644
index 621200eb5..000000000
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import { useCallback, useMemo } from 'react';
-import type { ReactNode } from 'react';
-import type { VegaTxState } from '@vegaprotocol/wallet';
-import {
- VegaTxStatus,
- WalletError,
- useVegaWallet,
- useVegaWalletDialogStore,
- ClientErrors,
-} from '@vegaprotocol/wallet';
-import { DealTicket } from './deal-ticket';
-import type { MarketDealTicket } from '@vegaprotocol/market-list';
-import { useOrderSubmit, OrderFeedback } from '@vegaprotocol/orders';
-import * as Schema from '@vegaprotocol/types';
-import { Button, Icon, Intent } from '@vegaprotocol/ui-toolkit';
-import { t } from '@vegaprotocol/react-helpers';
-
-export interface DealTicketManagerProps {
- market: MarketDealTicket;
- children?: ReactNode | ReactNode[];
-}
-
-interface ErrorContentProps {
- transaction: VegaTxState;
- reset: () => void;
-}
-const ErrorContent = ({ transaction, reset }: ErrorContentProps) => {
- const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
- openVegaWalletDialog: store.openVegaWalletDialog,
- }));
- const { disconnect } = useVegaWallet();
- const reconnect = useCallback(async () => {
- reset();
- await disconnect();
- openVegaWalletDialog();
- }, [reset, disconnect, openVegaWalletDialog]);
- return useMemo(() => {
- const { error } = transaction;
- if (error) {
- if (
- error instanceof WalletError &&
- error.code === ClientErrors.NO_SERVICE.code
- ) {
- return (
-
- -
- {t('The connection to your Vega Wallet has been lost.')}
-
- -
-
-
-
- );
- }
- return (
-
- {error.message}{' '}
- {error instanceof WalletError ? `: ${error.data}` : null}
-
- );
- }
- return null;
- }, [transaction, reconnect]);
-};
-
-export const DealTicketManager = ({
- market,
- children,
-}: DealTicketManagerProps) => {
- const { submit, transaction, finalizedOrder, Dialog, reset } =
- useOrderSubmit();
- return (
- <>
- {children || (
- submit(order)}
- transactionStatus={
- transaction.status === VegaTxStatus.Requested ||
- transaction.status === VegaTxStatus.Pending
- ? 'pending'
- : 'default'
- }
- />
- )}
-
- ),
- Error: ,
- }}
- />
- >
- );
-};
-
-export const getOrderDialogTitle = (
- status?: Schema.OrderStatus
-): string | undefined => {
- if (!status) {
- return;
- }
-
- switch (status) {
- case Schema.OrderStatus.STATUS_ACTIVE:
- return t('Order submitted');
- case Schema.OrderStatus.STATUS_FILLED:
- return t('Order filled');
- case Schema.OrderStatus.STATUS_PARTIALLY_FILLED:
- return t('Order partially filled');
- case Schema.OrderStatus.STATUS_PARKED:
- return t('Order parked');
- case Schema.OrderStatus.STATUS_STOPPED:
- return t('Order stopped');
- case Schema.OrderStatus.STATUS_CANCELLED:
- return t('Order cancelled');
- case Schema.OrderStatus.STATUS_EXPIRED:
- return t('Order expired');
- case Schema.OrderStatus.STATUS_REJECTED:
- return t('Order rejected');
- default:
- return t('Submission failed');
- }
-};
-
-export const getOrderDialogIntent = (
- status?: Schema.OrderStatus
-): Intent | undefined => {
- if (!status) {
- return;
- }
- switch (status) {
- case Schema.OrderStatus.STATUS_PARKED:
- case Schema.OrderStatus.STATUS_EXPIRED:
- case Schema.OrderStatus.STATUS_PARTIALLY_FILLED:
- return Intent.Warning;
- case Schema.OrderStatus.STATUS_REJECTED:
- case Schema.OrderStatus.STATUS_STOPPED:
- case Schema.OrderStatus.STATUS_CANCELLED:
- return Intent.Danger;
- case Schema.OrderStatus.STATUS_FILLED:
- case Schema.OrderStatus.STATUS_ACTIVE:
- return Intent.Success;
- default:
- return;
- }
-};
-
-export const getOrderDialogIcon = (
- status?: Schema.OrderStatus
-): ReactNode | undefined => {
- if (!status) {
- return;
- }
-
- switch (status) {
- case Schema.OrderStatus.STATUS_PARKED:
- case Schema.OrderStatus.STATUS_EXPIRED:
- return ;
- case Schema.OrderStatus.STATUS_REJECTED:
- case Schema.OrderStatus.STATUS_STOPPED:
- case Schema.OrderStatus.STATUS_CANCELLED:
- return ;
- default:
- return;
- }
-};
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx
index 4c5d19156..8170b3fe8 100644
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx
@@ -26,7 +26,6 @@ jest.mock('../../hooks/use-has-no-balance', () => {
const market = generateMarket();
const submit = jest.fn();
-const transactionStatus = 'default';
const mockChainId = 'chain-id';
@@ -46,12 +45,7 @@ function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
return (
-
+
);
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 1ac186514..76e3d678d 100644
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx
@@ -1,4 +1,4 @@
-import { removeDecimal, t } from '@vegaprotocol/react-helpers';
+import { t } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import { memo, useCallback, useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
@@ -10,6 +10,7 @@ import { SideSelector } from './side-selector';
import { TimeInForceSelector } from './time-in-force-selector';
import { TypeSelector } from './type-selector';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
+import { normalizeOrderSubmission } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { InputError } from '@vegaprotocol/ui-toolkit';
import { useOrderMarginValidation } from '../../hooks/use-order-margin-validation';
@@ -32,8 +33,6 @@ export type TransactionStatus = 'default' | 'pending';
export interface DealTicketProps {
market: MarketDealTicket;
submit: (order: OrderSubmissionBody['orderSubmission']) => void;
- transactionStatus: TransactionStatus;
- defaultOrder?: OrderSubmissionBody['orderSubmission'];
}
export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
@@ -42,11 +41,7 @@ export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
summary: string;
};
-export const DealTicket = ({
- market,
- submit,
- transactionStatus,
-}: DealTicketProps) => {
+export const DealTicket = ({ market, submit }: DealTicketProps) => {
const { pubKey } = useVegaWallet();
const [persistedOrder, setPersistedOrder] = usePersistedOrder(market);
const {
@@ -123,15 +118,13 @@ export const DealTicket = ({
return;
}
- submit({
- ...order,
- price: order.price && removeDecimal(order.price, market.decimalPlaces),
- size: removeDecimal(order.size, market.positionDecimalPlaces),
- expiresAt:
- order.timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
- ? order.expiresAt
- : undefined,
- });
+ submit(
+ normalizeOrderSubmission(
+ order,
+ market.decimalPlaces,
+ market.positionDecimalPlaces
+ )
+ );
},
[
submit,
@@ -209,7 +202,6 @@ export const DealTicket = ({
)}
= 1}
- transactionStatus={transactionStatus}
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
/>
{
const { assetId, isOpen, open, close } = useDepositDialog();
+ const assetDetailsDialogOpen = useAssetDetailsDialogStore(
+ (state) => state.isOpen
+ );
const connectWalletDialogIsOpen = useWeb3ConnectStore(
(state) => state.isOpen
);
@@ -55,7 +59,7 @@ export const DepositDialog = () => {
);
return (