feat: deposit and deal ticket transaction stores and toasts (#2495)
This commit is contained in:
parent
4ce2924380
commit
5ccef2de5e
@ -1,5 +1,4 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { DealTicketManager } from '@vegaprotocol/deal-ticket';
|
|
||||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
@ -50,10 +49,10 @@ export const DealTicketContainer = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const container = (
|
const container = (
|
||||||
<DealTicketManager market={data}>
|
<>
|
||||||
{loading ? loader : balance}
|
{loading ? loader : balance}
|
||||||
<DealTicketSteps market={data} />
|
<DealTicketSteps market={data} />
|
||||||
</DealTicketManager>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { usePrevious } from './use-previous';
|
|
||||||
import type { BigNumber } from '../lib/bignumber';
|
import type { BigNumber } from '../lib/bignumber';
|
||||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||||
import colors from 'tailwindcss/colors';
|
import colors from 'tailwindcss/colors';
|
||||||
|
import { usePrevious } from '@vegaprotocol/react-helpers';
|
||||||
const customColors = theme.colors;
|
const customColors = theme.colors;
|
||||||
|
|
||||||
const FLASH_DURATION = 1200; // Duration of flash animation in milliseconds
|
const FLASH_DURATION = 1200; // Duration of flash animation in milliseconds
|
||||||
|
@ -4,7 +4,7 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
|||||||
NX_VEGA_CONFIG_URL=''
|
NX_VEGA_CONFIG_URL=''
|
||||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||||
NX_VEGA_ENV=CUSTOM
|
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_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_TOKEN_URL=https://token.fairground.wtf
|
||||||
NX_VEGA_URL=http://localhost:3028/query
|
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
|
# 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_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
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_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
||||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
||||||
|
@ -4,7 +4,7 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
|||||||
NX_VEGA_CONFIG_URL=''
|
NX_VEGA_CONFIG_URL=''
|
||||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||||
NX_VEGA_ENV=CUSTOM
|
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_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_TOKEN_URL=https://token.fairground.wtf
|
||||||
NX_VEGA_URL=http://localhost:3028/query
|
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
|
# 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_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
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_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
||||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
||||||
|
@ -88,7 +88,7 @@ describe('orders list', { tags: '@smoke' }, () => {
|
|||||||
cy.get(`[row-id="${partiallyFilledId}"]`).within(() => {
|
cy.get(`[row-id="${partiallyFilledId}"]`).within(() => {
|
||||||
cy.get(`[col-id='${orderStatus}']`).should(
|
cy.get(`[col-id='${orderStatus}']`).should(
|
||||||
'have.text',
|
'have.text',
|
||||||
'PartiallyFilled'
|
'Partially Filled'
|
||||||
);
|
);
|
||||||
cy.get(`[col-id='${orderRemaining}']`).should('have.text', '7/10');
|
cy.get(`[col-id='${orderRemaining}']`).should('have.text', '7/10');
|
||||||
cy.getByTestId(cancelOrderBtn).should('not.exist');
|
cy.getByTestId(cancelOrderBtn).should('not.exist');
|
||||||
@ -190,7 +190,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
|||||||
});
|
});
|
||||||
cy.getByTestId(`order-status-${orderId}`).should(
|
cy.getByTestId(`order-status-${orderId}`).should(
|
||||||
'have.text',
|
'have.text',
|
||||||
'PartiallyFilled'
|
'Partially Filled'
|
||||||
);
|
);
|
||||||
cy.getByTestId(`order-status-${orderId}`)
|
cy.getByTestId(`order-status-${orderId}`)
|
||||||
.parentsUntil(`.ag-row`)
|
.parentsUntil(`.ag-row`)
|
||||||
|
@ -21,6 +21,7 @@ export const testOrderSubmission = (
|
|||||||
orderSubmission: expectedOrder,
|
orderSubmission: expectedOrder,
|
||||||
};
|
};
|
||||||
vegaWalletTransaction(transaction);
|
vegaWalletTransaction(transaction);
|
||||||
|
verifyToast();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const testOrderAmendment = (
|
export const testOrderAmendment = (
|
||||||
@ -36,6 +37,7 @@ export const testOrderAmendment = (
|
|||||||
orderAmendment: expectedOrder,
|
orderAmendment: expectedOrder,
|
||||||
};
|
};
|
||||||
vegaWalletTransaction(transaction);
|
vegaWalletTransaction(transaction);
|
||||||
|
verifyToast();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const testOrderCancellation = (
|
export const testOrderCancellation = (
|
||||||
@ -51,11 +53,10 @@ export const testOrderCancellation = (
|
|||||||
orderCancellation: expectedOrder,
|
orderCancellation: expectedOrder,
|
||||||
};
|
};
|
||||||
vegaWalletTransaction(transaction);
|
vegaWalletTransaction(transaction);
|
||||||
|
verifyToast();
|
||||||
};
|
};
|
||||||
|
|
||||||
const vegaWalletTransaction = (transaction: Transaction) => {
|
const vegaWalletTransaction = (transaction: Transaction) => {
|
||||||
const dialogTitle = 'dialog-title';
|
|
||||||
const orderTransactionHash = 'tx-block-explorer';
|
|
||||||
cy.wait('@VegaWalletTransaction')
|
cy.wait('@VegaWalletTransaction')
|
||||||
.its('request.body.params')
|
.its('request.body.params')
|
||||||
.should('deep.equal', {
|
.should('deep.equal', {
|
||||||
@ -65,12 +66,13 @@ const vegaWalletTransaction = (transaction: Transaction) => {
|
|||||||
sendingMode: 'TYPE_SYNC',
|
sendingMode: 'TYPE_SYNC',
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
cy.getByTestId(dialogTitle).should(
|
};
|
||||||
'have.text',
|
|
||||||
'Awaiting network confirmation'
|
const verifyToast = () => {
|
||||||
);
|
cy.getByTestId('toast').should('contain.text', 'Awaiting confirmation');
|
||||||
cy.getByTestId(orderTransactionHash)
|
cy.getByTestId('toast')
|
||||||
.invoke('attr', 'href')
|
.find('a')
|
||||||
.should('include', `${Cypress.env('EXPLORER_URL')}/txs/0xtest-tx-hash`);
|
.invoke('attr', 'href')
|
||||||
cy.getByTestId('dialog-close').click();
|
.should('include', `${Cypress.env('EXPLORER_URL')}/txs/test-tx-hash`);
|
||||||
|
cy.getByTestId('toast-close').click();
|
||||||
};
|
};
|
||||||
|
194
apps/trading/lib/hooks/use-ethereum-transaction-toasts.tsx
Normal file
194
apps/trading/lib/hooks/use-ethereum-transaction-toasts.tsx
Normal file
@ -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 = (
|
||||||
|
<div className="mt-[5px]">
|
||||||
|
<span className="font-mono text-xs p-1 bg-gray-100 rounded">
|
||||||
|
{label} {num} {asset.symbol}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{details}
|
||||||
|
{tx.requiresConfirmation &&
|
||||||
|
[EthTxStatus.Pending].includes(tx.status) && (
|
||||||
|
<div className="mt-[10px]">
|
||||||
|
<span className="font-mono text-xs">
|
||||||
|
{t('Awaiting confirmations')}{' '}
|
||||||
|
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
|
||||||
|
</span>
|
||||||
|
<ProgressBar
|
||||||
|
value={(tx.confirmations / tx.requiredConfirmations) * 100}
|
||||||
|
intent={Intent.Warning}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EthTxToastContentProps = {
|
||||||
|
tx: EthStoredTxState;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{t('Action required')}</h3>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
'Please go to your wallet application and approve or reject the transaction.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<EthTransactionDetails tx={tx} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||||
|
const etherscanLink = useEtherscanLink();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
|
||||||
|
<p>{t('Please wait for your transaction to be confirmed')}</p>
|
||||||
|
{tx.txHash && (
|
||||||
|
<p className="break-all">
|
||||||
|
<ExternalLink
|
||||||
|
href={etherscanLink(ETHERSCAN_TX.replace(':hash', tx.txHash))}
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{t('View on Etherscan')}
|
||||||
|
</ExternalLink>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<EthTransactionDetails tx={tx} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{t('Error occurred')}</h3>
|
||||||
|
<p>{errorMessage}</p>
|
||||||
|
<EthTransactionDetails tx={tx} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||||
|
const etherscanLink = useEtherscanLink();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{t('Transaction completed')}</h3>
|
||||||
|
<p>{t('Your transaction has been completed')}</p>
|
||||||
|
{tx.txHash && (
|
||||||
|
<p className="break-all">
|
||||||
|
<ExternalLink
|
||||||
|
href={etherscanLink(ETHERSCAN_TX.replace(':hash', tx.txHash))}
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{t('View on Etherscan')}
|
||||||
|
</ExternalLink>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<EthTransactionDetails tx={tx} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = <TransactionContent {...tx} />;
|
||||||
|
if (tx.status === EthTxStatus.Requested) {
|
||||||
|
content = <EthTxRequestedToastContent tx={tx} />;
|
||||||
|
}
|
||||||
|
if (tx.status === EthTxStatus.Pending) {
|
||||||
|
content = <EthTxPendingToastContent tx={tx} />;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
tx.status === EthTxStatus.Confirmed ||
|
||||||
|
tx.status === EthTxStatus.Complete
|
||||||
|
) {
|
||||||
|
content = <EthTxConfirmedToastContent tx={tx} />;
|
||||||
|
}
|
||||||
|
if (tx.status === EthTxStatus.Error) {
|
||||||
|
content = <EthTxErrorToastContent tx={tx} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
};
|
@ -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 = (
|
||||||
|
<div className="mt-[5px]">
|
||||||
|
<span className="font-mono text-xs p-1 bg-gray-100 rounded">
|
||||||
|
{t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{title.length > 0 && <h3 className="font-bold">{title}</h3>}
|
||||||
|
<VerificationStatus state={tx} />
|
||||||
|
{details}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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: <EthWithdrawalApprovalToastContent tx={tx} />,
|
||||||
|
}),
|
||||||
|
[dismissWithdrawApproval]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toasts = useMemo(() => {
|
||||||
|
return [...compact(withdrawApprovals).map(fromWithdrawalApproval)];
|
||||||
|
}, [fromWithdrawalApproval, withdrawApprovals]);
|
||||||
|
|
||||||
|
return toasts;
|
||||||
|
};
|
312
apps/trading/lib/hooks/use-vega-transaction-toasts.spec.tsx
Normal file
312
apps/trading/lib/hooks/use-vega-transaction-toasts.spec.tsx
Normal file
@ -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(
|
||||||
|
<VegaTransactionDetails tx={unsupportedTransaction} />
|
||||||
|
);
|
||||||
|
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(<VegaTransactionDetails tx={tx} />);
|
||||||
|
expect(queryByTestId('vega-tx-details')?.textContent).toEqual(details);
|
||||||
|
});
|
||||||
|
});
|
567
apps/trading/lib/hooks/use-vega-transaction-toasts.tsx
Normal file
567
apps/trading/lib/hooks/use-vega-transaction-toasts.tsx
Normal file
@ -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;
|
||||||
|
}) => (
|
||||||
|
<div className="pt-[5px]" data-testid="vega-tx-details" title={title}>
|
||||||
|
<div className="font-mono text-xs p-2 bg-neutral-100 rounded">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
type SizeAtPriceProps = {
|
||||||
|
side: Side;
|
||||||
|
size: string;
|
||||||
|
price: string | undefined;
|
||||||
|
meta: { positionDecimalPlaces: number; decimalPlaces: number; asset: string };
|
||||||
|
};
|
||||||
|
const SizeAtPrice = ({ side, size, price, meta }: SizeAtPriceProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Size
|
||||||
|
side={side}
|
||||||
|
value={size}
|
||||||
|
positionDecimalPlaces={meta.positionDecimalPlaces}
|
||||||
|
forceTheme="light"
|
||||||
|
/>{' '}
|
||||||
|
{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 (
|
||||||
|
<Details>
|
||||||
|
<h4 className="font-bold">
|
||||||
|
{order
|
||||||
|
? t(
|
||||||
|
`Submit order - ${OrderStatusMapping[order.status].toLowerCase()}`
|
||||||
|
)
|
||||||
|
: t('Submit order')}
|
||||||
|
</h4>
|
||||||
|
<p>{market?.tradableInstrument.instrument.code}</p>
|
||||||
|
<p>
|
||||||
|
<SizeAtPrice
|
||||||
|
meta={{
|
||||||
|
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||||
|
decimalPlaces: market.decimalPlaces,
|
||||||
|
asset:
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset
|
||||||
|
.symbol,
|
||||||
|
}}
|
||||||
|
side={side}
|
||||||
|
size={size}
|
||||||
|
price={price}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
{order && order.rejectionReason && (
|
||||||
|
<p className="italic">{getRejectionReason(order)}</p>
|
||||||
|
)}
|
||||||
|
</Details>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = (
|
||||||
|
<SizeAtPrice
|
||||||
|
side={originalOrder.side}
|
||||||
|
size={originalOrder.size}
|
||||||
|
price={originalOrder.price}
|
||||||
|
meta={{
|
||||||
|
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||||
|
decimalPlaces: market.decimalPlaces,
|
||||||
|
asset:
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const edited = (
|
||||||
|
<SizeAtPrice
|
||||||
|
side={originalOrder.side}
|
||||||
|
size={String(Number(originalOrder.size) + (data.sizeDelta || 0))}
|
||||||
|
price={data.price}
|
||||||
|
meta={{
|
||||||
|
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||||
|
decimalPlaces: market.decimalPlaces,
|
||||||
|
asset:
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Details title={data.orderId}>
|
||||||
|
<h4 className="font-bold">
|
||||||
|
{order
|
||||||
|
? t(`Edit order - ${OrderStatusMapping[order.status].toLowerCase()}`)
|
||||||
|
: t('Edit order')}
|
||||||
|
</h4>
|
||||||
|
<p>{market?.tradableInstrument.instrument.code}</p>
|
||||||
|
<p>
|
||||||
|
<s>{original}</s>
|
||||||
|
</p>
|
||||||
|
<p>{edited}</p>
|
||||||
|
{order && order.rejectionReason && (
|
||||||
|
<p className="italic">{getRejectionReason(order)}</p>
|
||||||
|
)}
|
||||||
|
</Details>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = (
|
||||||
|
<SizeAtPrice
|
||||||
|
side={originalOrder.side}
|
||||||
|
size={originalOrder.size}
|
||||||
|
price={originalOrder.price}
|
||||||
|
meta={{
|
||||||
|
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||||
|
decimalPlaces: market.decimalPlaces,
|
||||||
|
asset:
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Details title={orderId}>
|
||||||
|
<h4 className="font-bold">
|
||||||
|
{order
|
||||||
|
? t(
|
||||||
|
`Cancel order - ${OrderStatusMapping[order.status].toLowerCase()}`
|
||||||
|
)
|
||||||
|
: t('Cancel order')}
|
||||||
|
</h4>
|
||||||
|
<p>{market?.tradableInstrument.instrument.code}</p>
|
||||||
|
<p>
|
||||||
|
<s>{original}</s>
|
||||||
|
</p>
|
||||||
|
{order && order.rejectionReason && (
|
||||||
|
<p className="italic">{getRejectionReason(order)}</p>
|
||||||
|
)}
|
||||||
|
</Details>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Details>
|
||||||
|
{t('Withdraw')} {num} {asset.symbol}
|
||||||
|
</Details>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOrderSubmissionTransaction(tx.body)) {
|
||||||
|
return (
|
||||||
|
<SubmitOrderDetails data={tx.body.orderSubmission} order={tx.order} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOrderCancellationTransaction(tx.body)) {
|
||||||
|
// CANCEL ALL (from Portfolio)
|
||||||
|
if (
|
||||||
|
tx.body.orderCancellation.marketId === undefined &&
|
||||||
|
tx.body.orderCancellation.orderId === undefined
|
||||||
|
) {
|
||||||
|
return <Details>{t('Cancel all orders')}</Details>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CANCEL
|
||||||
|
if (
|
||||||
|
tx.body.orderCancellation.orderId &&
|
||||||
|
tx.body.orderCancellation.marketId
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<CancelOrderDetails
|
||||||
|
orderId={String(tx.body.orderCancellation.orderId)}
|
||||||
|
order={tx.order}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
<Details>
|
||||||
|
{marketName
|
||||||
|
? `${t('Cancel all orders for')} ${marketName}`
|
||||||
|
: t('Cancel all orders')}
|
||||||
|
</Details>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOrderAmendmentTransaction(tx.body)) {
|
||||||
|
return (
|
||||||
|
<EditOrderDetails
|
||||||
|
data={tx.body.orderAmendment}
|
||||||
|
order={tx.order}
|
||||||
|
></EditOrderDetails>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Details>
|
||||||
|
{t('Close position for')} {market.tradableInstrument.instrument.code}
|
||||||
|
</Details>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBatchMarketInstructionsTransaction(tx.body)) {
|
||||||
|
return <Details>{t('Batch market instruction')}</Details>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type VegaTxToastContentProps = { tx: VegaStoredTxState };
|
||||||
|
|
||||||
|
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{t('Action required')}</h3>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
'Please go to your Vega wallet application and approve or reject the transaction.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<VegaTransactionDetails tx={tx} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
|
||||||
|
const explorerLink = useLinks(DApp.Explorer);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
|
||||||
|
<p>{t('Please wait for your transaction to be confirmed')}</p>
|
||||||
|
{tx.txHash && (
|
||||||
|
<p className="break-all">
|
||||||
|
<ExternalLink
|
||||||
|
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{t('View in block explorer')}
|
||||||
|
</ExternalLink>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<VegaTransactionDetails tx={tx} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||||
|
const { createEthWithdrawalApproval } = useEthWithdrawApprovalsStore(
|
||||||
|
(state) => ({
|
||||||
|
createEthWithdrawalApproval: state.create,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const explorerLink = useLinks(DApp.Explorer);
|
||||||
|
if (isWithdrawTransaction(tx.body)) {
|
||||||
|
const completeWithdrawalButton = tx.withdrawal && (
|
||||||
|
<div className="mt-[10px]">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
createEthWithdrawalApproval(
|
||||||
|
tx.withdrawal as WithdrawalBusEventFieldsFragment,
|
||||||
|
tx.withdrawalApproval
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Complete withdrawal')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{t('Funds unlocked')}</h3>
|
||||||
|
<p>{t('Your funds have been unlocked for withdrawal')}</p>
|
||||||
|
{tx.txHash && (
|
||||||
|
<p className="break-all">
|
||||||
|
<ExternalLink
|
||||||
|
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{t('View in block explorer')}
|
||||||
|
</ExternalLink>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<VegaTransactionDetails tx={tx} />
|
||||||
|
{completeWithdrawalButton}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{t('Confirmed')}</h3>
|
||||||
|
<p>{t('Your transaction has been confirmed ')}</p>
|
||||||
|
{tx.txHash && (
|
||||||
|
<p className="break-all">
|
||||||
|
<ExternalLink
|
||||||
|
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{t('View in block explorer')}
|
||||||
|
</ExternalLink>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<VegaTransactionDetails tx={tx} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold">{label}</h3>
|
||||||
|
<p>{errorMessage}</p>
|
||||||
|
{walletError && (
|
||||||
|
<Button size="xs" onClick={reconnectVegaWallet}>
|
||||||
|
{t('Connect vega wallet')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<VegaTransactionDetails tx={tx} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = <VegaTxRequestedToastContent tx={tx} />;
|
||||||
|
}
|
||||||
|
if (tx.status === VegaTxStatus.Pending) {
|
||||||
|
content = <VegaTxPendingToastContentProps tx={tx} />;
|
||||||
|
}
|
||||||
|
if (tx.status === VegaTxStatus.Complete) {
|
||||||
|
content = <VegaTxCompleteToastsContent tx={tx} />;
|
||||||
|
}
|
||||||
|
if (tx.status === VegaTxStatus.Error) {
|
||||||
|
content = <VegaTxErrorToastContent tx={tx} />;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
};
|
@ -1,473 +1,32 @@
|
|||||||
import {
|
import { ToastsContainer } from '@vegaprotocol/ui-toolkit';
|
||||||
Button,
|
import { useMemo } from 'react';
|
||||||
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 sortBy from 'lodash/sortBy';
|
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';
|
import { useUpdateNetworkParametersToasts } from '@vegaprotocol/governance';
|
||||||
|
|
||||||
const intentMap = {
|
import { useVegaTransactionToasts } from '../lib/hooks/use-vega-transaction-toasts';
|
||||||
Default: Intent.Primary,
|
import { useEthereumTransactionToasts } from '../lib/hooks/use-ethereum-transaction-toasts';
|
||||||
Requested: Intent.Warning,
|
import { useEthereumWithdrawApprovalsToasts } from '../lib/hooks/use-ethereum-withdraw-approval-toasts';
|
||||||
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<Asset, 'symbol' | 'decimals'>;
|
|
||||||
}) => {
|
|
||||||
const num = formatNumber(toBigNum(amount, asset.decimals), asset.decimals);
|
|
||||||
return (
|
|
||||||
<div className="mt-[5px]">
|
|
||||||
<span className="font-mono text-xs p-1 bg-gray-100 rounded">
|
|
||||||
{label} {num} {asset.symbol}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<TransactionDetails
|
|
||||||
label={t('Withdraw')}
|
|
||||||
amount={transactionDetails.withdrawSubmission.amount}
|
|
||||||
asset={asset}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<TransactionDetails
|
|
||||||
label={t('Withdraw')}
|
|
||||||
amount={tx.args[1]}
|
|
||||||
asset={asset}
|
|
||||||
/>
|
|
||||||
{tx.requiresConfirmation && (
|
|
||||||
<div className="mt-[10px]">
|
|
||||||
<span className="font-mono text-xs">
|
|
||||||
{t('Awaiting confirmations')}{' '}
|
|
||||||
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
|
|
||||||
</span>
|
|
||||||
<ProgressBar
|
|
||||||
value={(tx.confirmations / tx.requiredConfirmations) * 100}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ToastsManager = () => {
|
export const ToastsManager = () => {
|
||||||
const updateNetworkParametersToasts = useUpdateNetworkParametersToasts();
|
const updateNetworkParametersToasts = useUpdateNetworkParametersToasts();
|
||||||
const vegaTransactions = useVegaTransactionStore((state) =>
|
const vegaTransactionToasts = useVegaTransactionToasts();
|
||||||
state.transactions.filter((transaction) => transaction?.dialogOpen)
|
const ethTransactionToasts = useEthereumTransactionToasts();
|
||||||
);
|
const withdrawApprovalToasts = useEthereumWithdrawApprovalsToasts();
|
||||||
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<Toast> = {};
|
|
||||||
const defaultValues = {
|
|
||||||
id: `vega-${tx.id}`,
|
|
||||||
intent: intentMap[tx.status],
|
|
||||||
render: () => {
|
|
||||||
return <VegaTransaction transaction={tx} />;
|
|
||||||
},
|
|
||||||
onClose: () => dismissVegaTransaction(tx.id),
|
|
||||||
};
|
|
||||||
if (tx.status === VegaTxStatus.Requested) {
|
|
||||||
toast = {
|
|
||||||
render: () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Action required')}</h3>
|
|
||||||
<p>
|
|
||||||
{t(
|
|
||||||
'Please go to your Vega wallet application and approve or reject the transaction.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<VegaTransactionDetails tx={tx} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (tx.status === VegaTxStatus.Pending) {
|
|
||||||
toast = {
|
|
||||||
render: () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
|
|
||||||
<p>{t('Please wait for your transaction to be confirmed')}</p>
|
|
||||||
{tx.txHash && (
|
|
||||||
<p className="break-all">
|
|
||||||
<ExternalLink
|
|
||||||
href={explorerLink(
|
|
||||||
EXPLORER_TX.replace(':hash', prepend0x(tx.txHash))
|
|
||||||
)}
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{t('View in block explorer')}
|
|
||||||
</ExternalLink>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<VegaTransactionDetails tx={tx} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loader: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (tx.status === VegaTxStatus.Complete) {
|
|
||||||
toast = {
|
|
||||||
render: () => {
|
|
||||||
if (isWithdrawTransaction(tx.body)) {
|
|
||||||
const completeWithdrawalButton = tx.withdrawal && (
|
|
||||||
<div className="mt-[10px]">
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
onClick={() => {
|
|
||||||
createEthWithdrawalApproval(
|
|
||||||
tx.withdrawal as WithdrawalBusEventFieldsFragment,
|
|
||||||
tx.withdrawalApproval
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Complete withdrawal')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Funds unlocked')}</h3>
|
|
||||||
<p>{t('Your funds have been unlocked for withdrawal')}</p>
|
|
||||||
{tx.txHash && (
|
|
||||||
<p className="break-all">
|
|
||||||
<ExternalLink
|
|
||||||
href={explorerLink(
|
|
||||||
EXPLORER_TX.replace(':hash', prepend0x(tx.txHash))
|
|
||||||
)}
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{t('View in block explorer')}
|
|
||||||
</ExternalLink>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<VegaTransactionDetails tx={tx} />
|
|
||||||
{completeWithdrawalButton}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Confirmed')}</h3>
|
|
||||||
<p>{t('Your transaction has been confirmed ')}</p>
|
|
||||||
{tx.txHash && (
|
|
||||||
<p className="break-all">
|
|
||||||
<ExternalLink
|
|
||||||
href={explorerLink(
|
|
||||||
EXPLORER_TX.replace(':hash', prepend0x(tx.txHash))
|
|
||||||
)}
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{t('View in block explorer')}
|
|
||||||
</ExternalLink>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<VegaTransactionDetails tx={tx} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (tx.status === VegaTxStatus.Error) {
|
|
||||||
toast = {
|
|
||||||
render: () => {
|
|
||||||
const error = `${tx.error?.message} ${
|
|
||||||
tx.error instanceof WalletError
|
|
||||||
? tx.error?.data
|
|
||||||
? `: ${tx.error?.data}`
|
|
||||||
: ''
|
|
||||||
: ''
|
|
||||||
}`;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Error occurred')}</h3>
|
|
||||||
<p>{error}</p>
|
|
||||||
<VegaTransactionDetails tx={tx} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...defaultValues,
|
|
||||||
...toast,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[createEthWithdrawalApproval, dismissVegaTransaction, explorerLink]
|
|
||||||
);
|
|
||||||
|
|
||||||
const fromEthTransaction = useCallback(
|
|
||||||
(tx: EthStoredTxState): Toast => {
|
|
||||||
let toast: Partial<Toast> = {};
|
|
||||||
const defaultValues = {
|
|
||||||
id: `eth-${tx.id}`,
|
|
||||||
intent: intentMap[tx.status],
|
|
||||||
render: () => {
|
|
||||||
return <TransactionContent {...tx} />;
|
|
||||||
},
|
|
||||||
onClose: () => dismissEthTransaction(tx.id),
|
|
||||||
};
|
|
||||||
if (tx.status === EthTxStatus.Requested) {
|
|
||||||
toast = {
|
|
||||||
render: () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Action required')}</h3>
|
|
||||||
<p>
|
|
||||||
{t(
|
|
||||||
'Please go to your wallet application and approve or reject the transaction.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<EthTransactionDetails tx={tx} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (tx.status === EthTxStatus.Pending) {
|
|
||||||
toast = {
|
|
||||||
render: () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
|
|
||||||
<p>{t('Please wait for your transaction to be confirmed')}</p>
|
|
||||||
{tx.txHash && (
|
|
||||||
<p className="break-all">
|
|
||||||
<ExternalLink
|
|
||||||
href={etherscanLink(
|
|
||||||
ETHERSCAN_TX.replace(':hash', tx.txHash)
|
|
||||||
)}
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{t('View on Etherscan')}
|
|
||||||
</ExternalLink>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<EthTransactionDetails tx={tx} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loader: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (tx.status === EthTxStatus.Confirmed) {
|
|
||||||
toast = {
|
|
||||||
render: () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Transaction completed')}</h3>
|
|
||||||
<p>{t('Your transaction has been completed')}</p>
|
|
||||||
{tx.txHash && (
|
|
||||||
<p className="break-all">
|
|
||||||
<ExternalLink
|
|
||||||
href={etherscanLink(
|
|
||||||
ETHERSCAN_TX.replace(':hash', tx.txHash)
|
|
||||||
)}
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{t('View on Etherscan')}
|
|
||||||
</ExternalLink>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<EthTransactionDetails tx={tx} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
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 (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{t('Error occurred')}</h3>
|
|
||||||
<p>{errorMessage}</p>
|
|
||||||
<EthTransactionDetails tx={tx} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
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 (
|
|
||||||
<div>
|
|
||||||
{title.length > 0 && <h3 className="font-bold">{title}</h3>}
|
|
||||||
<VerificationStatus state={tx} />
|
|
||||||
<TransactionDetails
|
|
||||||
label={t('Withdraw')}
|
|
||||||
amount={tx.withdrawal.amount}
|
|
||||||
asset={tx.withdrawal.asset}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onClose: () => dismissWithdrawApproval(tx.id),
|
|
||||||
|
|
||||||
loader: tx.status === ApprovalStatus.Pending,
|
|
||||||
}),
|
|
||||||
[dismissWithdrawApproval]
|
|
||||||
);
|
|
||||||
|
|
||||||
const toasts = useMemo(() => {
|
const toasts = useMemo(() => {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
[
|
[
|
||||||
...compact(vegaTransactions).map(fromVegaTransaction),
|
...vegaTransactionToasts,
|
||||||
...compact(ethTransactions).map(fromEthTransaction),
|
...ethTransactionToasts,
|
||||||
...compact(withdrawApprovals).map(fromWithdrawalApproval),
|
...withdrawApprovalToasts,
|
||||||
...updateNetworkParametersToasts,
|
...updateNetworkParametersToasts,
|
||||||
],
|
],
|
||||||
['createdBy']
|
['createdBy']
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
vegaTransactions,
|
vegaTransactionToasts,
|
||||||
fromVegaTransaction,
|
ethTransactionToasts,
|
||||||
ethTransactions,
|
withdrawApprovalToasts,
|
||||||
fromEthTransaction,
|
|
||||||
withdrawApprovals,
|
|
||||||
fromWithdrawalApproval,
|
|
||||||
updateNetworkParametersToasts,
|
updateNetworkParametersToasts,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { accountsDataProvider } from './accounts-data-provider';
|
|||||||
import type { Account } from './accounts-data-provider';
|
import type { Account } from './accounts-data-provider';
|
||||||
import { getSettlementAccount } from './get-settlement-account';
|
import { getSettlementAccount } from './get-settlement-account';
|
||||||
|
|
||||||
export const useAccountBalance = (assetId: string) => {
|
export const useAccountBalance = (assetId?: string) => {
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const [accountBalance, setAccountBalance] = useState<string>('');
|
const [accountBalance, setAccountBalance] = useState<string>('');
|
||||||
const [accountDecimals, setAccountDecimals] = useState<number | null>(null);
|
const [accountDecimals, setAccountDecimals] = useState<number | null>(null);
|
||||||
@ -14,7 +14,9 @@ export const useAccountBalance = (assetId: string) => {
|
|||||||
}, [pubKey]);
|
}, [pubKey]);
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
({ data }: { data: Account[] | null }) => {
|
({ data }: { data: Account[] | null }) => {
|
||||||
const account = getSettlementAccount({ accounts: data, assetId });
|
const account = assetId
|
||||||
|
? getSettlementAccount({ accounts: data, assetId })
|
||||||
|
: undefined;
|
||||||
if (accountBalance !== account?.balance) {
|
if (accountBalance !== account?.balance) {
|
||||||
setAccountBalance(account?.balance || '');
|
setAccountBalance(account?.balance || '');
|
||||||
}
|
}
|
||||||
|
@ -4,31 +4,25 @@ import { Button } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
transactionStatus: 'default' | 'pending';
|
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
variant: ButtonVariant;
|
variant: ButtonVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DealTicketButton = ({
|
export const DealTicketButton = ({ disabled, variant }: Props) => {
|
||||||
transactionStatus,
|
|
||||||
disabled,
|
|
||||||
variant,
|
|
||||||
}: Props) => {
|
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||||
}));
|
}));
|
||||||
const isPending = transactionStatus === 'pending';
|
|
||||||
return pubKey ? (
|
return pubKey ? (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Button
|
<Button
|
||||||
variant={variant}
|
variant={variant}
|
||||||
fill
|
fill
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={disabled || isPending}
|
disabled={disabled}
|
||||||
data-testid="place-order"
|
data-testid="place-order"
|
||||||
>
|
>
|
||||||
{isPending ? t('Pending...') : t('Place order')}
|
{t('Place order')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -5,8 +5,9 @@ import type {
|
|||||||
MarketDataUpdateFieldsFragment,
|
MarketDataUpdateFieldsFragment,
|
||||||
MarketDealTicket,
|
MarketDealTicket,
|
||||||
} from '@vegaprotocol/market-list';
|
} from '@vegaprotocol/market-list';
|
||||||
|
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||||
import { marketDealTicketProvider } from '@vegaprotocol/market-list';
|
import { marketDealTicketProvider } from '@vegaprotocol/market-list';
|
||||||
import { DealTicketManager } from './deal-ticket-manager';
|
import { DealTicket } from './deal-ticket';
|
||||||
|
|
||||||
export interface DealTicketContainerProps {
|
export interface DealTicketContainerProps {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
@ -27,6 +28,7 @@ export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => {
|
|||||||
variables,
|
variables,
|
||||||
skip: !marketId,
|
skip: !marketId,
|
||||||
});
|
});
|
||||||
|
const create = useVegaTransactionStore((state) => state.create);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer<MarketDealTicket>
|
<AsyncRenderer<MarketDealTicket>
|
||||||
@ -35,7 +37,10 @@ export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => {
|
|||||||
error={error}
|
error={error}
|
||||||
>
|
>
|
||||||
{data ? (
|
{data ? (
|
||||||
<DealTicketManager market={data} />
|
<DealTicket
|
||||||
|
market={data}
|
||||||
|
submit={(orderSubmission) => create({ orderSubmission })}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Splash>
|
<Splash>
|
||||||
<p>{t('Could not load market')}</p>
|
<p>{t('Could not load market')}</p>
|
||||||
|
@ -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 (
|
|
||||||
<ul data-testid="connectors-list" className="mb-6">
|
|
||||||
<li className="mb-2 last:mb-0" data-testid={transaction.status}>
|
|
||||||
{t('The connection to your Vega Wallet has been lost.')}
|
|
||||||
</li>
|
|
||||||
<li className="mb-0 pt-2">
|
|
||||||
<Button onClick={reconnect}>{t('Connect vega wallet')}</Button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<p data-testid={transaction.status}>
|
|
||||||
{error.message}{' '}
|
|
||||||
{error instanceof WalletError ? `: ${error.data}` : null}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [transaction, reconnect]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DealTicketManager = ({
|
|
||||||
market,
|
|
||||||
children,
|
|
||||||
}: DealTicketManagerProps) => {
|
|
||||||
const { submit, transaction, finalizedOrder, Dialog, reset } =
|
|
||||||
useOrderSubmit();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{children || (
|
|
||||||
<DealTicket
|
|
||||||
market={market}
|
|
||||||
submit={(order) => submit(order)}
|
|
||||||
transactionStatus={
|
|
||||||
transaction.status === VegaTxStatus.Requested ||
|
|
||||||
transaction.status === VegaTxStatus.Pending
|
|
||||||
? 'pending'
|
|
||||||
: 'default'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Dialog
|
|
||||||
title={getOrderDialogTitle(finalizedOrder?.status)}
|
|
||||||
intent={getOrderDialogIntent(finalizedOrder?.status)}
|
|
||||||
icon={getOrderDialogIcon(finalizedOrder?.status)}
|
|
||||||
content={{
|
|
||||||
Complete: (
|
|
||||||
<OrderFeedback transaction={transaction} order={finalizedOrder} />
|
|
||||||
),
|
|
||||||
Error: <ErrorContent transaction={transaction} reset={reset} />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 <Icon name="warning-sign" />;
|
|
||||||
case Schema.OrderStatus.STATUS_REJECTED:
|
|
||||||
case Schema.OrderStatus.STATUS_STOPPED:
|
|
||||||
case Schema.OrderStatus.STATUS_CANCELLED:
|
|
||||||
return <Icon name="error" />;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
@ -26,7 +26,6 @@ jest.mock('../../hooks/use-has-no-balance', () => {
|
|||||||
|
|
||||||
const market = generateMarket();
|
const market = generateMarket();
|
||||||
const submit = jest.fn();
|
const submit = jest.fn();
|
||||||
const transactionStatus = 'default';
|
|
||||||
|
|
||||||
const mockChainId = 'chain-id';
|
const mockChainId = 'chain-id';
|
||||||
|
|
||||||
@ -46,12 +45,7 @@ function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
|
|||||||
return (
|
return (
|
||||||
<MockedProvider mocks={[chainIdMock]}>
|
<MockedProvider mocks={[chainIdMock]}>
|
||||||
<VegaWalletContext.Provider value={{ pubKey: mockChainId } as any}>
|
<VegaWalletContext.Provider value={{ pubKey: mockChainId } as any}>
|
||||||
<DealTicket
|
<DealTicket market={market} submit={submit} />
|
||||||
defaultOrder={order}
|
|
||||||
market={market}
|
|
||||||
submit={submit}
|
|
||||||
transactionStatus={transactionStatus}
|
|
||||||
/>
|
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { removeDecimal, t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { memo, useCallback, useEffect } from 'react';
|
import { memo, useCallback, useEffect } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
@ -10,6 +10,7 @@ import { SideSelector } from './side-selector';
|
|||||||
import { TimeInForceSelector } from './time-in-force-selector';
|
import { TimeInForceSelector } from './time-in-force-selector';
|
||||||
import { TypeSelector } from './type-selector';
|
import { TypeSelector } from './type-selector';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
|
import { normalizeOrderSubmission } from '@vegaprotocol/wallet';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { InputError } from '@vegaprotocol/ui-toolkit';
|
import { InputError } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useOrderMarginValidation } from '../../hooks/use-order-margin-validation';
|
import { useOrderMarginValidation } from '../../hooks/use-order-margin-validation';
|
||||||
@ -32,8 +33,6 @@ export type TransactionStatus = 'default' | 'pending';
|
|||||||
export interface DealTicketProps {
|
export interface DealTicketProps {
|
||||||
market: MarketDealTicket;
|
market: MarketDealTicket;
|
||||||
submit: (order: OrderSubmissionBody['orderSubmission']) => void;
|
submit: (order: OrderSubmissionBody['orderSubmission']) => void;
|
||||||
transactionStatus: TransactionStatus;
|
|
||||||
defaultOrder?: OrderSubmissionBody['orderSubmission'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
|
export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
|
||||||
@ -42,11 +41,7 @@ export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
|
|||||||
summary: string;
|
summary: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DealTicket = ({
|
export const DealTicket = ({ market, submit }: DealTicketProps) => {
|
||||||
market,
|
|
||||||
submit,
|
|
||||||
transactionStatus,
|
|
||||||
}: DealTicketProps) => {
|
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const [persistedOrder, setPersistedOrder] = usePersistedOrder(market);
|
const [persistedOrder, setPersistedOrder] = usePersistedOrder(market);
|
||||||
const {
|
const {
|
||||||
@ -123,15 +118,13 @@ export const DealTicket = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
submit({
|
submit(
|
||||||
...order,
|
normalizeOrderSubmission(
|
||||||
price: order.price && removeDecimal(order.price, market.decimalPlaces),
|
order,
|
||||||
size: removeDecimal(order.size, market.positionDecimalPlaces),
|
market.decimalPlaces,
|
||||||
expiresAt:
|
market.positionDecimalPlaces
|
||||||
order.timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
|
)
|
||||||
? order.expiresAt
|
);
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
submit,
|
submit,
|
||||||
@ -209,7 +202,6 @@ export const DealTicket = ({
|
|||||||
)}
|
)}
|
||||||
<DealTicketButton
|
<DealTicketButton
|
||||||
disabled={Object.keys(errors).length >= 1}
|
disabled={Object.keys(errors).length >= 1}
|
||||||
transactionStatus={transactionStatus}
|
|
||||||
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
|
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
|
||||||
/>
|
/>
|
||||||
<SummaryMessage
|
<SummaryMessage
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
export * from './deal-ticket-amount';
|
export * from './deal-ticket-amount';
|
||||||
export * from './deal-ticket-container';
|
export * from './deal-ticket-container';
|
||||||
export * from './deal-ticket-limit-amount';
|
export * from './deal-ticket-limit-amount';
|
||||||
export * from './deal-ticket-manager';
|
|
||||||
export * from './deal-ticket-market-amount';
|
export * from './deal-ticket-market-amount';
|
||||||
export * from './deal-ticket';
|
export * from './deal-ticket';
|
||||||
export * from './expiry-selector';
|
export * from './expiry-selector';
|
||||||
|
@ -5,6 +5,7 @@ import { Dialog } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { DepositContainer } from './deposit-container';
|
import { DepositContainer } from './deposit-container';
|
||||||
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
|
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
|
||||||
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -41,6 +42,9 @@ const DEFAULT_STYLE: DepositDialogStyleProps = {
|
|||||||
|
|
||||||
export const DepositDialog = () => {
|
export const DepositDialog = () => {
|
||||||
const { assetId, isOpen, open, close } = useDepositDialog();
|
const { assetId, isOpen, open, close } = useDepositDialog();
|
||||||
|
const assetDetailsDialogOpen = useAssetDetailsDialogStore(
|
||||||
|
(state) => state.isOpen
|
||||||
|
);
|
||||||
const connectWalletDialogIsOpen = useWeb3ConnectStore(
|
const connectWalletDialogIsOpen = useWeb3ConnectStore(
|
||||||
(state) => state.isOpen
|
(state) => state.isOpen
|
||||||
);
|
);
|
||||||
@ -55,7 +59,7 @@ export const DepositDialog = () => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isOpen && !connectWalletDialogIsOpen}
|
open={isOpen && !(connectWalletDialogIsOpen || assetDetailsDialogOpen)}
|
||||||
onChange={(isOpen) => (isOpen ? open() : close())}
|
onChange={(isOpen) => (isOpen ? open() : close())}
|
||||||
{...dialogStyleProps}
|
{...dialogStyleProps}
|
||||||
>
|
>
|
||||||
|
@ -322,7 +322,6 @@ const FormButton = ({
|
|||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
} else if (chainId !== desiredChainId) {
|
} else if (chainId !== desiredChainId) {
|
||||||
console.log(chainId, desiredChainId);
|
|
||||||
const chainName = getChainName(desiredChainId);
|
const chainName = getChainName(desiredChainId);
|
||||||
message = t(`This app only works on ${chainName}.`);
|
message = t(`This app only works on ${chainName}.`);
|
||||||
button = (
|
button = (
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import { DepositForm } from './deposit-form';
|
import { DepositForm } from './deposit-form';
|
||||||
import { useSubmitDeposit } from './use-submit-deposit';
|
import type { DepositFormProps } from './deposit-form';
|
||||||
|
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import { prepend0x } from '@vegaprotocol/smart-contracts';
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
import { useSubmitApproval } from './use-submit-approval';
|
import { useSubmitApproval } from './use-submit-approval';
|
||||||
import { useSubmitFaucet } from './use-submit-faucet';
|
import { useSubmitFaucet } from './use-submit-faucet';
|
||||||
import { useDepositStore } from './deposit-store';
|
import { useEffect, useState } from 'react';
|
||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import { useDepositBalances } from './use-deposit-balances';
|
import { useDepositBalances } from './use-deposit-balances';
|
||||||
|
import { useDepositDialog } from './deposit-dialog';
|
||||||
import type { Asset } from '@vegaprotocol/assets';
|
import type { Asset } from '@vegaprotocol/assets';
|
||||||
import type { DepositDialogStylePropsSetter } from './deposit-dialog';
|
import type { DepositDialogStylePropsSetter } from './deposit-dialog';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import type { EthTransaction } from '@vegaprotocol/web3';
|
import type { EthTransaction } from '@vegaprotocol/web3';
|
||||||
import { EthTxStatus } from '@vegaprotocol/web3';
|
import {
|
||||||
|
EthTxStatus,
|
||||||
|
useEthTransactionStore,
|
||||||
|
useBridgeContract,
|
||||||
|
useEthereumConfig,
|
||||||
|
} from '@vegaprotocol/web3';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
interface DepositManagerProps {
|
interface DepositManagerProps {
|
||||||
@ -20,53 +27,36 @@ interface DepositManagerProps {
|
|||||||
setDialogStyleProps?: DepositDialogStylePropsSetter;
|
setDialogStyleProps?: DepositDialogStylePropsSetter;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useDepositAsset = (assets: Asset[], assetId?: string) => {
|
|
||||||
const { asset, balance, allowance, deposited, max, update } =
|
|
||||||
useDepositStore();
|
|
||||||
|
|
||||||
const handleSelectAsset = useCallback(
|
|
||||||
(id: string) => {
|
|
||||||
const asset = assets.find((a) => a.id === id);
|
|
||||||
update({ asset });
|
|
||||||
},
|
|
||||||
[assets, update]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
handleSelectAsset(assetId || '');
|
|
||||||
}, [assetId, handleSelectAsset]);
|
|
||||||
|
|
||||||
return { asset, balance, allowance, deposited, max, handleSelectAsset };
|
|
||||||
};
|
|
||||||
|
|
||||||
const getProps = (txContent?: EthTransaction['TxContent']) =>
|
const getProps = (txContent?: EthTransaction['TxContent']) =>
|
||||||
txContent ? pick(txContent, ['title', 'icon', 'intent']) : undefined;
|
txContent ? pick(txContent, ['title', 'icon', 'intent']) : undefined;
|
||||||
|
|
||||||
export const DepositManager = ({
|
export const DepositManager = ({
|
||||||
assetId,
|
assetId: initialAssetId,
|
||||||
assets,
|
assets,
|
||||||
isFaucetable,
|
isFaucetable,
|
||||||
setDialogStyleProps,
|
setDialogStyleProps,
|
||||||
}: DepositManagerProps) => {
|
}: DepositManagerProps) => {
|
||||||
const { asset, balance, allowance, deposited, max, handleSelectAsset } =
|
const createEthTransaction = useEthTransactionStore((state) => state.create);
|
||||||
useDepositAsset(assets, assetId);
|
const { config } = useEthereumConfig();
|
||||||
|
const [assetId, setAssetId] = useState(initialAssetId);
|
||||||
|
const asset = assets.find((a) => a.id === assetId);
|
||||||
|
const bridgeContract = useBridgeContract();
|
||||||
|
const closeDepositDialog = useDepositDialog((state) => state.close);
|
||||||
|
|
||||||
useDepositBalances(isFaucetable);
|
const { balance, allowance, deposited, max, refresh } = useDepositBalances(
|
||||||
|
asset,
|
||||||
|
isFaucetable
|
||||||
|
);
|
||||||
|
|
||||||
// Set up approve transaction
|
// Set up approve transaction
|
||||||
const approve = useSubmitApproval();
|
const approve = useSubmitApproval(asset);
|
||||||
|
|
||||||
// Set up deposit transaction
|
|
||||||
const deposit = useSubmitDeposit();
|
|
||||||
|
|
||||||
// Set up faucet transaction
|
// Set up faucet transaction
|
||||||
const faucet = useSubmitFaucet();
|
const faucet = useSubmitFaucet(asset);
|
||||||
|
|
||||||
const transactionInProgress = [
|
const transactionInProgress = [approve.TxContent, faucet.TxContent].filter(
|
||||||
approve.TxContent,
|
(t) => t.status !== EthTxStatus.Default
|
||||||
deposit.TxContent,
|
)[0];
|
||||||
faucet.TxContent,
|
|
||||||
].filter((t) => t.status !== EthTxStatus.Default)[0];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDialogStyleProps?.(getProps(transactionInProgress));
|
setDialogStyleProps?.(getProps(transactionInProgress));
|
||||||
@ -74,17 +64,44 @@ export const DepositManager = ({
|
|||||||
|
|
||||||
const returnLabel = t('Return to deposit');
|
const returnLabel = t('Return to deposit');
|
||||||
|
|
||||||
|
const submitDeposit = (
|
||||||
|
args: Parameters<DepositFormProps['submitDeposit']>['0']
|
||||||
|
) => {
|
||||||
|
if (!asset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
createEthTransaction(
|
||||||
|
bridgeContract,
|
||||||
|
'deposit_asset',
|
||||||
|
[
|
||||||
|
args.assetSource,
|
||||||
|
removeDecimal(args.amount, asset.decimals),
|
||||||
|
prepend0x(args.vegaPublicKey),
|
||||||
|
],
|
||||||
|
asset.id,
|
||||||
|
config?.confirmations ?? 1,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
closeDepositDialog();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!transactionInProgress && (
|
{!transactionInProgress && (
|
||||||
<DepositForm
|
<DepositForm
|
||||||
balance={balance}
|
balance={balance}
|
||||||
selectedAsset={asset}
|
selectedAsset={asset}
|
||||||
onSelectAsset={handleSelectAsset}
|
onSelectAsset={setAssetId}
|
||||||
assets={sortBy(assets, 'name')}
|
assets={sortBy(assets, 'name')}
|
||||||
submitApprove={() => approve.perform()}
|
submitApprove={async () => {
|
||||||
submitDeposit={(args) => deposit.perform(args)}
|
await approve.perform();
|
||||||
requestFaucet={() => faucet.perform()}
|
refresh();
|
||||||
|
}}
|
||||||
|
submitDeposit={submitDeposit}
|
||||||
|
requestFaucet={async () => {
|
||||||
|
await faucet.perform();
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
deposited={deposited}
|
deposited={deposited}
|
||||||
max={max}
|
max={max}
|
||||||
allowance={allowance}
|
allowance={allowance}
|
||||||
@ -94,7 +111,6 @@ export const DepositManager = ({
|
|||||||
|
|
||||||
<approve.TxContent.Content returnLabel={returnLabel} />
|
<approve.TxContent.Content returnLabel={returnLabel} />
|
||||||
<faucet.TxContent.Content returnLabel={returnLabel} />
|
<faucet.TxContent.Content returnLabel={returnLabel} />
|
||||||
<deposit.TxContent.Content returnLabel={returnLabel} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import type { Asset } from '@vegaprotocol/assets';
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import create from 'zustand';
|
|
||||||
|
|
||||||
interface DepositStore {
|
|
||||||
balance: BigNumber;
|
|
||||||
allowance: BigNumber;
|
|
||||||
asset: Asset | undefined;
|
|
||||||
deposited: BigNumber;
|
|
||||||
max: BigNumber;
|
|
||||||
update: (state: Partial<DepositStore>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDepositStore = create<DepositStore>((set) => ({
|
|
||||||
balance: new BigNumber(0),
|
|
||||||
allowance: new BigNumber(0),
|
|
||||||
deposited: new BigNumber(0),
|
|
||||||
max: new BigNumber(0),
|
|
||||||
asset: undefined,
|
|
||||||
update: (updatedState) => {
|
|
||||||
set(updatedState);
|
|
||||||
},
|
|
||||||
}));
|
|
@ -3,7 +3,6 @@ export * from './deposit-container';
|
|||||||
export * from './deposit-form';
|
export * from './deposit-form';
|
||||||
export * from './deposit-limits';
|
export * from './deposit-limits';
|
||||||
export * from './deposit-manager';
|
export * from './deposit-manager';
|
||||||
export * from './deposit-store';
|
|
||||||
export * from './deposits-table';
|
export * from './deposits-table';
|
||||||
export * from './use-deposit-balances';
|
export * from './use-deposit-balances';
|
||||||
export * from './use-deposits';
|
export * from './use-deposits';
|
||||||
@ -12,6 +11,5 @@ export * from './use-get-balance-of-erc20-token';
|
|||||||
export * from './use-get-deposit-maximum';
|
export * from './use-get-deposit-maximum';
|
||||||
export * from './use-get-deposited-amount';
|
export * from './use-get-deposited-amount';
|
||||||
export * from './use-submit-approval';
|
export * from './use-submit-approval';
|
||||||
export * from './use-submit-deposit';
|
|
||||||
export * from './use-submit-faucet';
|
export * from './use-submit-faucet';
|
||||||
export * from './deposit-dialog';
|
export * from './deposit-dialog';
|
||||||
|
@ -1,19 +1,40 @@
|
|||||||
import { useBridgeContract, useTokenContract } from '@vegaprotocol/web3';
|
import { useBridgeContract, useTokenContract } from '@vegaprotocol/web3';
|
||||||
import { useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { useDepositStore } from './deposit-store';
|
|
||||||
import { useGetAllowance } from './use-get-allowance';
|
import { useGetAllowance } from './use-get-allowance';
|
||||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||||
import { useGetDepositMaximum } from './use-get-deposit-maximum';
|
import { useGetDepositMaximum } from './use-get-deposit-maximum';
|
||||||
import { useGetDepositedAmount } from './use-get-deposited-amount';
|
import { useGetDepositedAmount } from './use-get-deposited-amount';
|
||||||
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
import { isAssetTypeERC20, usePrevious } from '@vegaprotocol/react-helpers';
|
||||||
|
import { useAccountBalance } from '@vegaprotocol/accounts';
|
||||||
|
import type { Asset } from '@vegaprotocol/assets';
|
||||||
|
|
||||||
|
type DepositBalances = {
|
||||||
|
balance: BigNumber;
|
||||||
|
allowance: BigNumber;
|
||||||
|
deposited: BigNumber;
|
||||||
|
max: BigNumber;
|
||||||
|
refresh: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DepositBalancesState = Omit<DepositBalances, 'refresh'>;
|
||||||
|
|
||||||
|
const initialState: DepositBalancesState = {
|
||||||
|
balance: new BigNumber(0),
|
||||||
|
allowance: new BigNumber(0),
|
||||||
|
deposited: new BigNumber(0),
|
||||||
|
max: new BigNumber(0),
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook which fetches all the balances required for depositing
|
* Hook which fetches all the balances required for depositing
|
||||||
* whenever the asset changes in the form
|
* whenever the asset changes in the form
|
||||||
*/
|
*/
|
||||||
export const useDepositBalances = (isFaucetable: boolean) => {
|
export const useDepositBalances = (
|
||||||
const { asset, update } = useDepositStore();
|
asset: Asset | undefined,
|
||||||
|
isFaucetable: boolean
|
||||||
|
): DepositBalances => {
|
||||||
const tokenContract = useTokenContract(
|
const tokenContract = useTokenContract(
|
||||||
isAssetTypeERC20(asset) ? asset.source.contractAddress : undefined,
|
isAssetTypeERC20(asset) ? asset.source.contractAddress : undefined,
|
||||||
isFaucetable
|
isFaucetable
|
||||||
@ -23,9 +44,20 @@ export const useDepositBalances = (isFaucetable: boolean) => {
|
|||||||
const getBalance = useGetBalanceOfERC20Token(tokenContract, asset);
|
const getBalance = useGetBalanceOfERC20Token(tokenContract, asset);
|
||||||
const getDepositMaximum = useGetDepositMaximum(bridgeContract, asset);
|
const getDepositMaximum = useGetDepositMaximum(bridgeContract, asset);
|
||||||
const getDepositedAmount = useGetDepositedAmount(asset);
|
const getDepositedAmount = useGetDepositedAmount(asset);
|
||||||
|
const prevAsset = usePrevious(asset);
|
||||||
|
const [state, setState] = useState<DepositBalancesState>(initialState);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getBalances = async () => {
|
if (asset?.id !== prevAsset?.id) {
|
||||||
|
// reset values to initial state when asset changes
|
||||||
|
setState(initialState);
|
||||||
|
}
|
||||||
|
}, [asset?.id, prevAsset?.id]);
|
||||||
|
|
||||||
|
const { accountBalance } = useAccountBalance(asset?.id);
|
||||||
|
|
||||||
|
const getBalances = useCallback(async () => {
|
||||||
|
if (!asset) return;
|
||||||
try {
|
try {
|
||||||
const [max, deposited, balance, allowance] = await Promise.all([
|
const [max, deposited, balance, allowance] = await Promise.all([
|
||||||
getDepositMaximum(),
|
getDepositMaximum(),
|
||||||
@ -34,26 +66,20 @@ export const useDepositBalances = (isFaucetable: boolean) => {
|
|||||||
getAllowance(),
|
getAllowance(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
update({
|
setState({
|
||||||
max,
|
max: max ?? initialState.max,
|
||||||
deposited,
|
deposited: deposited ?? initialState.deposited,
|
||||||
balance,
|
balance: balance ?? initialState.balance,
|
||||||
allowance,
|
allowance: allowance ?? initialState.allowance,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
}
|
}
|
||||||
};
|
}, [asset, getAllowance, getBalance, getDepositMaximum, getDepositedAmount]);
|
||||||
|
|
||||||
if (asset) {
|
useEffect(() => {
|
||||||
getBalances();
|
getBalances();
|
||||||
}
|
}, [asset, getBalances, accountBalance]);
|
||||||
}, [
|
|
||||||
asset,
|
return { ...state, refresh: getBalances };
|
||||||
update,
|
|
||||||
getDepositMaximum,
|
|
||||||
getDepositedAmount,
|
|
||||||
getAllowance,
|
|
||||||
getBalance,
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
@ -6,17 +6,14 @@ import {
|
|||||||
useEthereumTransaction,
|
useEthereumTransaction,
|
||||||
useTokenContract,
|
useTokenContract,
|
||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
import { useDepositStore } from './deposit-store';
|
import type { Asset } from '@vegaprotocol/assets';
|
||||||
import { useGetAllowance } from './use-get-allowance';
|
|
||||||
|
|
||||||
export const useSubmitApproval = () => {
|
export const useSubmitApproval = (asset?: Asset) => {
|
||||||
const { config } = useEthereumConfig();
|
const { config } = useEthereumConfig();
|
||||||
const { asset, update } = useDepositStore();
|
|
||||||
const contract = useTokenContract(
|
const contract = useTokenContract(
|
||||||
isAssetTypeERC20(asset) ? asset.source.contractAddress : undefined,
|
isAssetTypeERC20(asset) ? asset.source.contractAddress : undefined,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const getAllowance = useGetAllowance(contract, asset);
|
|
||||||
const transaction = useEthereumTransaction<Token, 'approve'>(
|
const transaction = useEthereumTransaction<Token, 'approve'>(
|
||||||
contract,
|
contract,
|
||||||
'approve'
|
'approve'
|
||||||
@ -31,8 +28,6 @@ export const useSubmitApproval = () => {
|
|||||||
config.collateral_bridge_contract.address,
|
config.collateral_bridge_contract.address,
|
||||||
amount
|
amount
|
||||||
);
|
);
|
||||||
const allowance = await getAllowance();
|
|
||||||
update({ allowance });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
}
|
}
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
import { useSubscription } from '@apollo/client';
|
|
||||||
import * as Sentry from '@sentry/react';
|
|
||||||
import type {
|
|
||||||
DepositEventSubscription,
|
|
||||||
DepositEventSubscriptionVariables,
|
|
||||||
} from './__generated__/Deposit';
|
|
||||||
import { DepositEventDocument } from './__generated__/Deposit';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import {
|
|
||||||
isAssetTypeERC20,
|
|
||||||
remove0x,
|
|
||||||
removeDecimal,
|
|
||||||
} from '@vegaprotocol/react-helpers';
|
|
||||||
import {
|
|
||||||
useBridgeContract,
|
|
||||||
useEthereumConfig,
|
|
||||||
useEthereumTransaction,
|
|
||||||
useTokenContract,
|
|
||||||
} from '@vegaprotocol/web3';
|
|
||||||
import type { CollateralBridge } from '@vegaprotocol/smart-contracts';
|
|
||||||
import { prepend0x } from '@vegaprotocol/smart-contracts';
|
|
||||||
import { useDepositStore } from './deposit-store';
|
|
||||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
|
||||||
|
|
||||||
export const useSubmitDeposit = () => {
|
|
||||||
const { asset, update } = useDepositStore();
|
|
||||||
const { config } = useEthereumConfig();
|
|
||||||
const bridgeContract = useBridgeContract();
|
|
||||||
const tokenContract = useTokenContract(
|
|
||||||
isAssetTypeERC20(asset) ? asset.source.contractAddress : undefined,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
// Store public key from contract arguments for use in the subscription,
|
|
||||||
// NOTE: it may be different from the users connected key
|
|
||||||
const [partyId, setPartyId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const getBalance = useGetBalanceOfERC20Token(tokenContract, asset);
|
|
||||||
|
|
||||||
const transaction = useEthereumTransaction<CollateralBridge, 'deposit_asset'>(
|
|
||||||
bridgeContract,
|
|
||||||
'deposit_asset',
|
|
||||||
config?.confirmations,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
useSubscription<DepositEventSubscription, DepositEventSubscriptionVariables>(
|
|
||||||
DepositEventDocument,
|
|
||||||
{
|
|
||||||
variables: { partyId: partyId ? remove0x(partyId) : '' },
|
|
||||||
skip: !partyId,
|
|
||||||
onSubscriptionData: ({ subscriptionData }) => {
|
|
||||||
if (!subscriptionData.data?.busEvents?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchingDeposit = subscriptionData.data.busEvents.find((e) => {
|
|
||||||
if (e.event.__typename !== 'Deposit') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
e.event.txHash === transaction.transaction.txHash &&
|
|
||||||
// Note there is a bug in data node where the subscription is not emitted when the status
|
|
||||||
// changes from 'Open' to 'Finalized' as a result the deposit UI will hang in a pending state right now
|
|
||||||
// https://github.com/vegaprotocol/data-node/issues/460
|
|
||||||
e.event.status === Schema.DepositStatus.STATUS_FINALIZED
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (matchingDeposit && matchingDeposit.event.__typename === 'Deposit') {
|
|
||||||
transaction.setConfirmed();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...transaction,
|
|
||||||
perform: async (args: {
|
|
||||||
assetSource: string;
|
|
||||||
amount: string;
|
|
||||||
vegaPublicKey: string;
|
|
||||||
}) => {
|
|
||||||
if (!asset) return;
|
|
||||||
try {
|
|
||||||
setPartyId(args.vegaPublicKey);
|
|
||||||
const publicKey = prepend0x(args.vegaPublicKey);
|
|
||||||
const amount = removeDecimal(args.amount, asset.decimals);
|
|
||||||
await transaction.perform(args.assetSource, amount, publicKey);
|
|
||||||
const balance = await getBalance();
|
|
||||||
update({ balance });
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.captureException(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,17 +1,14 @@
|
|||||||
import type { TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
import type { TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { useEthereumTransaction, useTokenContract } from '@vegaprotocol/web3';
|
import { useEthereumTransaction, useTokenContract } from '@vegaprotocol/web3';
|
||||||
import { useDepositStore } from './deposit-store';
|
|
||||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
|
||||||
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
||||||
|
import type { Asset } from '@vegaprotocol/assets';
|
||||||
|
|
||||||
export const useSubmitFaucet = () => {
|
export const useSubmitFaucet = (asset?: Asset) => {
|
||||||
const { asset, update } = useDepositStore();
|
|
||||||
const contract = useTokenContract(
|
const contract = useTokenContract(
|
||||||
isAssetTypeERC20(asset) ? asset.source.contractAddress : undefined,
|
isAssetTypeERC20(asset) ? asset.source.contractAddress : undefined,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const getBalance = useGetBalanceOfERC20Token(contract, asset);
|
|
||||||
const transaction = useEthereumTransaction<TokenFaucetable, 'faucet'>(
|
const transaction = useEthereumTransaction<TokenFaucetable, 'faucet'>(
|
||||||
contract,
|
contract,
|
||||||
'faucet'
|
'faucet'
|
||||||
@ -21,8 +18,6 @@ export const useSubmitFaucet = () => {
|
|||||||
perform: async () => {
|
perform: async () => {
|
||||||
try {
|
try {
|
||||||
await transaction.perform();
|
await transaction.perform();
|
||||||
const balance = await getBalance();
|
|
||||||
update({ balance });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
}
|
}
|
||||||
|
@ -65,9 +65,7 @@ export const useUpdateNetworkParametersToasts = (): Toast[] => {
|
|||||||
return {
|
return {
|
||||||
id: `update-network-param-proposal-${proposal.id}`,
|
id: `update-network-param-proposal-${proposal.id}`,
|
||||||
intent: Intent.Warning,
|
intent: Intent.Warning,
|
||||||
render: () => (
|
content: <UpdateNetworkParameterToastContent proposal={proposal} />,
|
||||||
<UpdateNetworkParameterToastContent proposal={proposal} />
|
|
||||||
),
|
|
||||||
onClose: () => remove(id),
|
onClose: () => remove(id),
|
||||||
closeAfter: CLOSE_AFTER,
|
closeAfter: CLOSE_AFTER,
|
||||||
};
|
};
|
||||||
|
@ -22,6 +22,12 @@ fragment OrderFields on Order {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query OrderById($orderId: ID!) {
|
||||||
|
orderByID(id: $orderId) {
|
||||||
|
...OrderFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query Orders(
|
query Orders(
|
||||||
$partyId: ID!
|
$partyId: ID!
|
||||||
$pagination: Pagination
|
$pagination: Pagination
|
||||||
|
@ -5,6 +5,13 @@ import * as Apollo from '@apollo/client';
|
|||||||
const defaultOptions = {} as const;
|
const defaultOptions = {} as const;
|
||||||
export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null };
|
export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null };
|
||||||
|
|
||||||
|
export type OrderByIdQueryVariables = Types.Exact<{
|
||||||
|
orderId: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type OrderByIdQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } };
|
||||||
|
|
||||||
export type OrdersQueryVariables = Types.Exact<{
|
export type OrdersQueryVariables = Types.Exact<{
|
||||||
partyId: Types.Scalars['ID'];
|
partyId: Types.Scalars['ID'];
|
||||||
pagination?: Types.InputMaybe<Types.Pagination>;
|
pagination?: Types.InputMaybe<Types.Pagination>;
|
||||||
@ -72,6 +79,41 @@ export const OrderUpdateFieldsFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const OrderByIdDocument = gql`
|
||||||
|
query OrderById($orderId: ID!) {
|
||||||
|
orderByID(id: $orderId) {
|
||||||
|
...OrderFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${OrderFieldsFragmentDoc}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useOrderByIdQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useOrderByIdQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useOrderByIdQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useOrderByIdQuery({
|
||||||
|
* variables: {
|
||||||
|
* orderId: // value for 'orderId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useOrderByIdQuery(baseOptions: Apollo.QueryHookOptions<OrderByIdQuery, OrderByIdQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<OrderByIdQuery, OrderByIdQueryVariables>(OrderByIdDocument, options);
|
||||||
|
}
|
||||||
|
export function useOrderByIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<OrderByIdQuery, OrderByIdQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<OrderByIdQuery, OrderByIdQueryVariables>(OrderByIdDocument, options);
|
||||||
|
}
|
||||||
|
export type OrderByIdQueryHookResult = ReturnType<typeof useOrderByIdQuery>;
|
||||||
|
export type OrderByIdLazyQueryHookResult = ReturnType<typeof useOrderByIdLazyQuery>;
|
||||||
|
export type OrderByIdQueryResult = Apollo.QueryResult<OrderByIdQuery, OrderByIdQueryVariables>;
|
||||||
export const OrdersDocument = gql`
|
export const OrdersDocument = gql`
|
||||||
query Orders($partyId: ID!, $pagination: Pagination, $dateRange: DateRange, $filter: OrderFilter, $marketId: ID) {
|
query Orders($partyId: ID!, $pagination: Pagination, $dateRange: DateRange, $filter: OrderFilter, $marketId: ID) {
|
||||||
party(id: $partyId) {
|
party(id: $partyId) {
|
||||||
|
@ -76,7 +76,9 @@ export const OrderFeedback = ({ transaction, order }: OrderFeedbackProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRejectionReason = (order: OrderEventFieldsFragment): string | null => {
|
export const getRejectionReason = (
|
||||||
|
order: OrderEventFieldsFragment
|
||||||
|
): string | null => {
|
||||||
switch (order.status) {
|
switch (order.status) {
|
||||||
case Schema.OrderStatus.STATUS_STOPPED:
|
case Schema.OrderStatus.STATUS_STOPPED:
|
||||||
return t(
|
return t(
|
||||||
|
@ -17,11 +17,11 @@ import type { Filter, Sort } from './use-order-list-data';
|
|||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { TransactionResult } from '@vegaprotocol/wallet';
|
import {
|
||||||
import type { VegaTxState } from '@vegaprotocol/wallet';
|
normalizeOrderAmendment,
|
||||||
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
|
useVegaTransactionStore,
|
||||||
import { useOrderEdit } from '../../order-hooks/use-order-edit';
|
} from '@vegaprotocol/wallet';
|
||||||
import { OrderFeedback } from '../order-feedback';
|
import type { VegaTxState, TransactionResult } from '@vegaprotocol/wallet';
|
||||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||||
import type { OrderEventFieldsFragment } from '../../order-hooks';
|
import type { OrderEventFieldsFragment } from '../../order-hooks';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
@ -76,8 +76,7 @@ export const OrderListManager = ({
|
|||||||
const [sort, setSort] = useState<Sort[] | undefined>();
|
const [sort, setSort] = useState<Sort[] | undefined>();
|
||||||
const [filter, setFilter] = useState<Filter | undefined>();
|
const [filter, setFilter] = useState<Filter | undefined>();
|
||||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||||
const orderCancel = useOrderCancel();
|
const create = useVegaTransactionStore((state) => state.create);
|
||||||
const orderEdit = useOrderEdit(editOrder);
|
|
||||||
const hasActiveOrder = useHasActiveOrder(marketId);
|
const hasActiveOrder = useHasActiveOrder(marketId);
|
||||||
|
|
||||||
const { data, error, loading, addNewRows, getRows } = useOrderListData({
|
const { data, error, loading, addNewRows, getRows } = useOrderListData({
|
||||||
@ -136,9 +135,11 @@ export const OrderListManager = ({
|
|||||||
onSortChanged={onSortChange}
|
onSortChanged={onSortChange}
|
||||||
cancel={(order: Order) => {
|
cancel={(order: Order) => {
|
||||||
if (!order.market) return;
|
if (!order.market) return;
|
||||||
orderCancel.cancel({
|
create({
|
||||||
|
orderCancellation: {
|
||||||
orderId: order.id,
|
orderId: order.id,
|
||||||
marketId: order.market.id,
|
marketId: order.market.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
setEditOrder={setEditOrder}
|
setEditOrder={setEditOrder}
|
||||||
@ -157,7 +158,13 @@ export const OrderListManager = ({
|
|||||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => orderCancel.cancel({ marketId })}
|
onClick={() => {
|
||||||
|
create({
|
||||||
|
orderCancellation: {
|
||||||
|
marketId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
data-testid="cancelAll"
|
data-testid="cancelAll"
|
||||||
>
|
>
|
||||||
{t('Cancel all')}
|
{t('Cancel all')}
|
||||||
@ -165,31 +172,7 @@ export const OrderListManager = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<orderCancel.Dialog
|
|
||||||
title={getCancelDialogTitle(orderCancel)}
|
|
||||||
intent={getCancelDialogIntent(orderCancel)}
|
|
||||||
content={{
|
|
||||||
Complete: orderCancel.cancelledOrder ? (
|
|
||||||
<OrderFeedback
|
|
||||||
transaction={orderCancel.transaction}
|
|
||||||
order={orderCancel.cancelledOrder}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<TransactionComplete {...orderCancel} />
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<orderEdit.Dialog
|
|
||||||
title={getEditDialogTitle(orderEdit.updatedOrder?.status)}
|
|
||||||
content={{
|
|
||||||
Complete: (
|
|
||||||
<OrderFeedback
|
|
||||||
transaction={orderEdit.transaction}
|
|
||||||
order={orderEdit.updatedOrder}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{editOrder && (
|
{editOrder && (
|
||||||
<OrderEditDialog
|
<OrderEditDialog
|
||||||
isOpen={Boolean(editOrder)}
|
isOpen={Boolean(editOrder)}
|
||||||
@ -198,8 +181,17 @@ export const OrderListManager = ({
|
|||||||
}}
|
}}
|
||||||
order={editOrder}
|
order={editOrder}
|
||||||
onSubmit={(fields) => {
|
onSubmit={(fields) => {
|
||||||
|
if (!editOrder.market) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const orderAmendment = normalizeOrderAmendment(
|
||||||
|
editOrder,
|
||||||
|
editOrder.market,
|
||||||
|
fields.limitPrice,
|
||||||
|
fields.size
|
||||||
|
);
|
||||||
|
create({ orderAmendment });
|
||||||
setEditOrder(null);
|
setEditOrder(null);
|
||||||
orderEdit.edit({ price: fields.limitPrice, size: fields.size });
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -9,7 +9,6 @@ import type { OrderEventSubscription } from './';
|
|||||||
import { OrderEventDocument } from './';
|
import { OrderEventDocument } from './';
|
||||||
import type { MockedResponse } from '@apollo/client/testing';
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import { toNanoSeconds } from '@vegaprotocol/react-helpers';
|
|
||||||
|
|
||||||
const defaultMarket = {
|
const defaultMarket = {
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
@ -180,7 +179,7 @@ describe('useOrderSubmit', () => {
|
|||||||
side: Schema.Side.SIDE_BUY,
|
side: Schema.Side.SIDE_BUY,
|
||||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||||
price: '123456789',
|
price: '123456789',
|
||||||
expiresAt: toNanoSeconds(order.expiresAt),
|
expiresAt: new Date('2022-01-01').toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -217,7 +216,7 @@ describe('useOrderSubmit', () => {
|
|||||||
side: Schema.Side.SIDE_BUY,
|
side: Schema.Side.SIDE_BUY,
|
||||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||||
price: '123456789',
|
price: '123456789',
|
||||||
expiresAt: undefined,
|
expiresAt: new Date('2022-01-01').toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { OrderEventFieldsFragment } from './__generated__/OrderEvent';
|
import type { OrderEventFieldsFragment } from './__generated__/OrderEvent';
|
||||||
import { toNanoSeconds } from '@vegaprotocol/react-helpers';
|
|
||||||
import {
|
import {
|
||||||
useVegaWallet,
|
useVegaWallet,
|
||||||
useVegaTransaction,
|
useVegaTransaction,
|
||||||
@ -108,28 +107,15 @@ export const useOrderSubmit = () => {
|
|||||||
}, [resetTransaction]);
|
}, [resetTransaction]);
|
||||||
|
|
||||||
const submit = useCallback(
|
const submit = useCallback(
|
||||||
async (order: OrderSubmissionBody['orderSubmission']) => {
|
async (orderSubmission: OrderSubmissionBody['orderSubmission']) => {
|
||||||
if (!pubKey || !order.side) {
|
if (!pubKey || !orderSubmission.side) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFinalizedOrder(null);
|
setFinalizedOrder(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await send(pubKey, {
|
const res = await send(pubKey, { orderSubmission });
|
||||||
orderSubmission: {
|
|
||||||
...order,
|
|
||||||
price:
|
|
||||||
order.type === Schema.OrderType.TYPE_LIMIT && order.price
|
|
||||||
? order.price
|
|
||||||
: undefined,
|
|
||||||
expiresAt:
|
|
||||||
order.expiresAt &&
|
|
||||||
order.timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
|
|
||||||
? toNanoSeconds(order.expiresAt) // Wallet expects timestamp in nanoseconds
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
const orderId = determineId(res.signature);
|
const orderId = determineId(res.signature);
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { AsyncRenderer, Icon, Intent } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useClosePosition, usePositionsData, PositionsTable } from '../';
|
import { usePositionsData, PositionsTable } from '../';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { Requested } from './close-position-dialog/requested';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { Complete } from './close-position-dialog/complete';
|
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||||
import type { TransactionResult } from '@vegaprotocol/wallet';
|
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
interface PositionsManagerProps {
|
interface PositionsManagerProps {
|
||||||
@ -14,14 +13,7 @@ interface PositionsManagerProps {
|
|||||||
export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const { data, error, loading, getRows } = usePositionsData(partyId, gridRef);
|
const { data, error, loading, getRows } = usePositionsData(partyId, gridRef);
|
||||||
const {
|
const create = useVegaTransactionStore((store) => store.create);
|
||||||
submit,
|
|
||||||
closingOrder,
|
|
||||||
closingOrderResult,
|
|
||||||
transaction,
|
|
||||||
transactionResult,
|
|
||||||
Dialog,
|
|
||||||
} = useClosePosition();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
@ -29,7 +21,30 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
|||||||
rowModelType="infinite"
|
rowModelType="infinite"
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
datasource={{ getRows }}
|
datasource={{ getRows }}
|
||||||
onClose={(position) => submit(position)}
|
onClose={({ marketId, openVolume }) =>
|
||||||
|
create({
|
||||||
|
batchMarketInstructions: {
|
||||||
|
cancellations: [
|
||||||
|
{
|
||||||
|
marketId,
|
||||||
|
orderId: '', // omit order id to cancel all active orders
|
||||||
|
},
|
||||||
|
],
|
||||||
|
submissions: [
|
||||||
|
{
|
||||||
|
marketId: marketId,
|
||||||
|
type: Schema.OrderType.TYPE_MARKET as const,
|
||||||
|
timeInForce: Schema.OrderTimeInForce
|
||||||
|
.TIME_IN_FORCE_FOK as const,
|
||||||
|
side: openVolume.startsWith('-')
|
||||||
|
? Schema.Side.SIDE_BUY
|
||||||
|
: Schema.Side.SIDE_SELL,
|
||||||
|
size: openVolume.replace('-', ''),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
noRowsOverlayComponent={() => null}
|
noRowsOverlayComponent={() => null}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
@ -41,63 +56,6 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
|||||||
noDataCondition={(data) => !(data && data.length)}
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Dialog
|
|
||||||
intent={getDialogIntent(transactionResult)}
|
|
||||||
icon={getDialogIcon(transactionResult)}
|
|
||||||
title={getDialogTitle(transactionResult)}
|
|
||||||
content={{
|
|
||||||
Requested: <Requested partyId={partyId} order={closingOrder} />,
|
|
||||||
Complete: (
|
|
||||||
<Complete
|
|
||||||
partyId={partyId}
|
|
||||||
closingOrder={closingOrder}
|
|
||||||
closingOrderResult={closingOrderResult}
|
|
||||||
transaction={transaction}
|
|
||||||
transactionResult={transactionResult}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDialogIntent = (transactionResult?: TransactionResult) => {
|
|
||||||
if (!transactionResult) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
transactionResult &&
|
|
||||||
'error' in transactionResult &&
|
|
||||||
transactionResult.error
|
|
||||||
) {
|
|
||||||
return Intent.Danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Intent.Success;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDialogIcon = (transactionResult?: TransactionResult) => {
|
|
||||||
if (!transactionResult) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transactionResult.status) {
|
|
||||||
return <Icon name="tick" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Icon name="error" />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDialogTitle = (transactionResult?: TransactionResult) => {
|
|
||||||
if (!transactionResult) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transactionResult.status) {
|
|
||||||
return t('Position closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return t('Position not closed');
|
|
||||||
};
|
|
||||||
|
@ -11,3 +11,4 @@ export * from './use-screen-dimensions';
|
|||||||
export * from './use-theme-switcher';
|
export * from './use-theme-switcher';
|
||||||
export * from './use-storybook-theme-observer';
|
export * from './use-storybook-theme-observer';
|
||||||
export * from './use-yesterday';
|
export * from './use-yesterday';
|
||||||
|
export * from './use-previous';
|
||||||
|
22
libs/react-helpers/src/hooks/use-previous.spec.ts
Normal file
22
libs/react-helpers/src/hooks/use-previous.spec.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { usePrevious } from './use-previous';
|
||||||
|
|
||||||
|
describe('usePrevious', () => {
|
||||||
|
it('returns previous value', () => {
|
||||||
|
const { result, rerender } = renderHook(
|
||||||
|
({ value }: { value: string }) => usePrevious(value),
|
||||||
|
{
|
||||||
|
initialProps: {
|
||||||
|
value: 'ABC',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result.current).toEqual('ABC');
|
||||||
|
rerender({ value: 'DEF' });
|
||||||
|
expect(result.current).toEqual('ABC');
|
||||||
|
rerender({ value: 'GHI' });
|
||||||
|
expect(result.current).toEqual('DEF');
|
||||||
|
rerender({ value: 'JKL' });
|
||||||
|
expect(result.current).toEqual('GHI');
|
||||||
|
});
|
||||||
|
});
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
export function usePrevious<T>(value: T): T | undefined {
|
export function usePrevious<T>(value: T): T | undefined {
|
||||||
const ref = React.useRef<T | undefined>(value);
|
const ref = useRef<T | undefined>(value);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
ref.current = value;
|
ref.current = value;
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
@ -7,17 +7,31 @@ export const Size = ({
|
|||||||
value,
|
value,
|
||||||
side,
|
side,
|
||||||
positionDecimalPlaces = 0,
|
positionDecimalPlaces = 0,
|
||||||
|
forceTheme,
|
||||||
}: {
|
}: {
|
||||||
value: string;
|
value: string;
|
||||||
side: Schema.Side;
|
side?: Schema.Side;
|
||||||
positionDecimalPlaces?: number;
|
positionDecimalPlaces?: number;
|
||||||
|
forceTheme?: 'dark' | 'light';
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
data-testid="size"
|
data-testid="size"
|
||||||
className={classNames('text-right', {
|
className={classNames('text-right', {
|
||||||
'text-vega-green dark:text-vega-green': side === Schema.Side.SIDE_BUY,
|
// BUY
|
||||||
'text-vega-pink dark:text-vega-pink': side === Schema.Side.SIDE_SELL,
|
'text-vega-green-dark dark:text-vega-green':
|
||||||
|
side === Schema.Side.SIDE_BUY && !forceTheme,
|
||||||
|
'text-vega-green-dark':
|
||||||
|
side === Schema.Side.SIDE_BUY && forceTheme === 'light',
|
||||||
|
'text-vega-green':
|
||||||
|
side === Schema.Side.SIDE_BUY && forceTheme === 'dark',
|
||||||
|
// SELL
|
||||||
|
'text-vega-pink-dark dark:text-vega-pink':
|
||||||
|
side === Schema.Side.SIDE_SELL && !forceTheme,
|
||||||
|
'text-vega-pink-dark':
|
||||||
|
side === Schema.Side.SIDE_SELL && forceTheme === 'light',
|
||||||
|
'text-vega-pink':
|
||||||
|
side === Schema.Side.SIDE_SELL && forceTheme === 'dark',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{side === Schema.Side.SIDE_BUY
|
{side === Schema.Side.SIDE_BUY
|
||||||
|
@ -215,7 +215,7 @@ export const OrderStatusMapping: {
|
|||||||
STATUS_EXPIRED: 'Expired',
|
STATUS_EXPIRED: 'Expired',
|
||||||
STATUS_FILLED: 'Filled',
|
STATUS_FILLED: 'Filled',
|
||||||
STATUS_PARKED: 'Parked',
|
STATUS_PARKED: 'Parked',
|
||||||
STATUS_PARTIALLY_FILLED: 'PartiallyFilled',
|
STATUS_PARTIALLY_FILLED: 'Partially Filled',
|
||||||
STATUS_REJECTED: 'Rejected',
|
STATUS_REJECTED: 'Rejected',
|
||||||
STATUS_STOPPED: 'Stopped',
|
STATUS_STOPPED: 'Stopped',
|
||||||
};
|
};
|
||||||
|
@ -9,18 +9,14 @@ export default {
|
|||||||
} as ComponentMeta<typeof Toast>;
|
} as ComponentMeta<typeof Toast>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Toast> = (args) => {
|
const Template: ComponentStory<typeof Toast> = (args) => {
|
||||||
return (
|
const toastContent = (
|
||||||
<Toast
|
|
||||||
{...args}
|
|
||||||
render={() => (
|
|
||||||
<>
|
<>
|
||||||
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
|
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
|
||||||
<p>Eaque exercitationem saepe cupiditate sunt impedit.</p>
|
<p>Eaque exercitationem saepe cupiditate sunt impedit.</p>
|
||||||
<p>I really like 🥪🥪🥪!</p>
|
<p>I really like 🥪🥪🥪!</p>
|
||||||
</>
|
</>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
return <Toast {...args} content={toastContent} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
export const Default = Template.bind({});
|
||||||
|
@ -11,15 +11,14 @@ import { Intent } from '../../utils/intent';
|
|||||||
import { Icon } from '../icon';
|
import { Icon } from '../icon';
|
||||||
import { Loader } from '../loader';
|
import { Loader } from '../loader';
|
||||||
|
|
||||||
type ToastContentProps = { id: string };
|
export type ToastContent = JSX.Element | undefined;
|
||||||
type ToastContent = (props: ToastContentProps) => JSX.Element;
|
|
||||||
|
|
||||||
type ToastState = 'initial' | 'showing' | 'expired';
|
type ToastState = 'initial' | 'showing' | 'expired';
|
||||||
|
|
||||||
export type Toast = {
|
export type Toast = {
|
||||||
id: string;
|
id: string;
|
||||||
intent: Intent;
|
intent: Intent;
|
||||||
render: ToastContent;
|
content: ToastContent;
|
||||||
closeAfter?: number;
|
closeAfter?: number;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
signal?: 'close';
|
signal?: 'close';
|
||||||
@ -52,7 +51,7 @@ export const CLOSE_DELAY = 750;
|
|||||||
export const Toast = ({
|
export const Toast = ({
|
||||||
id,
|
id,
|
||||||
intent,
|
intent,
|
||||||
render,
|
content,
|
||||||
closeAfter,
|
closeAfter,
|
||||||
signal,
|
signal,
|
||||||
state = 'initial',
|
state = 'initial',
|
||||||
@ -137,7 +136,7 @@ export const Toast = ({
|
|||||||
className="flex-1 p-2 pr-6 text-sm overflow-auto"
|
className="flex-1 p-2 pr-6 text-sm overflow-auto"
|
||||||
data-testid="toast-content"
|
data-testid="toast-content"
|
||||||
>
|
>
|
||||||
{render({ id })}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,17 +23,17 @@ describe('ToastsContainer', () => {
|
|||||||
add({
|
add({
|
||||||
id: 'toast-a',
|
id: 'toast-a',
|
||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
render: () => <p>A</p>,
|
content: <p>A</p>,
|
||||||
});
|
});
|
||||||
add({
|
add({
|
||||||
id: 'toast-b',
|
id: 'toast-b',
|
||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
render: () => <p>B</p>,
|
content: <p>B</p>,
|
||||||
});
|
});
|
||||||
add({
|
add({
|
||||||
id: 'toast-c',
|
id: 'toast-c',
|
||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
render: () => <p>C</p>,
|
content: <p>C</p>,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const { baseElement } = render(
|
const { baseElement } = render(
|
||||||
@ -54,17 +54,17 @@ describe('ToastsContainer', () => {
|
|||||||
add({
|
add({
|
||||||
id: 'toast-a',
|
id: 'toast-a',
|
||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
render: () => <p>A</p>,
|
content: <p>A</p>,
|
||||||
});
|
});
|
||||||
add({
|
add({
|
||||||
id: 'toast-b',
|
id: 'toast-b',
|
||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
render: () => <p>B</p>,
|
content: <p>B</p>,
|
||||||
});
|
});
|
||||||
add({
|
add({
|
||||||
id: 'toast-c',
|
id: 'toast-c',
|
||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
render: () => <p>C</p>,
|
content: <p>C</p>,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const { baseElement } = render(
|
const { baseElement } = render(
|
||||||
@ -90,13 +90,13 @@ describe('ToastsContainer', () => {
|
|||||||
add({
|
add({
|
||||||
id: 'toast-a',
|
id: 'toast-a',
|
||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
render: () => <p>A</p>,
|
content: <p>A</p>,
|
||||||
onClose: () => remove('toast-a'),
|
onClose: () => remove('toast-a'),
|
||||||
});
|
});
|
||||||
add({
|
add({
|
||||||
id: 'toast-b',
|
id: 'toast-b',
|
||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
render: () => <p>B</p>,
|
content: <p>B</p>,
|
||||||
onClose: () => remove('toast-b'),
|
onClose: () => remove('toast-b'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -69,7 +69,7 @@ const randomToast = (): Toast => {
|
|||||||
Intent.Danger,
|
Intent.Danger,
|
||||||
Intent.Success,
|
Intent.Success,
|
||||||
]) as Intent,
|
]) as Intent,
|
||||||
render: () => <p>{content}</p>,
|
content: <p>{content}</p>,
|
||||||
closeAfter: sample([undefined, random(1000, 5000)]),
|
closeAfter: sample([undefined, random(1000, 5000)]),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -114,20 +114,20 @@ const Template: ComponentStory<typeof ToastsContainer> = (args) => {
|
|||||||
];
|
];
|
||||||
add({
|
add({
|
||||||
...t,
|
...t,
|
||||||
render: ({ id }) => (
|
content: (
|
||||||
<>
|
<>
|
||||||
<h1>{words[0]}</h1>
|
<h1>{words[0]}</h1>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
className="underline text-gray-600 mr-2"
|
className="underline text-gray-600 mr-2"
|
||||||
onClick={() => setTimeout(() => close(id), 500)}
|
onClick={() => setTimeout(() => close(t.id), 500)}
|
||||||
>
|
>
|
||||||
{words[1]}
|
{words[1]}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="underline text-gray-600"
|
className="underline text-gray-600"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
update(id, {
|
update(t.id, {
|
||||||
intent: sample([
|
intent: sample([
|
||||||
Intent.Danger,
|
Intent.Danger,
|
||||||
Intent.Warning,
|
Intent.Warning,
|
||||||
@ -164,7 +164,7 @@ const Template: ComponentStory<typeof ToastsContainer> = (args) => {
|
|||||||
};
|
};
|
||||||
add({
|
add({
|
||||||
...t,
|
...t,
|
||||||
render: () => <ToastContent />,
|
content: <ToastContent />,
|
||||||
onClose: () => remove(t.id),
|
onClose: () => remove(t.id),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ export const ToastsContainer = ({
|
|||||||
return (
|
return (
|
||||||
<ul
|
<ul
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute top-0 right-0 pt-2 pr-2 max-w-full z-20 max-h-full overflow-auto',
|
'absolute top-0 right-0 pt-2 pr-2 max-w-full z-20 max-h-full overflow-x-hidden overflow-y-auto',
|
||||||
{
|
{
|
||||||
'flex flex-col-reverse': order === 'desc',
|
'flex flex-col-reverse': order === 'desc',
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,15 @@ fragment OrderBusEventFields on Order {
|
|||||||
tradableInstrument {
|
tradableInstrument {
|
||||||
instrument {
|
instrument {
|
||||||
name
|
name
|
||||||
|
code
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
settlementAsset {
|
||||||
|
symbol
|
||||||
|
decimals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
libs/wallet/src/__generated__/TransactionResult.ts
generated
13
libs/wallet/src/__generated__/TransactionResult.ts
generated
@ -21,14 +21,14 @@ export type WithdrawalBusEventSubscriptionVariables = Types.Exact<{
|
|||||||
|
|
||||||
export type WithdrawalBusEventSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order' } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal' } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote' } | { __typename?: 'Withdrawal', id: string, status: Types.WithdrawalStatus, amount: string, createdTimestamp: any, withdrawnTimestamp?: any | null, txHash?: string | null, pendingOnForeignChain: boolean, asset: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, status: Types.AssetStatus, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } }, details?: { __typename?: 'Erc20WithdrawalDetails', receiverAddress: string } | null } }> | null };
|
export type WithdrawalBusEventSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order' } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal' } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote' } | { __typename?: 'Withdrawal', id: string, status: Types.WithdrawalStatus, amount: string, createdTimestamp: any, withdrawnTimestamp?: any | null, txHash?: string | null, pendingOnForeignChain: boolean, asset: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, status: Types.AssetStatus, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } }, details?: { __typename?: 'Erc20WithdrawalDetails', receiverAddress: string } | null } }> | null };
|
||||||
|
|
||||||
export type OrderBusEventFieldsFragment = { __typename?: 'Order', type?: Types.OrderType | null, id: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, createdAt: any, size: string, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, side: Types.Side, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } };
|
export type OrderBusEventFieldsFragment = { __typename?: 'Order', type?: Types.OrderType | null, id: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, createdAt: any, size: string, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, side: Types.Side, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', symbol: string, decimals: number } } } } } };
|
||||||
|
|
||||||
export type OrderBusEventsSubscriptionVariables = Types.Exact<{
|
export type OrderBusEventsSubscriptionVariables = Types.Exact<{
|
||||||
partyId: Types.Scalars['ID'];
|
partyId: Types.Scalars['ID'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type OrderBusEventsSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', type: Types.BusEventType, event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order', type?: Types.OrderType | null, id: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, createdAt: any, size: string, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, side: Types.Side, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal' } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote' } | { __typename?: 'Withdrawal' } }> | null };
|
export type OrderBusEventsSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', type: Types.BusEventType, event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order', type?: Types.OrderType | null, id: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, createdAt: any, size: string, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, side: Types.Side, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', symbol: string, decimals: number } } } } } } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal' } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote' } | { __typename?: 'Withdrawal' } }> | null };
|
||||||
|
|
||||||
export type DepositBusEventFieldsFragment = { __typename?: 'Deposit', id: string, status: Types.DepositStatus, amount: string, createdTimestamp: any, creditedTimestamp?: any | null, txHash?: string | null, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number } };
|
export type DepositBusEventFieldsFragment = { __typename?: 'Deposit', id: string, status: Types.DepositStatus, amount: string, createdTimestamp: any, creditedTimestamp?: any | null, txHash?: string | null, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number } };
|
||||||
|
|
||||||
@ -94,6 +94,15 @@ export const OrderBusEventFieldsFragmentDoc = gql`
|
|||||||
tradableInstrument {
|
tradableInstrument {
|
||||||
instrument {
|
instrument {
|
||||||
name
|
name
|
||||||
|
code
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
settlementAsset {
|
||||||
|
symbol
|
||||||
|
decimals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { WalletClient } from '@vegaprotocol/wallet-client';
|
import { WalletClient, WalletClientError } from '@vegaprotocol/wallet-client';
|
||||||
import { clearConfig, getConfig, setConfig } from '../storage';
|
import { clearConfig, getConfig, setConfig } from '../storage';
|
||||||
import type { Transaction, VegaConnector } from './vega-connector';
|
import type { Transaction, VegaConnector } from './vega-connector';
|
||||||
import { WalletError } from './vega-connector';
|
import { WalletError } from './vega-connector';
|
||||||
@ -21,16 +21,14 @@ export const ClientErrors = {
|
|||||||
105,
|
105,
|
||||||
t('Unknown error occurred')
|
t('Unknown error occurred')
|
||||||
),
|
),
|
||||||
|
NO_CLIENT: new WalletError(t('No client found.'), 106),
|
||||||
|
REQUEST_REJECTED: new WalletError(
|
||||||
|
t('Request rejected'),
|
||||||
|
107,
|
||||||
|
t('The request has been rejected by the user')
|
||||||
|
),
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
class NoClientError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super(
|
|
||||||
t('No client found. The connector needs to be initialized with a url.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class JsonRpcConnector implements VegaConnector {
|
export class JsonRpcConnector implements VegaConnector {
|
||||||
version = VERSION;
|
version = VERSION;
|
||||||
private _url: string | null = null;
|
private _url: string | null = null;
|
||||||
@ -62,7 +60,7 @@ export class JsonRpcConnector implements VegaConnector {
|
|||||||
|
|
||||||
async getChainId() {
|
async getChainId() {
|
||||||
if (!this.client) {
|
if (!this.client) {
|
||||||
throw new NoClientError();
|
throw ClientErrors.NO_CLIENT;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { result } = await this.client.GetChainId();
|
const { result } = await this.client.GetChainId();
|
||||||
@ -74,7 +72,7 @@ export class JsonRpcConnector implements VegaConnector {
|
|||||||
|
|
||||||
async connectWallet() {
|
async connectWallet() {
|
||||||
if (!this.client) {
|
if (!this.client) {
|
||||||
throw new NoClientError();
|
throw ClientErrors.NO_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -86,7 +84,11 @@ export class JsonRpcConnector implements VegaConnector {
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw ClientErrors.INVALID_RESPONSE;
|
const clientErr =
|
||||||
|
err instanceof WalletClientError && err.code === 3001
|
||||||
|
? ClientErrors.REQUEST_REJECTED
|
||||||
|
: ClientErrors.INVALID_RESPONSE;
|
||||||
|
throw clientErr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +96,7 @@ export class JsonRpcConnector implements VegaConnector {
|
|||||||
// which retrieves the session token
|
// which retrieves the session token
|
||||||
async connect() {
|
async connect() {
|
||||||
if (!this.client) {
|
if (!this.client) {
|
||||||
throw new NoClientError();
|
throw ClientErrors.NO_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -107,7 +109,7 @@ export class JsonRpcConnector implements VegaConnector {
|
|||||||
|
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
if (!this.client) {
|
if (!this.client) {
|
||||||
throw new NoClientError();
|
throw ClientErrors.NO_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.client.DisconnectWallet();
|
await this.client.DisconnectWallet();
|
||||||
@ -116,10 +118,9 @@ export class JsonRpcConnector implements VegaConnector {
|
|||||||
|
|
||||||
async sendTx(pubKey: string, transaction: Transaction) {
|
async sendTx(pubKey: string, transaction: Transaction) {
|
||||||
if (!this.client) {
|
if (!this.client) {
|
||||||
throw new NoClientError();
|
throw ClientErrors.NO_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const { result } = await this.client.SendTransaction({
|
const { result } = await this.client.SendTransaction({
|
||||||
publicKey: pubKey,
|
publicKey: pubKey,
|
||||||
sendingMode: 'TYPE_SYNC',
|
sendingMode: 'TYPE_SYNC',
|
||||||
@ -132,9 +133,6 @@ export class JsonRpcConnector implements VegaConnector {
|
|||||||
receivedAt: result.receivedAt,
|
receivedAt: result.receivedAt,
|
||||||
signature: result.transaction.signature.value,
|
signature: result.transaction.signature.value,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
|
||||||
throw ClientErrors.INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkCompat() {
|
async checkCompat() {
|
||||||
|
@ -318,6 +318,11 @@ export const isOrderAmendmentTransaction = (
|
|||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
): transaction is OrderAmendmentBody => 'orderAmendment' in transaction;
|
): transaction is OrderAmendmentBody => 'orderAmendment' in transaction;
|
||||||
|
|
||||||
|
export const isBatchMarketInstructionsTransaction = (
|
||||||
|
transaction: Transaction
|
||||||
|
): transaction is BatchMarketInstructionSubmissionBody =>
|
||||||
|
'batchMarketInstructions' in transaction;
|
||||||
|
|
||||||
export interface TransactionResponse {
|
export interface TransactionResponse {
|
||||||
transactionHash: string;
|
transactionHash: string;
|
||||||
signature: string; // still to be added by core
|
signature: string; // still to be added by core
|
||||||
|
@ -4,6 +4,7 @@ import { ClientErrors } from './connectors';
|
|||||||
import { WalletError } from './connectors';
|
import { WalletError } from './connectors';
|
||||||
import { VegaTxStatus } from './use-vega-transaction';
|
import { VegaTxStatus } from './use-vega-transaction';
|
||||||
import { useVegaTransactionStore } from './use-vega-transaction-store';
|
import { useVegaTransactionStore } from './use-vega-transaction-store';
|
||||||
|
import { WalletClientError } from '@vegaprotocol/wallet-client';
|
||||||
|
|
||||||
export const useVegaTransactionManager = () => {
|
export const useVegaTransactionManager = () => {
|
||||||
const { sendTx, pubKey } = useVegaWallet();
|
const { sendTx, pubKey } = useVegaWallet();
|
||||||
@ -39,7 +40,10 @@ export const useVegaTransactionManager = () => {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
update(transaction.id, {
|
update(transaction.id, {
|
||||||
error: err instanceof WalletError ? err : ClientErrors.UNKNOWN,
|
error:
|
||||||
|
err instanceof WalletError || err instanceof WalletClientError
|
||||||
|
? err
|
||||||
|
: ClientErrors.UNKNOWN,
|
||||||
status: VegaTxStatus.Error,
|
status: VegaTxStatus.Error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
isOrderSubmissionTransaction,
|
isOrderSubmissionTransaction,
|
||||||
isOrderCancellationTransaction,
|
isOrderCancellationTransaction,
|
||||||
isOrderAmendmentTransaction,
|
isOrderAmendmentTransaction,
|
||||||
|
isBatchMarketInstructionsTransaction,
|
||||||
} from './connectors';
|
} from './connectors';
|
||||||
import { determineId } from './utils';
|
import { determineId } from './utils';
|
||||||
|
|
||||||
@ -126,17 +127,42 @@ export const useVegaTransactionStore = create<VegaTransactionStore>(
|
|||||||
updateOrder: (order: OrderBusEventFieldsFragment) => {
|
updateOrder: (order: OrderBusEventFieldsFragment) => {
|
||||||
set(
|
set(
|
||||||
produce((state: VegaTransactionStore) => {
|
produce((state: VegaTransactionStore) => {
|
||||||
const transaction = state.transactions.find(
|
const transaction = state.transactions.find((transaction) => {
|
||||||
(transaction) =>
|
if (!transaction || transaction.status !== VegaTxStatus.Pending) {
|
||||||
transaction &&
|
return false;
|
||||||
transaction.status === VegaTxStatus.Pending &&
|
}
|
||||||
|
if (
|
||||||
|
isOrderSubmissionTransaction(transaction?.body) &&
|
||||||
transaction.signature &&
|
transaction.signature &&
|
||||||
(isOrderSubmissionTransaction(transaction?.body) ||
|
|
||||||
isOrderCancellationTransaction(transaction?.body) ||
|
|
||||||
isOrderAmendmentTransaction(transaction?.body)) &&
|
|
||||||
order.id === determineId(transaction.signature)
|
order.id === determineId(transaction.signature)
|
||||||
);
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isOrderCancellationTransaction(transaction?.body) &&
|
||||||
|
order.id === transaction.body.orderCancellation.orderId
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isOrderAmendmentTransaction(transaction?.body) &&
|
||||||
|
order.id === transaction.body.orderAmendment.orderId
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isBatchMarketInstructionsTransaction(transaction?.body) &&
|
||||||
|
transaction.signature &&
|
||||||
|
order.id === determineId(transaction.signature)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
|
// TODO: handle multiple orders from batch market instructions
|
||||||
|
// Note: If multiple orders are submitted the first order ID is determined by hashing the signature of the transaction
|
||||||
|
// (see determineId function). For each subsequent order's ID, a hash of the previous orders ID is used
|
||||||
transaction.order = order;
|
transaction.order = order;
|
||||||
transaction.status = VegaTxStatus.Complete;
|
transaction.status = VegaTxStatus.Complete;
|
||||||
transaction.dialogOpen = true;
|
transaction.dialogOpen = true;
|
||||||
@ -158,6 +184,14 @@ export const useVegaTransactionStore = create<VegaTransactionStore>(
|
|||||||
);
|
);
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
transaction.transactionResult = transactionResult;
|
transaction.transactionResult = transactionResult;
|
||||||
|
if (
|
||||||
|
isOrderCancellationTransaction(transaction.body) &&
|
||||||
|
!transaction.body.orderCancellation.orderId &&
|
||||||
|
!transactionResult.error &&
|
||||||
|
transactionResult.status
|
||||||
|
) {
|
||||||
|
transaction.status = VegaTxStatus.Complete;
|
||||||
|
}
|
||||||
transaction.dialogOpen = true;
|
transaction.dialogOpen = true;
|
||||||
transaction.updatedAt = new Date();
|
transaction.updatedAt = new Date();
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,15 @@ const orderBusEvent: OrderBusEventFieldsFragment = {
|
|||||||
__typename: 'TradableInstrument',
|
__typename: 'TradableInstrument',
|
||||||
instrument: {
|
instrument: {
|
||||||
name: 'UNIDAI Monthly (30 Jun 2022)',
|
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||||
|
code: 'UNIDAI',
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
settlementAsset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
decimals: 8,
|
||||||
|
symbol: 'AAA',
|
||||||
|
},
|
||||||
|
},
|
||||||
__typename: 'Instrument',
|
__typename: 'Instrument',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -7,6 +7,7 @@ import type { Intent } from '@vegaprotocol/ui-toolkit';
|
|||||||
import type { Transaction } from './connectors';
|
import type { Transaction } from './connectors';
|
||||||
import { ClientErrors } from './connectors';
|
import { ClientErrors } from './connectors';
|
||||||
import { WalletError } from './connectors';
|
import { WalletError } from './connectors';
|
||||||
|
import type { WalletClientError } from '@vegaprotocol/wallet-client';
|
||||||
|
|
||||||
export interface DialogProps {
|
export interface DialogProps {
|
||||||
intent?: Intent;
|
intent?: Intent;
|
||||||
@ -25,7 +26,7 @@ export enum VegaTxStatus {
|
|||||||
|
|
||||||
export interface VegaTxState {
|
export interface VegaTxState {
|
||||||
status: VegaTxStatus;
|
status: VegaTxStatus;
|
||||||
error: WalletError | Error | null;
|
error: WalletError | WalletClientError | Error | null;
|
||||||
txHash: string | null;
|
txHash: string | null;
|
||||||
signature: string | null;
|
signature: string | null;
|
||||||
dialogOpen: boolean;
|
dialogOpen: boolean;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { VegaWalletContext } from '.';
|
import { useVegaWalletDialogStore, VegaWalletContext } from '.';
|
||||||
|
|
||||||
export function useVegaWallet() {
|
export function useVegaWallet() {
|
||||||
const context = useContext(VegaWalletContext);
|
const context = useContext(VegaWalletContext);
|
||||||
@ -8,3 +8,16 @@ export function useVegaWallet() {
|
|||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useReconnectVegaWallet() {
|
||||||
|
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||||
|
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||||
|
}));
|
||||||
|
const { disconnect } = useVegaWallet();
|
||||||
|
const reconnect = useCallback(async () => {
|
||||||
|
await disconnect();
|
||||||
|
openVegaWalletDialog();
|
||||||
|
}, [disconnect, openVegaWalletDialog]);
|
||||||
|
|
||||||
|
return reconnect;
|
||||||
|
}
|
||||||
|
@ -1,10 +1,127 @@
|
|||||||
import { determineId } from './utils';
|
import {
|
||||||
|
determineId,
|
||||||
it('produces a known result for an ID', () => {
|
normalizeOrderAmendment,
|
||||||
|
normalizeOrderSubmission,
|
||||||
|
} from './utils';
|
||||||
|
import type { OrderSubmissionBody } from './connectors/vega-connector';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
describe('determineId', () => {
|
||||||
|
it('produces a known result for an ID', () => {
|
||||||
const res = determineId(
|
const res = determineId(
|
||||||
'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909'
|
'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909'
|
||||||
);
|
);
|
||||||
expect(res).toStrictEqual(
|
expect(res).toStrictEqual(
|
||||||
'2fca514cebf9f465ae31ecb4c5721e3a6f5f260425ded887ca50ba15b81a5d50'
|
'2fca514cebf9f465ae31ecb4c5721e3a6f5f260425ded887ca50ba15b81a5d50'
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('normalizeOrderSubmission', () => {
|
||||||
|
it('sets and formats price only for limit orders', () => {
|
||||||
|
expect(
|
||||||
|
normalizeOrderSubmission(
|
||||||
|
{ price: '100' } as unknown as OrderSubmissionBody['orderSubmission'],
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
).price
|
||||||
|
).toBeUndefined();
|
||||||
|
expect(
|
||||||
|
normalizeOrderSubmission(
|
||||||
|
{
|
||||||
|
price: '100',
|
||||||
|
type: Schema.OrderType.TYPE_LIMIT,
|
||||||
|
} as unknown as OrderSubmissionBody['orderSubmission'],
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
).price
|
||||||
|
).toEqual('10000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets and formats expiresAt only for time in force orders', () => {
|
||||||
|
expect(
|
||||||
|
normalizeOrderSubmission(
|
||||||
|
{
|
||||||
|
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||||
|
} as OrderSubmissionBody['orderSubmission'],
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
).expiresAt
|
||||||
|
).toBeUndefined();
|
||||||
|
expect(
|
||||||
|
normalizeOrderSubmission(
|
||||||
|
{
|
||||||
|
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||||
|
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||||
|
} as OrderSubmissionBody['orderSubmission'],
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
).expiresAt
|
||||||
|
).toEqual('1640995200000000000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats size', () => {
|
||||||
|
expect(
|
||||||
|
normalizeOrderSubmission(
|
||||||
|
{
|
||||||
|
size: '100',
|
||||||
|
} as OrderSubmissionBody['orderSubmission'],
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
).size
|
||||||
|
).toEqual('1000');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('normalizeOrderAmendment', () => {
|
||||||
|
type Order = Parameters<typeof normalizeOrderAmendment>[0];
|
||||||
|
type Market = Parameters<typeof normalizeOrderAmendment>[1];
|
||||||
|
const order: Order = {
|
||||||
|
id: '123',
|
||||||
|
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||||
|
size: '100',
|
||||||
|
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
const market: Market = {
|
||||||
|
id: '456',
|
||||||
|
decimalPlaces: 1,
|
||||||
|
positionDecimalPlaces: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('sets and formats order id, market id, expires and timeInForce as given', () => {
|
||||||
|
const orderAmendment = normalizeOrderAmendment(order, market, '1', '1');
|
||||||
|
expect(orderAmendment.orderId).toEqual('123');
|
||||||
|
expect(orderAmendment.marketId).toEqual('456');
|
||||||
|
expect(orderAmendment.expiresAt).toEqual('1640995200000000000');
|
||||||
|
expect(orderAmendment.timeInForce).toEqual(
|
||||||
|
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['1.1', 1, '11'],
|
||||||
|
['1.1', 2, '110'],
|
||||||
|
['0.001', 8, '100000'],
|
||||||
|
])('sets and formats price', (price, decimalPlaces, output) => {
|
||||||
|
const orderAmendment = normalizeOrderAmendment(
|
||||||
|
order,
|
||||||
|
{ ...market, decimalPlaces },
|
||||||
|
price,
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
expect(orderAmendment.price).toEqual(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['9', 1, -10],
|
||||||
|
['90', 2, 8900],
|
||||||
|
['0.001', 8, 99900],
|
||||||
|
])('sets and formats size delta', (size, positionDecimalPlaces, output) => {
|
||||||
|
const orderAmendment = normalizeOrderAmendment(
|
||||||
|
order,
|
||||||
|
{ ...market, positionDecimalPlaces },
|
||||||
|
'1',
|
||||||
|
size
|
||||||
|
);
|
||||||
|
expect(orderAmendment.sizeDelta).toEqual(output);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
|
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/react-helpers';
|
||||||
|
import type { Market, Order } from '@vegaprotocol/types';
|
||||||
|
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { sha3_256 } from 'js-sha3';
|
import { sha3_256 } from 'js-sha3';
|
||||||
import type { Transaction } from './connectors';
|
import type {
|
||||||
|
OrderAmendmentBody,
|
||||||
|
OrderSubmissionBody,
|
||||||
|
Transaction,
|
||||||
|
} from './connectors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an ID in the same way that core does on the backend. This way we
|
* Creates an ID in the same way that core does on the backend. This way we
|
||||||
@ -18,3 +26,40 @@ export const encodeTransaction = (tx: Transaction): string => {
|
|||||||
ethers.utils.toUtf8Bytes(JSON.stringify(tx))
|
ethers.utils.toUtf8Bytes(JSON.stringify(tx))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const normalizeOrderSubmission = (
|
||||||
|
order: OrderSubmissionBody['orderSubmission'],
|
||||||
|
decimalPlaces: number,
|
||||||
|
positionDecimalPlaces: number
|
||||||
|
): OrderSubmissionBody['orderSubmission'] => ({
|
||||||
|
...order,
|
||||||
|
price:
|
||||||
|
order.type === OrderType.TYPE_LIMIT && order.price
|
||||||
|
? removeDecimal(order.price, decimalPlaces)
|
||||||
|
: undefined,
|
||||||
|
size: removeDecimal(order.size, positionDecimalPlaces),
|
||||||
|
expiresAt:
|
||||||
|
order.expiresAt && order.timeInForce === OrderTimeInForce.TIME_IN_FORCE_GTT
|
||||||
|
? toNanoSeconds(order.expiresAt)
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const normalizeOrderAmendment = (
|
||||||
|
order: Pick<Order, 'id' | 'timeInForce' | 'size' | 'expiresAt'>,
|
||||||
|
market: Pick<Market, 'id' | 'decimalPlaces' | 'positionDecimalPlaces'>,
|
||||||
|
price: string,
|
||||||
|
size: string
|
||||||
|
): OrderAmendmentBody['orderAmendment'] => ({
|
||||||
|
orderId: order.id,
|
||||||
|
marketId: market.id,
|
||||||
|
price: removeDecimal(price, market.decimalPlaces),
|
||||||
|
timeInForce: order.timeInForce,
|
||||||
|
sizeDelta: size
|
||||||
|
? new BigNumber(removeDecimal(size, market.positionDecimalPlaces))
|
||||||
|
.minus(order.size)
|
||||||
|
.toNumber()
|
||||||
|
: 0,
|
||||||
|
expiresAt: order.expiresAt
|
||||||
|
? toNanoSeconds(order.expiresAt) // Wallet expects timestamp in nanoseconds
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
@ -21,19 +21,19 @@ export interface EthStoredTxState extends EthTxState {
|
|||||||
id: number;
|
id: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
contract: Contract;
|
contract: Contract | null;
|
||||||
methodName: ContractMethod;
|
methodName: ContractMethod;
|
||||||
args: string[];
|
args: string[];
|
||||||
requiredConfirmations: number;
|
requiredConfirmations: number;
|
||||||
requiresConfirmation: boolean;
|
requiresConfirmation: boolean;
|
||||||
asset?: string;
|
assetId?: string;
|
||||||
deposit?: DepositBusEventFieldsFragment;
|
deposit?: DepositBusEventFieldsFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EthTransactionStore {
|
export interface EthTransactionStore {
|
||||||
transactions: (EthStoredTxState | undefined)[];
|
transactions: (EthStoredTxState | undefined)[];
|
||||||
create: (
|
create: (
|
||||||
contract: Contract,
|
contract: Contract | null,
|
||||||
methodName: ContractMethod,
|
methodName: ContractMethod,
|
||||||
args: string[],
|
args: string[],
|
||||||
assetId?: string,
|
assetId?: string,
|
||||||
@ -58,10 +58,10 @@ export const useEthTransactionStore = create<EthTransactionStore>(
|
|||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
transactions: [] as EthStoredTxState[],
|
transactions: [] as EthStoredTxState[],
|
||||||
create: (
|
create: (
|
||||||
contract: Contract,
|
contract: Contract | null,
|
||||||
methodName: ContractMethod,
|
methodName: ContractMethod,
|
||||||
args: string[] = [],
|
args: string[] = [],
|
||||||
asset,
|
assetId?: string,
|
||||||
requiredConfirmations = 1,
|
requiredConfirmations = 1,
|
||||||
requiresConfirmation = false
|
requiresConfirmation = false
|
||||||
) => {
|
) => {
|
||||||
@ -82,7 +82,7 @@ export const useEthTransactionStore = create<EthTransactionStore>(
|
|||||||
dialogOpen: true,
|
dialogOpen: true,
|
||||||
requiredConfirmations,
|
requiredConfirmations,
|
||||||
requiresConfirmation,
|
requiresConfirmation,
|
||||||
asset: asset,
|
assetId,
|
||||||
};
|
};
|
||||||
set({ transactions: transactions.concat(transaction) });
|
set({ transactions: transactions.concat(transaction) });
|
||||||
return transaction.id;
|
return transaction.id;
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
"@sentry/nextjs": "^6.19.3",
|
"@sentry/nextjs": "^6.19.3",
|
||||||
"@sentry/react": "^6.19.2",
|
"@sentry/react": "^6.19.2",
|
||||||
"@sentry/tracing": "^6.19.2",
|
"@sentry/tracing": "^6.19.2",
|
||||||
"@vegaprotocol/wallet-client": "0.1.4",
|
"@vegaprotocol/wallet-client": "0.1.5",
|
||||||
"@walletconnect/ethereum-provider": "^1.7.5",
|
"@walletconnect/ethereum-provider": "^1.7.5",
|
||||||
"@web3-react/core": "8.0.20-beta.0",
|
"@web3-react/core": "8.0.20-beta.0",
|
||||||
"@web3-react/metamask": "8.0.16-beta.0",
|
"@web3-react/metamask": "8.0.16-beta.0",
|
||||||
|
@ -7559,10 +7559,10 @@
|
|||||||
"@typescript-eslint/types" "5.40.0"
|
"@typescript-eslint/types" "5.40.0"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
"@vegaprotocol/wallet-client@0.1.4":
|
"@vegaprotocol/wallet-client@0.1.5":
|
||||||
version "0.1.4"
|
version "0.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@vegaprotocol/wallet-client/-/wallet-client-0.1.4.tgz#202fa1a84dbef57199810383f2887a7ee0afd64c"
|
resolved "https://registry.yarnpkg.com/@vegaprotocol/wallet-client/-/wallet-client-0.1.5.tgz#9d72a7fc9ceb9767f5119c9b7eebe29a2c30f682"
|
||||||
integrity sha512-uGEbusoi3lwyl7Nn9ovBg9YHrfcH/Rl33KUcLfdMeTX8FZO+n7BVm3ejd00e5RsM/PJlqJ1oW4qkiq7kervxng==
|
integrity sha512-7FmIBFxissr3h2QsEjvD+HXEXJ3u/oaVUg055IgZ08dmy1+4Nx22BOffFyidLBmaH1xJYjyiqxHnhLGnN5BfwA==
|
||||||
dependencies:
|
dependencies:
|
||||||
express "4.18.2"
|
express "4.18.2"
|
||||||
nanoid "3.3.4"
|
nanoid "3.3.4"
|
||||||
|
Loading…
Reference in New Issue
Block a user