Compare commits

..
Author SHA1 Message Date
asiaznik cdbef93ab5 fix(environment): missing block height in node switcher 2023-11-07 17:18:14 +01:00
29 changed files with 582 additions and 744 deletions
@@ -133,7 +133,7 @@ export const proposalsData = {
instrument: {
name: 'UNIDAI Monthly (Dec 2022)',
code: 'UNIDAI.MF21',
product: {
futureProduct: {
settlementAsset: { symbol: 'tDAI', __typename: 'Asset' },
__typename: 'FutureProduct',
},
@@ -240,7 +240,7 @@ export const proposalsData = {
instrument: {
name: 'ETHBTC Quarterly (Feb 2023)',
code: 'ETHBTC.QM21',
product: {
futureProduct: {
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
__typename: 'FutureProduct',
},
@@ -77,7 +77,7 @@ describe('Proposal header', () => {
__typename: 'InstrumentConfiguration',
name: 'Some market',
code: 'FX:BTCUSD/DEC99',
product: {
futureProduct: {
__typename: 'FutureProduct',
settlementAsset: {
__typename: 'Asset',
@@ -39,18 +39,6 @@ export const ProposalHeader = ({
const titleContent = shorten(title ?? '', 100);
const getAsset = (proposal: ProposalQuery['proposal']) => {
const terms = proposal?.terms;
if (
terms?.change.__typename === 'NewMarket' &&
(terms.change.instrument.product?.__typename === 'FutureProduct' ||
terms.change.instrument.product?.__typename === 'PerpetualProduct')
) {
return terms.change.instrument.product.settlementAsset;
}
return undefined;
};
switch (change?.__typename) {
case 'NewMarket': {
proposalType =
@@ -66,10 +54,10 @@ export const ProposalHeader = ({
<span>
{t('Code')}: {change.instrument.code}.
</span>{' '}
{proposal?.terms && getAsset(proposal)?.symbol ? (
{change.instrument.futureProduct?.settlementAsset.symbol ? (
<>
<span className="font-semibold">
{getAsset(proposal)?.symbol}
{change.instrument.futureProduct.settlementAsset.symbol}
</span>{' '}
{t('settled future')}.
</>
@@ -132,25 +132,46 @@ query Proposal(
instrument {
name
code
product {
... on FutureProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
futureProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
operator
value
}
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
@@ -158,44 +179,52 @@ query Proposal(
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
}
}
... on PerpetualProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
# dataSourceSpecForTradingTermination {
# sourceType {
# ... on DataSourceDefinitionInternal {
# sourceType {
# ... on DataSourceSpecConfigurationTime {
# conditions {
# operator
# value
# }
# }
# }
# }
# ... on DataSourceDefinitionExternal {
# sourceType {
# ... on DataSourceSpecConfiguration {
# signers {
# signer {
# ... on PubKey {
# key
# }
# ... on ETHAddress {
# address
# }
# }
# }
# filters {
# key {
# name
# type
# }
# conditions {
# operator
# value
# }
# }
# }
# }
# }
# }
# }
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
}
}
File diff suppressed because one or more lines are too long
@@ -101,16 +101,9 @@ fragment ProposalFields on Proposal {
instrument {
name
code
product {
... on FutureProduct {
settlementAsset {
symbol
}
}
... on PerpetualProduct {
settlementAsset {
symbol
}
futureProduct {
settlementAsset {
symbol
}
}
}
@@ -11,7 +11,7 @@ export type UpdateReferralProgramsFragment = { __typename?: 'Proposal', terms: {
export type UpdateVolumeDiscountProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } } };
export type ProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, product?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | { __typename?: 'PerpetualProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | { __typename?: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } };
export type ProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } };
export type ProposalsQueryVariables = Types.Exact<{
includeNewMarketProductFields: Types.Scalars['Boolean'];
@@ -21,7 +21,7 @@ export type ProposalsQueryVariables = Types.Exact<{
}>;
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, product?: { __typename: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | { __typename: 'PerpetualProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram', windowLength: number, endOfProgram: any, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null, product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram', windowLength: number, endOfProgram: any, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
export const NewMarketProductFieldsFragmentDoc = gql`
fragment NewMarketProductFields on Proposal {
@@ -131,16 +131,9 @@ export const ProposalFieldsFragmentDoc = gql`
instrument {
name
code
product {
... on FutureProduct {
settlementAsset {
symbol
}
}
... on PerpetualProduct {
settlementAsset {
symbol
}
futureProduct {
settlementAsset {
symbol
}
}
}
@@ -1,5 +1,7 @@
import { Route, Routes } from 'react-router-dom';
import { Route, Routes, useParams } from 'react-router-dom';
import { MarketState } from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import { useMarket } from '@vegaprotocol/markets';
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
import {
SidebarButton,
@@ -9,7 +11,12 @@ import {
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const MarketsSidebar = () => {
const { marketId } = useParams();
const currentRouteId = useGetCurrentRouteId();
const { data } = useMarket(marketId);
const active =
data &&
[MarketState.STATE_ACTIVE, MarketState.STATE_PENDING].includes(data.state);
return (
<>
@@ -37,12 +44,14 @@ export const MarketsSidebar = () => {
element={
<>
<SidebarDivider />
<SidebarButton
view={ViewType.Order}
icon={VegaIconNames.TICKET}
tooltip={t('Order')}
routeId={currentRouteId}
/>
{active && (
<SidebarButton
view={ViewType.Order}
icon={VegaIconNames.TICKET}
tooltip={t('Order')}
routeId={currentRouteId}
/>
)}
<SidebarButton
view={ViewType.Info}
icon={VegaIconNames.BREAKDOWN}
@@ -8,7 +8,7 @@ jest.mock('@vegaprotocol/accounts', () => ({
),
}));
jest.mock('../../components/welcome-dialog/get-started', () => ({
jest.mock('../../components/welcome-dialog/get-started.ts', () => ({
GetStarted: () => <div>GetStarted</div>,
}));
@@ -8,7 +8,7 @@ jest.mock('../../components/withdraw-container', () => ({
),
}));
jest.mock('../../components/welcome-dialog/get-started', () => ({
jest.mock('../../components/welcome-dialog/get-started.ts', () => ({
GetStarted: () => <div>GetStarted</div>,
}));
+2 -2
View File
@@ -1,11 +1,11 @@
#!/bin/bash -e
yarn --pure-lockfile
app=${1:-trading}
envCmd="envCmd="yarn -f ./apps/${app}/.env.${2:-mainnet}"
envCmd="envCmd="yarn env-cmd -f ./apps/${app}/.env.${2:-mainnet}"
yarn install
if [ "${app}" = "trading" ]; then
$envCmd yarn nx export trading
DIST_LOCATION=dist/apps/trading/exported/
DIST_LOCATION=dist/apps/trading/exported
else
$envCmd yarn nx build ${app}
DIST_LOCATION=dist/apps/${app}
+4 -11
View File
@@ -1,10 +1,9 @@
import sortBy from 'lodash/sortBy';
import * as Schema from '@vegaprotocol/types';
import { truncateByChars } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
NetworkParams,
useNetworkParams,
useNetworkParam,
} from '@vegaprotocol/network-parameters';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type { Transfer } from '@vegaprotocol/wallet';
@@ -22,11 +21,7 @@ export const ALLOWED_ACCOUNTS = [
export const TransferContainer = ({ assetId }: { assetId?: string }) => {
const { pubKey, pubKeys } = useVegaWallet();
const { params } = useNetworkParams([
NetworkParams.transfer_fee_factor,
NetworkParams.transfer_minTransferQuantumMultiple,
]);
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
const { data } = useDataProvider({
dataProvider: accountsDataProvider,
variables: { partyId: pubKey || '' },
@@ -45,7 +40,6 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
const accounts = data
? data.filter((account) => ALLOWED_ACCOUNTS.includes(account.type))
: [];
const sortedAccounts = sortBy(accounts, (a) => a.asset.symbol.toLowerCase());
return (
<>
@@ -65,10 +59,9 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
pubKey={pubKey}
pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null}
assetId={assetId}
feeFactor={params.transfer_fee_factor}
minQuantumMultiple={params.transfer_minTransferQuantumMultiple}
feeFactor={param}
submitTransfer={transfer}
accounts={sortedAccounts}
accounts={accounts}
/>
</>
);
+29 -112
View File
@@ -9,10 +9,18 @@ import {
} from './transfer-form';
import { AccountType } from '@vegaprotocol/types';
import { removeDecimal } from '@vegaprotocol/utils';
import { MockedProvider } from '@apollo/client/testing';
describe('TransferForm', () => {
const renderComponent = (props: TransferFormProps) => {
return render(<TransferForm {...props} />);
return render(
// Wrap with mock provider as the form will make queries to fetch the selected
// toVegaKey accounts. We don't test this for now but we need to wrap so that
// the component has access to the client
<MockedProvider>
<TransferForm {...props} />
</MockedProvider>
);
};
const submit = async () => {
@@ -46,7 +54,6 @@ describe('TransferForm', () => {
symbol: '€',
name: 'EUR',
decimals: 2,
quantum: '1',
};
const props = {
pubKey,
@@ -65,10 +72,9 @@ describe('TransferForm', () => {
{
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
asset,
balance: '10000',
balance: '100000',
},
],
minQuantumMultiple: '1',
};
it('form tooltips correctly displayed', async () => {
@@ -126,7 +132,7 @@ describe('TransferForm', () => {
// 1003-TRAN-004
renderComponent(props);
await submit();
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey is set as default value
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey is set as default value
const toggle = screen.getByText('Enter manually');
await userEvent.click(toggle);
// has switched to input
@@ -139,12 +145,12 @@ describe('TransferForm', () => {
screen.getByLabelText('To Vega key'),
'invalid-address'
);
expect(screen.getAllByTestId('input-error-text')[1]).toHaveTextContent(
expect(screen.getAllByTestId('input-error-text')[0]).toHaveTextContent(
'Invalid Vega key'
);
});
it('sends transfer from general accounts', async () => {
it('validates fields and submits', async () => {
// 1003-TRAN-002
// 1003-TRAN-003
// 1002-WITH-010
@@ -162,7 +168,7 @@ describe('TransferForm', () => {
]);
await submit();
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey is set as default value
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey is set as default value
// Select a pubkey
await userEvent.selectOptions(
@@ -175,20 +181,15 @@ describe('TransferForm', () => {
await userEvent.selectOptions(
screen.getByLabelText('From account'),
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
);
const amountInput = screen.getByLabelText('Amount');
// Test use max button
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue('1000');
// Test amount validation
await userEvent.clear(amountInput);
await userEvent.type(amountInput, '0.001'); // Below quantum multiple amount
await userEvent.type(amountInput, '0.00000001');
expect(
await screen.findByText(/Amount below minimum requirement/)
await screen.findByText('Value is below minimum')
).toBeInTheDocument();
await userEvent.clear(amountInput);
@@ -209,86 +210,9 @@ describe('TransferForm', () => {
await waitFor(() => {
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
expect(props.submitTransfer).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1],
asset: asset.id,
amount: removeDecimal(amount, asset.decimals),
oneOff: {},
});
});
});
it('sends transfer from vested accounts', async () => {
const mockSubmit = jest.fn();
renderComponent({
...props,
submitTransfer: mockSubmit,
minQuantumMultiple: '100000',
});
// check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key');
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
pubKeyOptions
);
await submit();
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
// Select a pubkey
await userEvent.selectOptions(
screen.getByLabelText('To Vega key'),
props.pubKeys[1] // Use not current pubkey so we can check it switches to current pubkey later
);
// Select asset
await selectAsset(asset);
await userEvent.selectOptions(
screen.getByLabelText('From account'),
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${asset.id}`
);
// Check switch back to connected key
expect(screen.getByLabelText('To Vega key')).toHaveValue(props.pubKey);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
await userEvent.clear(amountInput);
await userEvent.type(amountInput, '50');
expect(await screen.findByText(/Use max to bypass/)).toBeInTheDocument();
// Test use max button
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue('100');
// If transfering from a vested account 'include fees' checkbox should
// be disabled and fees should be 0
expect(checkbox).not.toBeChecked();
expect(checkbox).toBeDisabled();
const expectedFee = '0';
const total = new BigNumber(amount).plus(expectedFee).toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
await submit();
await waitFor(() => {
// 1003-TRAN-023
expect(mockSubmit).toHaveBeenCalledTimes(1);
expect(mockSubmit).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKey,
to: props.pubKeys[1],
asset: asset.id,
amount: removeDecimal(amount, asset.decimals),
oneOff: {},
@@ -310,7 +234,7 @@ describe('TransferForm', () => {
);
await submit();
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey set as default value
// Select a pubkey
await userEvent.selectOptions(
@@ -323,7 +247,7 @@ describe('TransferForm', () => {
await userEvent.selectOptions(
screen.getByLabelText('From account'),
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
);
const amountInput = screen.getByLabelText('Amount');
@@ -357,7 +281,7 @@ describe('TransferForm', () => {
// 1003-TRAN-023
expect(mockSubmit).toHaveBeenCalledTimes(1);
expect(mockSubmit).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1],
asset: asset.id,
@@ -379,7 +303,7 @@ describe('TransferForm', () => {
);
await submit();
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey set as default value
// Select a pubkey
await userEvent.selectOptions(
@@ -390,11 +314,6 @@ describe('TransferForm', () => {
// Select asset
await selectAsset(asset);
await userEvent.selectOptions(
screen.getByLabelText('From account'),
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
@@ -414,28 +333,26 @@ describe('TransferForm', () => {
describe('AddressField', () => {
const props = {
mode: 'select' as const,
select: <div>select</div>,
input: <div>input</div>,
onChange: jest.fn(),
};
it('renders correct content by mode prop and calls onChange', async () => {
it('toggles content and calls onChange', async () => {
const mockOnChange = jest.fn();
const { rerender } = render(
<AddressField {...props} onChange={mockOnChange} />
);
render(<AddressField {...props} onChange={mockOnChange} />);
// select should be shown by default
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
await userEvent.click(screen.getByText('Enter manually'));
expect(mockOnChange).toHaveBeenCalled();
rerender(<AddressField {...props} mode="input" />);
expect(screen.queryByText('select')).not.toBeInTheDocument();
expect(screen.getByText('input')).toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(1);
await userEvent.click(screen.getByText('Select from wallet'));
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(2);
});
});
+143 -195
View File
@@ -1,12 +1,12 @@
import sortBy from 'lodash/sortBy';
import {
minSafe,
maxSafe,
required,
vegaPublicKey,
addDecimal,
formatNumber,
addDecimalsFormatNumber,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
@@ -27,20 +27,14 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { AssetOption, Balance } from '@vegaprotocol/assets';
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { accountsDataProvider } from './accounts-data-provider';
interface FormFields {
toVegaKey: string;
asset: string; // This is used to simply filter the from account list, the fromAccount type should be used in the tx
asset: string;
amount: string;
fromAccount: string; // AccountType-AssetId
}
interface Asset {
id: string;
symbol: string;
name: string;
decimals: number;
quantum: string;
fromAccount: AccountType;
}
export interface TransferFormProps {
@@ -49,11 +43,10 @@ export interface TransferFormProps {
accounts: Array<{
type: AccountType;
balance: string;
asset: Asset;
asset: { id: string; symbol: string; name: string; decimals: number };
}>;
assetId?: string;
feeFactor: string | null;
minQuantumMultiple: string | null;
submitTransfer: (transfer: Transfer) => void;
}
@@ -64,7 +57,6 @@ export const TransferForm = ({
feeFactor,
submitTransfer,
accounts,
minQuantumMultiple,
}: TransferFormProps) => {
const {
control,
@@ -80,8 +72,6 @@ export const TransferForm = ({
},
});
const [toVegaKeyMode, setToVegaKeyMode] = useState<ToVegaKeyMode>('select');
const assets = sortBy(
accounts
.filter(
@@ -109,29 +99,48 @@ export const TransferForm = ({
...account.asset,
balance: addDecimal(account.balance, account.asset.decimals),
})),
(a) => a.symbol.toLowerCase()
'name'
);
const selectedPubKey = watch('toVegaKey');
const amount = watch('amount');
const fromAccount = watch('fromAccount');
const selectedAssetId = watch('asset');
const assetId = watch('asset');
// Convert the account type (Type-AssetId) into separate values
const [accountType, accountAssetId] = fromAccount
? parseFromAccount(fromAccount)
: [undefined, undefined];
const fromVested = accountType === AccountType.ACCOUNT_TYPE_VESTED_REWARDS;
const asset = assets.find((a) => a.id === accountAssetId);
const asset = assets.find((a) => a.id === assetId);
const { data: toAccounts } = useDataProvider({
dataProvider: accountsDataProvider,
variables: {
partyId: selectedPubKey,
},
skip: !selectedPubKey,
});
const account = accounts.find(
(a) => a.asset.id === accountAssetId && a.type === accountType
(a) => a.asset.id === assetId && a.type === fromAccount
);
const accountBalance =
account && addDecimal(account.balance, account.asset.decimals);
// The general account of the selected pubkey. You can only transfer
// to general accounts, either when redeeming vested rewards or just
// during normal general -> general transfers
const toGeneralAccount =
toAccounts &&
toAccounts.find((a) => {
return (
a.asset.id === assetId && a.type === AccountType.ACCOUNT_TYPE_GENERAL
);
});
const [includeFee, setIncludeFee] = useState(false);
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
const min = asset
? new BigNumber(addDecimal('1', asset.decimals))
: new BigNumber(0);
// Max amount given selected asset and from account
const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0);
@@ -155,21 +164,16 @@ export const TransferForm = ({
const onSubmit = useCallback(
(fields: FormFields) => {
if (!transferAmount) {
throw new Error('Submitted transfer with no amount selected');
}
const [type, assetId] = parseFromAccount(fields.fromAccount);
const asset = assets.find((a) => a.id === assetId);
if (!asset) {
throw new Error('Submitted transfer with no asset selected');
}
if (!transferAmount) {
throw new Error('Submitted transfer with no amount selected');
}
const transfer = normalizeTransfer(
fields.toVegaKey,
transferAmount,
type,
fields.fromAccount,
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
{
id: asset.id,
@@ -178,7 +182,7 @@ export const TransferForm = ({
);
submitTransfer(transfer);
},
[submitTransfer, transferAmount, assets]
[asset, submitTransfer, transferAmount]
);
// reset for placeholder workaround https://github.com/radix-ui/primitives/issues/1569
@@ -194,10 +198,55 @@ export const TransferForm = ({
className="text-sm"
data-testid="transfer-form"
>
<TradingFormGroup label="To Vega key" labelFor="toVegaKey">
<AddressField
onChange={() => setValue('toVegaKey', '')}
select={
<TradingSelect {...register('toVegaKey')} id="toVegaKey">
<option value="" disabled={true}>
{t('Please select')}
</option>
{pubKeys?.map((pk) => {
const text = pk === pubKey ? t('Current key: ') + pk : pk;
return (
<option key={pk} value={pk}>
{text}
</option>
);
})}
</TradingSelect>
}
input={
<TradingInput
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true} // focus input immediately after is shown
id="toVegaKey"
type="text"
{...register('toVegaKey', {
validate: {
required,
vegaPublicKey,
},
})}
/>
}
/>
{errors.toVegaKey?.message && (
<TradingInputError forInput="toVegaKey">
{errors.toVegaKey.message}
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup label={t('Asset')} labelFor="asset">
<Controller
control={control}
name="asset"
rules={{
validate: {
required,
},
}}
render={({ field }) => (
<TradingRichSelect
data-testid="select-asset"
@@ -205,7 +254,6 @@ export const TransferForm = ({
name={field.name}
onValueChange={(value) => {
field.onChange(value);
setValue('fromAccount', '');
}}
placeholder={t('Please select an asset')}
value={field.value}
@@ -232,10 +280,10 @@ export const TransferForm = ({
)}
</TradingFormGroup>
<TradingFormGroup label={t('From account')} labelFor="fromAccount">
<Controller
control={control}
name="fromAccount"
rules={{
<TradingSelect
id="fromAccount"
defaultValue=""
{...register('fromAccount', {
validate: {
required,
sameAccount: (value) => {
@@ -250,106 +298,50 @@ export const TransferForm = ({
return true;
},
},
}}
render={({ field }) => (
<TradingSelect
id="fromAccount"
defaultValue=""
{...field}
onChange={(e) => {
field.onChange(e);
const [type] = parseFromAccount(e.target.value);
// Enforce that if transferring from a vested rewards account it must go to
// the current connected general account
if (
type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS &&
pubKey
) {
setValue('toVegaKey', pubKey);
setToVegaKeyMode('select');
setIncludeFee(false);
}
}}
>
<option value="" disabled={true}>
{t('Please select')}
</option>
{accounts
.filter((a) => {
if (!selectedAssetId) return true;
return selectedAssetId === a.asset.id;
})
.map((a) => {
const id = `${a.type}-${a.asset.id}`;
return (
<option value={id} key={id}>
{AccountTypeMapping[a.type]} (
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
{a.asset.symbol})
</option>
);
})}
</TradingSelect>
)}
/>
})}
>
<option value="" disabled={true}>
{t('Please select')}
</option>
{accounts
.filter((a) => {
if (!assetId) return true;
return assetId === a.asset.id;
})
.map((a) => {
return (
<option value={a.type} key={`${a.type}-${a.asset.id}`}>
{AccountTypeMapping[a.type]} (
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
{a.asset.symbol})
</option>
);
})}
</TradingSelect>
{errors.fromAccount?.message && (
<TradingInputError forInput="fromAccount">
{errors.fromAccount.message}
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup label="To Vega key" labelFor="toVegaKey">
<AddressField
onChange={() => {
setValue('toVegaKey', '');
setToVegaKeyMode((curr) => (curr === 'input' ? 'select' : 'input'));
}}
mode={toVegaKeyMode}
select={
<TradingSelect
{...register('toVegaKey')}
disabled={fromVested}
id="toVegaKey"
>
<option value="" disabled={true}>
{t('Please select')}
</option>
{pubKeys?.map((pk) => {
const text = pk === pubKey ? t('Current key: ') + pk : pk;
return (
<option key={pk} value={pk}>
{text}
</option>
);
})}
</TradingSelect>
}
input={
fromVested ? null : (
<TradingInput
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true} // focus input immediately after is shown
id="toVegaKey"
type="text"
disabled={fromVested}
{...register('toVegaKey', {
validate: {
required,
vegaPublicKey,
},
})}
/>
)
}
/>
{errors.toVegaKey?.message && (
<TradingInputError forInput="toVegaKey">
{errors.toVegaKey.message}
</TradingInputError>
)}
<TradingFormGroup label={t('To account')} labelFor="toAccount">
<TradingSelect
id="toAccount"
defaultValue={AccountType.ACCOUNT_TYPE_GENERAL}
>
<option value={AccountType.ACCOUNT_TYPE_GENERAL}>
{toGeneralAccount
? `${
AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]
} (${addDecimalsFormatNumber(
toGeneralAccount.balance,
toGeneralAccount.asset.decimals
)} ${toGeneralAccount.asset.symbol})`
: `${AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]} ${
asset ? `(0 ${asset.symbol})` : ''
}`}
</option>
</TradingSelect>
</TradingFormGroup>
<TradingFormGroup label="Amount" labelFor="amount">
<TradingInput
@@ -361,43 +353,7 @@ export const TransferForm = ({
{...register('amount', {
validate: {
required,
minSafe: (v) => {
if (!asset || !minQuantumMultiple) return true;
const value = new BigNumber(v);
if (value.isZero()) {
return t('Amount cannot be 0');
}
const minByQuantumMultiple = toBigNum(
minQuantumMultiple,
asset.decimals
);
if (fromVested) {
// special conditions which let you bypass min transfer rules set by quantum multiple
if (value.isGreaterThanOrEqualTo(max)) {
return true;
}
if (value.isLessThan(minByQuantumMultiple)) {
return t(
'Amount below minimum requirements for partial transfer. Use max to bypass'
);
}
return true;
} else {
if (value.isLessThan(minByQuantumMultiple)) {
return t(
'Amount below minimum requirement set by transfer.minTransferQuantumMultiple'
);
}
}
return true;
},
minSafe: (value) => minSafe(new BigNumber(min))(value),
maxSafe: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(max)) {
@@ -413,9 +369,7 @@ export const TransferForm = ({
type="button"
className="absolute top-0 right-0 ml-auto text-xs underline"
onClick={() =>
setValue('amount', parseFloat(accountBalance).toString(), {
shouldValidate: true,
})
setValue('amount', parseFloat(accountBalance).toString())
}
>
{t('Use max')}
@@ -436,10 +390,10 @@ export const TransferForm = ({
<div>
<TradingCheckbox
name="include-transfer-fee"
disabled={!transferAmount || fromVested}
disabled={!transferAmount}
label={t('Include transfer fee')}
checked={includeFee}
onCheckedChange={() => setIncludeFee((x) => !x)}
onCheckedChange={() => setIncludeFee(!includeFee)}
/>
</div>
</Tooltip>
@@ -449,7 +403,7 @@ export const TransferForm = ({
amount={transferAmount}
transferAmount={transferAmount}
feeFactor={feeFactor}
fee={fromVested ? '0' : fee}
fee={fee}
decimals={asset?.decimals}
/>
)}
@@ -531,38 +485,32 @@ export const TransferFee = ({
);
};
type ToVegaKeyMode = 'input' | 'select';
interface AddressInputProps {
select: ReactNode;
input: ReactNode;
mode: ToVegaKeyMode;
onChange: () => void;
}
export const AddressField = ({
select,
input,
mode,
onChange,
}: AddressInputProps) => {
const isInput = mode === 'input';
const [isInput, setIsInput] = useState(false);
return (
<>
{isInput ? input : select}
{select && input && (
<button
type="button"
onClick={onChange}
className="absolute top-0 right-0 ml-auto text-xs underline"
>
{isInput ? t('Select from wallet') : t('Enter manually')}
</button>
)}
<button
type="button"
onClick={() => {
setIsInput((curr) => !curr);
onChange();
}}
className="absolute top-0 right-0 ml-auto text-xs underline"
>
{isInput ? t('Select from wallet') : t('Enter manually')}
</button>
</>
);
};
const parseFromAccount = (fromAccountStr: string) => {
return fromAccountStr.split('-') as [AccountType, string];
};
@@ -72,11 +72,15 @@ export const DealTicketFeeDetails = ({
return (
<KeyValue
label={
label={t('Fees')}
value={
totalDiscountedFeeAmount &&
`~${formatValue(totalDiscountedFeeAmount, assetDecimals)}`
}
formattedValue={
<>
{t('Fees')}
{totalDiscountFactor ? (
<Pill size="xxs" intent={Intent.Info} className="ml-1">
<Pill size="xxs" intent={Intent.Warning} className="mr-1">
-
{formatNumberPercentage(
new BigNumber(totalDiscountFactor).multipliedBy(100),
@@ -84,16 +88,10 @@ export const DealTicketFeeDetails = ({
)}
</Pill>
) : null}
{totalDiscountedFeeAmount &&
`~${formatValue(totalDiscountedFeeAmount, assetDecimals, quantum)}`}
</>
}
value={
totalDiscountedFeeAmount &&
`~${formatValue(totalDiscountedFeeAmount, assetDecimals)}`
}
formattedValue={
totalDiscountedFeeAmount &&
`~${formatValue(totalDiscountedFeeAmount, assetDecimals, quantum)}`
}
labelDescription={
<div className="flex flex-col gap-2">
<p>
@@ -79,11 +79,7 @@ export const FeesBreakdown = ({
volumeDiscountFactor
);
const {
discountedFee: discountedTotalFeeAmount,
volumeDiscount,
referralDiscount,
} = getDiscountedFee(
const { volumeDiscount, referralDiscount } = getDiscountedFee(
totalFeeAmount,
referralDiscountFactor,
volumeDiscountFactor
@@ -135,7 +131,7 @@ export const FeesBreakdown = ({
<FeesBreakdownItem
label={t('Total fees')}
factor={feeFactors ? sumFeesFactors(feeFactors)?.toString() : undefined}
value={discountedTotalFeeAmount}
value={totalFeeAmount}
symbol={symbol}
decimals={decimals}
/>
+9 -22
View File
@@ -289,30 +289,17 @@ describe('FeesDiscountBreakdownTooltip', () => {
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />);
const dt = container.querySelectorAll('dt');
const dd = container.querySelectorAll('dd');
const expectedDt = [
'Infrastructure Fee',
'Referral Discount',
'Volume Discount',
'Liquidity Fee',
'Referral Discount',
'Volume Discount',
'Maker Fee',
'Referral Discount',
'Volume Discount',
const expected = [
{ label: 'Infrastructure Fee Referral Discount', value: '0.05 BTC' },
{ label: 'Infrastructure Fee Volume Discount', value: '0.06 BTC' },
{ label: 'Liquidity Fee Referral Discount', value: '0.01 BTC' },
{ label: 'Liquidity Fee Volume Discount', value: '0.02 BTC' },
{ label: 'Maker Fee Referral Discount', value: '0.03 BTC' },
{ label: 'Maker Fee Volume Discount', value: '0.04 BTC' },
];
const expectedDD = [
'0.05 BTC',
'0.06 BTC',
'0.01 BTC',
'0.02 BTC',
'0.03 BTC',
'0.04 BTC',
];
expectedDt.forEach((label, i) => {
expected.forEach(({ label, value }, i) => {
expect(dt[i]).toHaveTextContent(label);
});
expectedDD.forEach((label, i) => {
expect(dd[i]).toHaveTextContent(label);
expect(dd[i]).toHaveTextContent(value);
});
});
});
+7 -20
View File
@@ -391,7 +391,7 @@ const FeesDiscountBreakdownTooltipItem = ({
label: string;
asset: ReturnType<typeof getAsset>;
}) =>
value && value !== '0' ? (
value ? (
<>
<dt className="col-span-1">{label}</dt>
<dd className="text-right col-span-1">
@@ -418,47 +418,34 @@ export const FeesDiscountBreakdownTooltip = ({
className="max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white"
>
<dl className="grid grid-cols-2 gap-x-1">
{(fees.infrastructureFeeReferralDiscount || '0') !== '0' ||
(fees.infrastructureFeeVolumeDiscount || '0') !== '0' ? (
<dt className="col-span-2">{t('Infrastructure Fee')}</dt>
) : null}
<FeesDiscountBreakdownTooltipItem
value={fees.infrastructureFeeReferralDiscount}
label={t('Referral Discount')}
label={t('Infrastructure Fee Referral Discount')}
asset={asset}
/>
<FeesDiscountBreakdownTooltipItem
value={fees.infrastructureFeeVolumeDiscount}
label={t('Volume Discount')}
label={t('Infrastructure Fee Volume Discount')}
asset={asset}
/>
{(fees.liquidityFeeReferralDiscount || '0') !== '0' ||
(fees.liquidityFeeVolumeDiscount || '0') !== '0' ? (
<dt className="col-span-2">{t('Liquidity Fee')}</dt>
) : null}
<FeesDiscountBreakdownTooltipItem
value={fees.liquidityFeeReferralDiscount}
label={t('Referral Discount')}
label={t('Liquidity Fee Referral Discount')}
asset={asset}
/>
<FeesDiscountBreakdownTooltipItem
value={fees.liquidityFeeVolumeDiscount}
label={t('Volume Discount')}
label={t('Liquidity Fee Volume Discount')}
asset={asset}
/>
{(fees.makerFeeReferralDiscount || '0') !== '0' ||
(fees.makerFeeVolumeDiscount || '0') !== '0' ? (
<dt className="col-span-2">{t('Maker Fee')}</dt>
) : null}
<FeesDiscountBreakdownTooltipItem
value={fees.makerFeeReferralDiscount}
label={t('Referral Discount')}
label={t('Maker Fee Referral Discount')}
asset={asset}
/>
<FeesDiscountBreakdownTooltipItem
value={fees.makerFeeVolumeDiscount}
label={t('Volume Discount')}
label={t('Maker Fee Volume Discount')}
asset={asset}
/>
</dl>
+5 -10
View File
@@ -2,6 +2,7 @@ import { useMemo } from 'react';
import {
addDecimalsFormatNumber,
addDecimalsFormatNumberQuantum,
formatNumberPercentage,
getDateTimeFormat,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
@@ -28,12 +29,6 @@ import { LiquidityProvisionStatus } from '@vegaprotocol/types';
import { LiquidityProvisionStatusMapping } from '@vegaprotocol/types';
import type { LiquidityProvisionData } from './liquidity-data-provider';
const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
const decimalPlaces =
typeof decimals === 'undefined' ? value.dp() || 0 : decimals;
return `${value.toFixed(decimalPlaces, 1)}%`;
};
const percentageFormatter = ({ value }: ValueFormatterParams) => {
if (!value) return '-';
return formatNumberPercentage(new BigNumber(value).times(100), 2) || '-';
@@ -131,11 +126,11 @@ export const LiquidityTable = ({
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).times(
100
),
4
2
),
formatNumberPercentage(
new BigNumber(data.commitmentMinTimeFraction).times(100),
4
2
),
]
);
@@ -148,7 +143,7 @@ export const LiquidityTable = ({
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).times(
100
),
4
2
),
]
);
@@ -399,7 +394,7 @@ export const LiquidityTable = ({
headerTooltip: t(
`The liquidity fees accrued by each provider, which will be distributed at the end of the epoch after applying any penalties.`
),
valueFormatter: assetDecimalsQuantumFormatter,
valueFormatter: stakeToCcyVolumeQuantumFormatter,
tooltipValueGetter: feesAccruedTooltip,
cellClassRules: {
'text-warning': ({ data }: { data: LiquidityProvisionData }) => {
+1 -1
View File
@@ -6,4 +6,4 @@ export * from './markets-candles';
export * from './markets-data';
export * from './OracleMarketsSpec';
export * from './OracleSpecDataConnection';
export * from './SuccessorMarket'
export * from './SuccessorMarket'
@@ -1,3 +1,12 @@
fragment MarketCandlesFields on Candle {
high
low
open
close
volume
periodStart
}
query MarketsCandles($interval: Interval!, $since: String!) {
marketsConnection {
edges {
@@ -176,7 +176,6 @@ export const NetworkParams = {
market_liquidity_feeCalculationTimeStep:
'market_liquidity_feeCalculationTimeStep',
transfer_fee_factor: 'transfer_fee_factor',
transfer_minTransferQuantumMultiple: 'transfer_minTransferQuantumMultiple',
network_validators_incumbentBonus: 'network_validators_incumbentBonus',
} as const;
@@ -85,6 +85,7 @@ describe('ProposalsList', () => {
'Settlement asset',
'State',
'Parent market',
'Voting',
'Closing date',
'Enactment date',
'', // actions col
@@ -1,6 +1,8 @@
import { useMemo } from 'react';
import BigNumber from 'bignumber.js';
import type { ColDef } from 'ag-grid-community';
import {
CenteredGridCellWrapper,
COL_DEFS,
DateRangeFilter,
SetFilter,
@@ -9,18 +11,33 @@ import {
import compact from 'lodash/compact';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import {
ProposalProductTypeShortName,
ProductTypeMapping,
ProductTypeShortName,
ProposalStateMapping,
} from '@vegaprotocol/types';
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
import { VoteProgress } from '../voting-progress';
import { ProposalActionsDropdown } from '../proposal-actions-dropdown';
export const useColumnDefs = () => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
]);
const requiredMajorityPercentage = useMemo(() => {
const requiredMajority =
params?.governance_proposal_market_requiredMajority ?? 1;
return new BigNumber(requiredMajority).times(100);
}, [params?.governance_proposal_market_requiredMajority]);
const columnDefs: ColDef[] = useMemo(() => {
return compact([
{
@@ -37,38 +54,27 @@ export const useColumnDefs = () => {
}) => {
if (!value || !data) return '-';
const getProductType = (data: ProposalListFieldsFragment) => {
if (
data.terms.__typename === 'ProposalTerms' &&
data.terms.change.__typename === 'NewMarket'
) {
return data.terms.change.instrument.product?.__typename;
}
return undefined;
};
const productType = getProductType(data);
// TODO: update when we switch to ProductConfiguration
const productType = 'Future';
return (
productType && (
<StackedCell
primary={value}
secondary={
<span
title={ProposalProductTypeShortName[productType]}
className="uppercase"
>
{ProposalProductTypeShortName[productType]}
</span>
}
/>
)
<StackedCell
primary={value}
secondary={
<span
title={ProductTypeMapping[productType]}
className="uppercase"
>
{ProductTypeShortName[productType]}
</span>
}
/>
);
},
},
{
colId: 'asset',
headerName: t('Settlement asset'),
field: 'terms.change.instrument.product.settlementAsset.symbol',
field: 'terms.change.instrument.futureProduct.settlementAsset.symbol',
},
{
colId: 'state',
@@ -88,6 +94,32 @@ export const useColumnDefs = () => {
field: 'terms.change.successorConfiguration.parentMarketId',
cellRenderer: 'ParentMarketCell',
},
{
colId: 'voting',
headerName: t('Voting'),
cellRenderer: ({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (data) {
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
const noTokens = new BigNumber(data.votes.no.totalTokens);
const totalTokensVoted = yesTokens.plus(noTokens);
const yesPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<CenteredGridCellWrapper>
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
/>
</CenteredGridCellWrapper>
);
}
return '-';
},
filter: false,
},
{
colId: 'closing-date',
headerName: t('Closing date'),
@@ -124,7 +156,7 @@ export const useColumnDefs = () => {
},
},
]);
}, []);
}, [requiredMajorityPercentage]);
return columnDefs;
};
@@ -16,90 +16,78 @@ fragment NewMarketFields on NewMarket {
instrument {
name
code
product {
... on FutureProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
futureProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
dataSourceSpecForTradingTermination {
sourceType {
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
}
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
}
... on PerpetualProduct {
settlementAsset {
id
name
symbol
decimals
quantum
dataSourceSpecForTradingTermination {
sourceType {
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
quoteName
}
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
}
}
File diff suppressed because one or more lines are too long
@@ -128,7 +128,7 @@ export const createProposalListFieldsFragment = (
instrument: {
code: 'ETHUSD',
name: 'ETHUSD',
product: {
futureProduct: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -262,7 +262,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'ETHUSD',
name: 'ETHUSD',
product: {
futureProduct: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -352,7 +352,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'LINKUSD',
name: 'LINKUSD',
product: {
futureProduct: {
settlementAsset: {
id: 'eb30d55e90e1f9e5c4727d6fa2a5a8cd36ab9ae9738eb8f3faf53e2bee4861ee',
name: 'mUSDT-II',
@@ -442,7 +442,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'LINKUSD',
name: 'LINKUSD',
product: {
futureProduct: {
settlementAsset: {
id: 'eb30d55e90e1f9e5c4727d6fa2a5a8cd36ab9ae9738eb8f3faf53e2bee4861ee',
name: 'mUSDT-II',
@@ -532,7 +532,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'ETHUSD',
name: 'ETHUSD',
product: {
futureProduct: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -622,7 +622,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'LINKUSD',
name: 'LINKUSD',
product: {
futureProduct: {
settlementAsset: {
id: 'eb30d55e90e1f9e5c4727d6fa2a5a8cd36ab9ae9738eb8f3faf53e2bee4861ee',
name: 'mUSDT-II',
@@ -712,7 +712,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'ETHDAI.MF21',
name: 'ETHDAI Monthly (Dec 2022)',
product: {
futureProduct: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -802,7 +802,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'AAPL.MF21',
name: 'Apple Monthly (Dec 2022)',
product: {
futureProduct: {
settlementAsset: {
id: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
name: 'tUSDC TEST',
@@ -892,7 +892,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'BTCUSD.MF21',
name: 'BTCUSD Monthly (Dec 2022)',
product: {
futureProduct: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -982,7 +982,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'TSLA.QM21',
name: 'Tesla Quarterly (Feb 2023)',
product: {
futureProduct: {
settlementAsset: {
id: '177e8f6c25a955bd18475084b99b2b1d37f28f3dec393fab7755a7e69c3d8c3b',
name: 'tEURO TEST',
@@ -1072,7 +1072,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'AAVEDAI.MF21',
name: 'AAVEDAI Monthly (Dec 2022)',
product: {
futureProduct: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -1162,7 +1162,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'ETHBTC.QM21',
name: 'ETHBTC Quarterly (Feb 2023)',
product: {
futureProduct: {
settlementAsset: {
id: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
name: 'tBTC TEST',
@@ -1252,7 +1252,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'UNIDAI.MF21',
name: 'UNIDAI Monthly (Dec 2022)',
product: {
futureProduct: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
+1 -1
View File
@@ -71,7 +71,7 @@
"jsondiffpatch": "^0.4.1",
"lodash": "^4.17.21",
"next": "13.3.0",
"pennant": "^1.14.1",
"pennant": "1.14.0",
"react": "18.2.0",
"react-copy-to-clipboard": "^5.0.4",
"react-dom": "18.2.0",
+4 -4
View File
@@ -20500,10 +20500,10 @@ pend@~1.2.0:
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
pennant@^1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.14.1.tgz#8e7a53256095e398b03397af31c831b02a56032b"
integrity sha512-rjzo/tlFanO96OKhJiyjQtjug7sY6pjVZqVbieD3Tf4zt20bqIb6m4L2JfwOXEe0jqP/yddgCNPY+vlkVuJW1Q==
pennant@1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.14.0.tgz#4100c25a6d836d6f0ff425181fb6f812f9fe5778"
integrity sha512-9H0zWzFUSbD1BlDXnHFmKwkAxXGb1xTxjkUD+RwaMygtSwPXzQEyk2ScVyMqxdcz0RuJmI5HCVmZTOjdr1NwuA==
dependencies:
"@babel/runtime" "^7.13.10"
"@d3fc/d3fc-technical-indicator" "^8.0.1"