Compare commits

..
Author SHA1 Message Date
Matthew Russell a87a4a1af5 chore: formatting 2023-11-19 13:30:26 -08:00
Bartłomiej Głownia ddc62e6913 feat(utils): use i18next 2023-11-19 13:20:37 -08:00
Bartłomiej Głownia 19b95832c4 feat(market-depth): use i18next (#5267) 2023-11-19 13:13:03 -08:00
Bartłomiej GłowniaandMatthew Russell 48e4ab53a1 feat(orders): use i18next (#5263)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-11-19 13:12:35 -08:00
Bartłomiej Głownia cefcff040f feat(positions): use i18next (#5266) 2023-11-17 19:49:19 -08:00
Bartłomiej Głownia c3ef639daf feat(web3): use i18next (#5256) 2023-11-17 16:44:30 -08:00
Bartłomiej Głownia b6052bc3e5 feat(markets): use i18next (#5255) 2023-11-17 12:01:18 -08:00
Bartłomiej Głownia 6e8894936e feat(ledger): use i18next (#5248) 2023-11-17 11:55:10 -08:00
Bartłomiej Głownia 66f55855ae feat(liquidity): use i18next (#5247) 2023-11-17 11:54:17 -08:00
Matthew Russell 5725b824ad Merge pull request #5309 from vegaprotocol/chore/sync-main
chore(governance,trading): sync with mainnet fixes
2023-11-17 11:51:03 -08:00
Matthew Russell 9344a2abc0 fix: no t function 2023-11-17 11:10:18 -08:00
Matthew Russell fae66ca4ce Merge branch 'main' into chore/sync-main 2023-11-17 10:56:19 -08:00
Matthew Russell 4b7338ed94 chore(deal-ticket,markets): dont show margin estimate for perps (#5305) 2023-11-17 10:33:24 -08:00
Edd 2983c6c6ba feat(governance): enable volume discount update view (#5304) (#5307) 2023-11-17 10:25:30 -08:00
Edd 31524ac7a7 feat(governance): enable volume discount update view (#5304) 2023-11-17 17:29:42 +00:00
Matthew Russellandasiaznik 70f42c1c7e fix(trading): fees page fixes for mainnet (#5303)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
2023-11-17 08:53:21 -08:00
m.ray 2304ce9763 fix(proposals): perpetual proposed markets (#5290) 2023-11-17 12:00:19 +00:00
m.ray 79a16b8562 fix(trading): show deal ticket in sidebar by default (#5292) 2023-11-16 09:08:20 -08:00
Matthew Russell 5661b74082 fix(trading): do not use stakeToCcyVolume to format fees value (#5280) 2023-11-16 09:34:38 +00:00
m.ray 22703d937e fix(trading): fix percentage formatter rounding in liquidity table (#5250) 2023-11-13 16:14:18 +00:00
101 changed files with 2112 additions and 1272 deletions
@@ -133,7 +133,7 @@ export const proposalsData = {
instrument: {
name: 'UNIDAI Monthly (Dec 2022)',
code: 'UNIDAI.MF21',
futureProduct: {
product: {
settlementAsset: { symbol: 'tDAI', __typename: 'Asset' },
__typename: 'FutureProduct',
},
@@ -240,7 +240,7 @@ export const proposalsData = {
instrument: {
name: 'ETHBTC Quarterly (Feb 2023)',
code: 'ETHBTC.QM21',
futureProduct: {
product: {
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
__typename: 'FutureProduct',
},
@@ -77,7 +77,7 @@ describe('Proposal header', () => {
__typename: 'InstrumentConfiguration',
name: 'Some market',
code: 'FX:BTCUSD/DEC99',
futureProduct: {
product: {
__typename: 'FutureProduct',
settlementAsset: {
__typename: 'Asset',
@@ -39,6 +39,18 @@ 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 =
@@ -54,10 +66,10 @@ export const ProposalHeader = ({
<span>
{t('Code')}: {change.instrument.code}.
</span>{' '}
{change.instrument.futureProduct?.settlementAsset.symbol ? (
{proposal?.terms && getAsset(proposal)?.symbol ? (
<>
<span className="font-semibold">
{change.instrument.futureProduct.settlementAsset.symbol}
{getAsset(proposal)?.symbol}
</span>{' '}
{t('settled future')}.
</>
@@ -130,46 +130,25 @@ query Proposal(
instrument {
name
code
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
}
product {
... on FutureProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
operator
value
@@ -177,52 +156,44 @@ query Proposal(
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
key {
name
type
}
conditions {
operator
value
}
}
}
}
}
}
}
}
# 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
... on PerpetualProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
}
}
}
File diff suppressed because one or more lines are too long
@@ -101,9 +101,16 @@ fragment ProposalFields on Proposal {
instrument {
name
code
futureProduct {
settlementAsset {
symbol
product {
... on FutureProduct {
settlementAsset {
symbol
}
}
... on PerpetualProduct {
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, 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 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 ProposalsQueryVariables = Types.Exact<{
includeNewMarketProductFields: Types.Scalars['Boolean'];
@@ -20,7 +20,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, 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 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 const NewMarketProductFieldsFragmentDoc = gql`
fragment NewMarketProductFields on Proposal {
@@ -130,9 +130,16 @@ export const ProposalFieldsFragmentDoc = gql`
instrument {
name
code
futureProduct {
settlementAsset {
symbol
product {
... on FutureProduct {
settlementAsset {
symbol
}
}
... on PerpetualProduct {
settlementAsset {
symbol
}
}
}
}
@@ -8,7 +8,7 @@ import {
doesValueEquateToParam,
} from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils';
import { useValidateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -41,6 +41,7 @@ export interface NewAssetProposalFormFields {
const DOCS_LINK = '/new-asset-proposal';
export const ProposeNewAsset = () => {
const validateJson = useValidateJson();
const {
params,
loading: networkParamsLoading,
@@ -7,7 +7,7 @@ import {
doesValueEquateToParam,
} from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils';
import { useValidateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -39,6 +39,7 @@ export interface NewMarketProposalFormFields {
const DOCS_LINK = '/new-market-proposal';
export const ProposeNewMarket = () => {
const validateJson = useValidateJson();
const {
params,
loading: networkParamsLoading,
@@ -14,7 +14,7 @@ import {
RoundedWrapper,
TextArea,
} from '@vegaprotocol/ui-toolkit';
import { validateJson } from '@vegaprotocol/utils';
import { useValidateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -31,6 +31,7 @@ export interface RawProposalFormFields {
}
export const ProposeRaw = () => {
const validateJson = useValidateJson();
const {
params,
loading: networkParamsLoading,
@@ -7,7 +7,7 @@ import {
doesValueEquateToParam,
} from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils';
import { useValidateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -39,6 +39,7 @@ export interface UpdateAssetProposalFormFields {
const DOCS_LINK = '/update-asset-proposal';
export const ProposeUpdateAsset = () => {
const validateJson = useValidateJson();
const {
params,
loading: networkParamsLoading,
@@ -8,7 +8,7 @@ import {
useProposalSubmit,
} from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils';
import { useValidateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -53,6 +53,7 @@ export interface UpdateMarketProposalFormFields {
const DOCS_LINK = '/update-market-proposal';
export const ProposeUpdateMarket = () => {
const validateJson = useValidateJson();
const {
params,
loading: networkParamsLoading,
@@ -260,7 +261,7 @@ export const ProposeUpdateMarket = () => {
</FormGroup>
{selectedMarket && (
<div className="mt-[-20px] mb-6">
<div className="mb-6 mt-[-20px]">
<KeyValueTable data-testid="update-market-details">
<KeyValueTableRow>
{t('MarketName')}
@@ -1,6 +1,4 @@
import { Route, Routes, useParams } from 'react-router-dom';
import { MarketState } from '@vegaprotocol/types';
import { useMarket } from '@vegaprotocol/markets';
import { Route, Routes } from 'react-router-dom';
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
import {
SidebarButton,
@@ -12,12 +10,7 @@ import { useT } from '../../lib/use-t';
export const MarketsSidebar = () => {
const t = useT();
const { marketId } = useParams();
const currentRouteId = useGetCurrentRouteId();
const { data } = useMarket(marketId);
const active =
data &&
[MarketState.STATE_ACTIVE, MarketState.STATE_PENDING].includes(data.state);
return (
<>
@@ -45,14 +38,12 @@ export const MarketsSidebar = () => {
element={
<>
<SidebarDivider />
{active && (
<SidebarButton
view={ViewType.Order}
icon={VegaIconNames.TICKET}
tooltip={t('Order')}
routeId={currentRouteId}
/>
)}
<SidebarButton
view={ViewType.Order}
icon={VegaIconNames.TICKET}
tooltip={t('Order')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Info}
icon={VegaIconNames.BREAKDOWN}
@@ -1,6 +1,6 @@
import {
getAsset,
tooltipMapping,
useTooltipMapping,
useMarket,
useStaticMarketData,
} from '@vegaprotocol/markets';
@@ -27,6 +27,7 @@ import { useT } from '../../lib/use-t';
export const LiquidityHeader = () => {
const t = useT();
const tooltipMapping = useTooltipMapping();
const { marketId } = useParams();
const { data: market } = useMarket(marketId);
const { data: marketData } = useStaticMarketData(marketId);
@@ -122,7 +123,7 @@ export const LiquidityHeader = () => {
<CopyWithTooltip text={marketId}>
<button
data-testid="copy-eth-oracle-address"
className="uppercase text-right"
className="text-right uppercase"
>
<span className="flex gap-1">
{truncateMiddle(marketId)}
+9 -6
View File
@@ -1,8 +1,8 @@
import sortBy from 'lodash/sortBy';
import {
maxSafe,
required,
vegaPublicKey,
useMaxSafe,
useRequired,
useVegaPublicKey,
addDecimal,
formatNumber,
addDecimalsFormatNumber,
@@ -67,6 +67,9 @@ export const TransferForm = ({
minQuantumMultiple,
}: TransferFormProps) => {
const t = useT();
const maxSafe = useMaxSafe();
const required = useRequired();
const vegaPublicKey = useVegaPublicKey();
const {
control,
register,
@@ -415,7 +418,7 @@ export const TransferForm = ({
{accountBalance && (
<button
type="button"
className="absolute top-0 right-0 ml-auto text-xs underline"
className="absolute right-0 top-0 ml-auto text-xs underline"
onClick={() =>
setValue('amount', parseFloat(accountBalance).toString(), {
shouldValidate: true,
@@ -491,7 +494,7 @@ export const TransferFee = ({
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
return (
<div className="flex flex-col mb-4 text-xs gap-2">
<div className="mb-4 flex flex-col gap-2 text-xs">
<div className="flex flex-wrap items-center justify-between gap-1">
<Tooltip
description={t(
@@ -560,7 +563,7 @@ export const AddressField = ({
<button
type="button"
onClick={onChange}
className="absolute top-0 right-0 ml-auto text-xs underline"
className="absolute right-0 top-0 ml-auto text-xs underline"
>
{isInput ? t('Select from wallet') : t('Enter manually')}
</button>
@@ -1,5 +1,5 @@
import { useCallback, useState } from 'react';
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
import { getAsset, getProductType, getQuoteName } from '@vegaprotocol/markets';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
@@ -297,120 +297,134 @@ export const DealTicketMarginDetails = ({
);
const quoteName = getQuoteName(market);
const productType = getProductType(market);
return (
<div className="flex flex-col w-full gap-2">
<Accordion>
<AccordionPanel
itemId="margin"
trigger={
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={classNames(
'w-full pt-2',
'flex items-center gap-2 text-xs',
'group'
)}
>
<div
data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'}
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip
description={t(
'MARGIN_DIFF_TOOLTIP_TEXT',
MARGIN_DIFF_TOOLTIP_TEXT,
{ assetSymbol }
)}
>
<span className="text-muted">{t('Margin required')}</span>
</Tooltip>
<div className="flex flex-col w-full gap-2 pt-2">
{/*
TODO: remove this conditional check once the following PRs are deployed
and the estimatePosition query is working for perps
<AccordionChevron size={10} />
</div>
<Tooltip
description={
formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
) ?? '-'
}
- https://github.com/vegaprotocol/vega/pull/10119
- https://github.com/vegaprotocol/vega/pull/10122
*/}
{productType === 'Future' && (
<>
<Accordion>
<AccordionPanel
itemId="margin"
trigger={
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={classNames(
'w-full',
'flex items-center gap-2 text-xs',
'group'
)}
>
<div className="font-mono text-right">
{formatValue(
marginRequiredWorstCase,
assetDecimals,
quantum
)}{' '}
{assetSymbol || ''}
<div
data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'}
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip
description={t(
'MARGIN_DIFF_TOOLTIP_TEXT',
MARGIN_DIFF_TOOLTIP_TEXT,
{ assetSymbol }
)}
>
<span className="text-muted">
{t('Margin required')}
</span>
</Tooltip>
<AccordionChevron size={10} />
</div>
<Tooltip
description={
formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
) ?? '-'
}
>
<div className="font-mono text-right">
{formatValue(
marginRequiredWorstCase,
assetDecimals,
quantum
)}{' '}
{assetSymbol || ''}
</div>
</Tooltip>
</div>
</Tooltip>
</div>
</AccordionPrimitive.Trigger>
}
>
<div className="flex flex-col w-full gap-2">
<KeyValue
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={t(
'TOTAL_MARGIN_AVAILABLE',
TOTAL_MARGIN_AVAILABLE,
{
generalAccountBalance: formatValue(
generalAccountBalance,
</AccordionPrimitive.Trigger>
}
>
<div className="flex flex-col w-full gap-2">
<KeyValue
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
assetDecimals,
quantum
),
marginAccountBalance: formatValue(
)}
symbol={assetSymbol}
labelDescription={t(
'TOTAL_MARGIN_AVAILABLE',
TOTAL_MARGIN_AVAILABLE,
{
generalAccountBalance: formatValue(
generalAccountBalance,
assetDecimals,
quantum
),
marginAccountBalance: formatValue(
marginAccountBalance,
assetDecimals,
quantum
),
marginMaintenance: formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
quantum
),
assetSymbol,
}
)}
/>
{deductionFromCollateral}
<KeyValue
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={t(
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
MARGIN_ACCOUNT_TOOLTIP_TEXT
)}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
quantum
),
marginMaintenance: formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
quantum
),
assetSymbol,
}
)}
/>
{deductionFromCollateral}
<KeyValue
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={t(
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
MARGIN_ACCOUNT_TOOLTIP_TEXT
)}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
quantum
)}
/>
</div>
</AccordionPanel>
</Accordion>
{projectedMargin}
)}
/>
</div>
</AccordionPanel>
</Accordion>
{projectedMargin}
</>
)}
<KeyValue
label={t('Liquidation')}
value={liquidationPriceEstimateRange}
@@ -1,7 +1,7 @@
import { Controller, type Control } from 'react-hook-form';
import type { Market } from '@vegaprotocol/markets';
import type { OrderFormValues } from '../../hooks/use-form-values';
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
import { toDecimal, useValidateAmount } from '@vegaprotocol/utils';
import {
TradingFormGroup,
TradingInput,
@@ -28,6 +28,7 @@ export const DealTicketSizeIceberg = ({
peakSize,
}: DealTicketSizeIcebergProps) => {
const t = useT();
const validateAmount = useValidateAmount();
const sizeStep = toDecimal(market?.positionDecimalPlaces);
const renderPeakSizeError = () => {
@@ -9,7 +9,7 @@ import {
formatValue,
removeDecimal,
toDecimal,
validateAmount,
useValidateAmount,
} from '@vegaprotocol/utils';
import { type Control, type UseFormWatch } from 'react-hook-form';
import { useForm, Controller, useController } from 'react-hook-form';
@@ -36,7 +36,6 @@ import {
} from '@vegaprotocol/markets';
import { ExpirySelector } from './expiry-selector';
import { SideSelector } from './side-selector';
import { timeInForceLabel } from '@vegaprotocol/orders';
import {
NoWalletWarning,
REDUCE_ONLY_TOOLTIP,
@@ -110,6 +109,7 @@ const Trigger = ({
decimalPlaces: number;
}) => {
const t = useT();
const validateAmount = useValidateAmount();
const triggerType = watch(oco ? 'ocoTriggerType' : 'triggerType');
const triggerDirection = watch('triggerDirection');
const isPriceTrigger = triggerType === 'price';
@@ -342,6 +342,7 @@ const Size = ({
assetUnit?: string;
}) => {
const t = useT();
const validateAmount = useValidateAmount();
return (
<Controller
name={oco ? 'ocoSize' : 'size'}
@@ -402,6 +403,7 @@ const Price = ({
oco?: boolean;
}) => {
const t = useT();
const validateAmount = useValidateAmount();
if (watch(oco ? 'ocoType' : 'type') === Schema.OrderType.TYPE_MARKET) {
return null;
}
@@ -479,13 +481,13 @@ const TimeInForce = ({
key={Schema.OrderTimeInForce.TIME_IN_FORCE_IOC}
value={Schema.OrderTimeInForce.TIME_IN_FORCE_IOC}
>
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_IOC)}
{t(Schema.OrderTimeInForce.TIME_IN_FORCE_IOC)}
</option>
<option
key={Schema.OrderTimeInForce.TIME_IN_FORCE_FOK}
value={Schema.OrderTimeInForce.TIME_IN_FORCE_FOK}
>
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_FOK)}
{t(Schema.OrderTimeInForce.TIME_IN_FORCE_FOK)}
</option>
</Select>
</FormGroup>
@@ -1181,7 +1183,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
testId={'stop-order-warning-limit'}
message={t(
'There is a limit of {{maxNumberOfOrders}} active stop orders per market. Orders submitted above the limit will be immediately rejected.',
{ maxNumberOfOrders: MAX_NUMBER_OF_ACTIVE_STOP_ORDERS.toString() }
{
maxNumberOfOrders: MAX_NUMBER_OF_ACTIVE_STOP_ORDERS.toString(),
}
)}
/>
</div>
@@ -495,12 +495,15 @@ describe('DealTicket', () => {
Array.from(screen.getByTestId('order-tif').children).map(
(o) => o.textContent
)
).toEqual(['Fill or Kill (FOK)', 'Immediate or Cancel (IOC)']);
).toEqual([
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
]);
// IOC should be default
// 7002-SORD-030
expect(screen.getByTestId('order-tif')).toHaveDisplayValue(
'Immediate or Cancel (IOC)'
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
// Select FOK - FOK should be selected
@@ -509,7 +512,7 @@ describe('DealTicket', () => {
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK
);
expect(screen.getByTestId('order-tif')).toHaveDisplayValue(
'Fill or Kill (FOK)'
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK
);
// Switch to type limit order -> all TIF options should be shown
@@ -28,7 +28,7 @@ import { useOpenVolume } from '@vegaprotocol/positions';
import {
toBigNum,
removeDecimal,
validateAmount,
useValidateAmount,
toDecimal,
formatForInput,
formatValue,
@@ -140,6 +140,7 @@ export const DealTicket = ({
onDeposit,
}: DealTicketProps) => {
const t = useT();
const validateAmount = useValidateAmount();
const { pubKey, isReadOnly } = useVegaWallet();
const setType = useDealTicketFormValues((state) => state.setType);
const storedFormValues = useDealTicketFormValues(
@@ -6,7 +6,6 @@ import {
SimpleGrid,
} from '@vegaprotocol/ui-toolkit';
import * as Schema from '@vegaprotocol/types';
import { timeInForceLabel } from '@vegaprotocol/orders';
import { compileGridData } from '../trading-mode-tooltip';
import { MarketModeValidationType } from '../../constants';
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
@@ -119,9 +118,7 @@ export const TimeInForceSelector = ({
hasError={!!errorMessage}
>
{options.map(([key, value]) => (
<option key={key} value={value}>
{timeInForceLabel(value)}
</option>
<TimeInForceOption key={key} value={value} />
))}
</TradingSelect>
{errorMessage && (
@@ -133,3 +130,8 @@ export const TimeInForceSelector = ({
</div>
);
};
const TimeInForceOption = ({ value }: { value: Schema.OrderTimeInForce }) => {
const t = useT();
return <option value={value}>{t(value)}</option>;
};
+12 -7
View File
@@ -1,11 +1,11 @@
import type { Asset, AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetOption } from '@vegaprotocol/assets';
import {
ethereumAddress,
required,
vegaPublicKey,
minSafe,
maxSafe,
useEthereumAddress,
useRequired,
useVegaPublicKey,
useMinSafe,
useMaxSafe,
addDecimal,
isAssetTypeERC20,
formatNumber,
@@ -85,6 +85,11 @@ export const DepositForm = ({
isFaucetable,
}: DepositFormProps) => {
const t = useT();
const ethereumAddress = useEthereumAddress();
const required = useRequired();
const vegaPublicKey = useVegaPublicKey();
const minSafe = useMinSafe();
const maxSafe = useMaxSafe();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openDialog = useWeb3ConnectStore((store) => store.open);
const { isActive, account } = useWeb3React();
@@ -459,7 +464,7 @@ const UseButton = (props: UseButtonProps) => {
<button
{...props}
type="button"
className="absolute top-0 right-0 ml-auto text-sm underline"
className="absolute right-0 top-0 ml-auto text-sm underline"
/>
);
};
@@ -519,7 +524,7 @@ export const AddressField = ({
setIsInput((curr) => !curr);
onChange();
}}
className="absolute top-0 right-0 ml-auto text-sm underline"
className="absolute right-0 top-0 ml-auto text-sm underline"
data-testid="enter-pubkey-manually"
>
{isInput ? t('Select from wallet') : t('Enter manually')}
+6
View File
@@ -10,7 +10,10 @@ import en_fills from './locales/en/fills.json';
import en_funding_payments from './locales/en/funding-payments.json';
import en_governance from './locales/en/governance.json';
import en_trading from './locales/en/trading.json';
import en_markets from './locales/en/markets.json';
import en_web3 from './locales/en/web3.json';
import en_positions from './locales/en/positions.json';
export const locales = {
en: {
accounts: en_accounts,
@@ -24,5 +27,8 @@ export const locales = {
'funding-payments': en_funding_payments,
governance: en_governance,
trading: en_trading,
markets: en_markets,
web3: en_web3,
positions: en_positions,
},
};
+7 -1
View File
@@ -128,5 +128,11 @@
"You need to connect your own wallet to start trading on this market": "You need to connect your own wallet to start trading on this market",
"You need to provide a minimum visible size": "You need to provide a minimum visible size",
"You need to provide a peak size": "You need to provide a peak size",
"You need to provide a size": "You need to provide a size"
"You need to provide a size": "You need to provide a size",
"TIME_IN_FORCE_FOK": "Fill or Kill (FOK)",
"TIME_IN_FORCE_GFA": "Good for Auction (GFA)",
"TIME_IN_FORCE_GFN": "Good for Normal (GFN)",
"TIME_IN_FORCE_GTC": "Good 'til Cancelled (GTC)",
"TIME_IN_FORCE_GTT": "Good 'til Time (GTT)",
"TIME_IN_FORCE_IOC": "Immediate or Cancel (IOC)"
}
+18
View File
@@ -0,0 +1,18 @@
{
"Date from": "Date from",
"Date to": "Date to",
"Download": "Download",
"Download all to .csv file": "Download all to .csv file",
"Download has been started": "Download has been started",
"Downloading for {{asset}} from {{startDate}} till {{endDate}}": "Downloading for {{asset}} from {{startDate}} till {{endDate}}",
"Export ledger entries": "Export ledger entries",
"Get file here": "Get file here",
"Please note this can take several minutes.": "Please note this can take several minutes.",
"Select asset": "Select asset",
"Something went wrong": "Something went wrong",
"Still in progress": "Still in progress",
"The downloaded file uses the UTC time zone for all listed times. Your time zone is UTC{{offset}}.": "The downloaded file uses the UTC time zone for all listed times. Your time zone is UTC{{offset}}.",
"Try again later": "Try again later",
"You will be notified here when your file is ready.": "You will be notified here when your file is ready.",
"Your file is ready": "Your file is ready"
}
+41
View File
@@ -0,0 +1,41 @@
{
"Adjusted stake share": "Adjusted stake share",
"Commitment ({{symbol}})": "Commitment ({{symbol}})",
"Commitment details": "Commitment details",
"Created": "Created",
"Current epoch fraction of time on the book.": "Current epoch fraction of time on the book.",
"Fee": "Fee",
"Fees accrued this epoch": "Fees accrued this epoch",
"Last bond penalty": "Last bond penalty",
"Last epoch bond penalty.": "Last epoch bond penalty.",
"Last epoch fee penalty.": "Last epoch fee penalty.",
"Last epoch fraction of time on the book.": "Last epoch fraction of time on the book.",
"Last epoch SLA details": "Last epoch SLA details",
"Last fee penalty": "Last fee penalty",
"Last time on the book": "Last time on the book",
"Live liquidity data": "Live liquidity data",
"Live liquidity quality score (%)": "Live liquidity quality score (%)",
"Live supplied liquidity": "Live supplied liquidity",
"Live time on book": "Live time on book",
"No liquidity provisions": "No liquidity provisions",
"Obligation": "Obligation",
"Party": "Party",
"Share": "Share",
"Status": "Status",
"The amount committed to the market by this liquidity provider.": "The amount committed to the market by this liquidity provider.",
"The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.": "The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.",
"The average score of the liquidity provider.": "The average score of the liquidity provider.",
"The current status of this liquidity provision.": "The current status of this liquidity provision.",
"The date and time this liquidity provision was created.": "The date and time this liquidity provision was created.",
"The date and time this liquidity provision was last updated.": "The date and time this liquidity provision was last updated.",
"The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.": "The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.",
"The fee percentage (per trade) proposed by each liquidity provider.": "The fee percentage (per trade) proposed by each liquidity provider.",
"The liquidity fees accrued by each provider, which will be distributed at the end of the epoch after applying any penalties.": "The liquidity fees accrued by each provider, which will be distributed at the end of the epoch after applying any penalties.",
"The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume.": "The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume.",
"The public key of the party making this commitment.": "The public key of the party making this commitment.",
"The virtual stake of the liquidity provider.": "The virtual stake of the liquidity provider.",
"This LP's time on the book in the current epoch ({{currentEpoch}}) is less than 100%, so they could lose some fees to a better performing LP.": "This LP's time on the book in the current epoch ({{currentEpoch}}) is less than 100%, so they could lose some fees to a better performing LP.",
"This LP's time on the book in the current epoch ({{currentEpoch}}) is less than the minimum required ({{minimumRequired}}), so they could lose all fee revenue for this epoch.": "This LP's time on the book in the current epoch ({{currentEpoch}}) is less than the minimum required ({{minimumRequired}}), so they could lose all fee revenue for this epoch.",
"Updated": "Updated",
"Updating next epoch": "Updating next epoch"
}
@@ -0,0 +1,5 @@
{
"Last traded price": "Last traded price",
"No open orders": "No open orders",
"Spread": "Spread"
}
+143
View File
@@ -0,0 +1,143 @@
{
"{{liquidityPriceRange}} of mid price": "{{liquidityPriceRange}} of mid price",
"{{probability}} probability price bounds": "{{probability}} probability price bounds",
"24 hour change is unavailable at this time. The price change in the last 120 hours is:": "24 hour change is unavailable at this time. The price change in the last 120 hours is:",
"24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}}": "24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}}",
"A concept derived from traditional markets. It is a calculated value for the current market price on a market.": "A concept derived from traditional markets. It is a calculated value for the current market price on a market.",
"A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.": "A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.",
"A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.": "A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.",
"ABI specification": "ABI specification",
"Added": "Added",
"Address": "Address",
"All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.": "All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.",
"Auction extension duration in seconds, should the price breach its theoretical level over the specified horizon at the specified probability level.": "Auction extension duration in seconds, should the price breach its theoretical level over the specified horizon at the specified probability level.",
"Block explorer": "Block explorer",
"Conditions": "Conditions",
"Could not load market": "Could not load market",
"Current fees": "Current fees",
"Data about the sector. Example: 'automotive' for a market based on value of Tesla shares.": "Data about the sector. Example: 'automotive' for a market based on value of Tesla shares.",
"Details": "Details",
"Determines how the probability of trading is scaled from the risk model, and is used to measure the relative competitiveness of an LP's supplied volume. This is a network parameter.": "Determines how the probability of trading is scaled from the risk model, and is used to measure the relative competitiveness of an LP's supplied volume. This is a network parameter.",
"Ethereum Oracle": "Ethereum Oracle",
"every {{duration}}": "every {{duration}}",
"every {{duration}} from {{initialTime}}": "every {{duration}} from {{initialTime}}",
"Fees paid to validators as a reward for running the infrastructure of the network.": "Fees paid to validators as a reward for running the infrastructure of the network.",
"Filters": "Filters",
"For liquidity orders to count towards a commitment, they must be within the liquidity monitoring bounds.": "For liquidity orders to count towards a commitment, they must be within the liquidity monitoring bounds.",
"Funding": "Funding",
"How big the smallest order / position on the market can be.": "How big the smallest order / position on the market can be.",
"How long an epoch is. LP rewards from liquidity fees are paid out once per epoch. How much they receive depends on whether they met the liquidity SLA and their previous performance in recent epochs. This is a network parameter.": "How long an epoch is. LP rewards from liquidity fees are paid out once per epoch. How much they receive depends on whether they met the liquidity SLA and their previous performance in recent epochs. This is a network parameter.",
"How often the quality of liquidity supplied by each liquidity provider is evaluated and the fees arising from that period are earmarked for specific providers. This is a market parameter. ": "How often the quality of liquidity supplied by each liquidity provider is evaluated and the fees arising from that period are earmarked for specific providers. This is a market parameter. ",
"Instrument": "Instrument",
"Insurance pool": "Insurance pool",
"Internal conditions": "Internal conditions",
"Invalid data source": "Invalid data source",
"involvedInMarkets_other": "Involved in {{count}} markets",
"involvedInMarkets_one": "Involved in {{count}} market",
"Key": "Key",
"Key details": "Key details",
"Liquidity": "Liquidity",
"Liquidity monitoring parameters": "Liquidity monitoring parameters",
"Liquidity portion of the fee is paid to liquidity providers, and is transferred to the liquidity fee pool for the market.": "Liquidity portion of the fee is paid to liquidity providers, and is transferred to the liquidity fee pool for the market.",
"Liquidity price range": "Liquidity price range",
"Liquidity SLA protocol": "Liquidity SLA protocol",
"Maker portion of the fee is transferred to the non-aggressive, or passive party in the trade (the maker, as opposed to the taker).": "Maker portion of the fee is transferred to the non-aggressive, or passive party in the trade (the maker, as opposed to the taker).",
"Margin scaling factors": "Margin scaling factors",
"Market": "Market",
"Market data": "Market data",
"Market governance": "Market governance",
"Market ID": "Market ID",
"Market price": "Market price",
"Market specification": "Market specification",
"Market volume": "Market volume",
"Maximum fraction of an LP's accrued fees that an LP would lose to liquidity providers that achieved a higher SLA performance than them. This is a market parameter.": "Maximum fraction of an LP's accrued fees that an LP would lose to liquidity providers that achieved a higher SLA performance than them. This is a market parameter.",
"Maximum value that a proposed fee amount can be, which is submitted as part of the LP commitment transaction. Note that a value of 0.05 = 5%. This is a network parameter.": "Maximum value that a proposed fee amount can be, which is submitted as part of the LP commitment transaction. Note that a value of 0.05 = 5%. This is a network parameter.",
"Metadata": "Metadata",
"moreProofs_one": "And {{count}} more proof",
"moreProofs_other": "And {{count}} more proofs",
"Multiplier used to translate an LP's commitment amount to their liquidity obligation. This is a network parameter.": "Multiplier used to translate an LP's commitment amount to their liquidity obligation. This is a network parameter.",
"No data": "No data",
"No oracle proof for settlement data": "No oracle proof for settlement data",
"No oracle proof for termination": "No oracle proof for termination",
"No oracle spec for trading termination. Internal timestamp used": "No oracle spec for trading termination. Internal timestamp used",
"Normalisers": "Normalisers",
"Not verified": "Not verified",
"Number of epochs over which past performance will continue to affect rewards. This is a market parameter.": "Number of epochs over which past performance will continue to affect rewards. This is a market parameter.",
"Oracle": "Oracle",
"Oracle repository": "Oracle repository",
"Oracle status for this market is <0>{{status}}</0>. {{description}} <1>Show more</1>": "Oracle status for this market is <0>{{status}}</0>. {{description}} <1>Show more</1>",
"Oracle status: {{status}}. {{description}}": "Oracle status: {{status}}. {{description}}",
"oracleInMarkets_one": "Oracle in {{count}} market",
"oracleInMarkets_other": "Oracle in {{count}} markets",
"Price monitoring bounds {{index}}": "Price monitoring bounds {{index}}",
"Probability level for price projection, e.g. value of 0.95 will result in a price range such that over the specified projection horizon, the prices observed in the market should be in that range 95% of the time.": "Probability level for price projection, e.g. value of 0.95 will result in a price range such that over the specified projection horizon, the prices observed in the market should be in that range 95% of the time.",
"Probability level used in <0>Expected Shortfall</0> calculation when obtaining Risk Factor Long and Risk Factor Short": "Probability level used in <0>Expected Shortfall</0> calculation when obtaining Risk Factor Long and Risk Factor Short",
"Projection horizon measured as a year fraction used in <0>Expected Shortfall</0> calculation when obtaining Risk Factor Long and Risk Factor Short": "Projection horizon measured as a year fraction used in <0>Expected Shortfall</0> calculation when obtaining Risk Factor Long and Risk Factor Short",
"proofsOfOwnership_one": "{{count}} proof of ownership",
"proofsOfOwnership_other": "{{count}} proofs of ownership",
"Proposal": "Proposal",
"Propose a change to market": "Propose a change to market",
"Read more": "Read more",
"Results in {{auctionExtensionSecs}} seconds auction if breached": "Results in {{auctionExtensionSecs}} seconds auction if breached",
"Risk factors": "Risk factors",
"Risk model": "Risk model",
"Settlement": "Settlement",
"Settlement asset": "Settlement asset",
"Settlement oracle": "Settlement oracle",
"Settlement schedule oracle": "Settlement schedule oracle",
"Show less": "Show less",
"SLA protocol = a part of the Vega protocol that creates similar incentives within the decentralised system to those achieved by a Service Level Agreement between parties in traditional finance. The SLA protocol involves no discussion, agreement, or contracts between parties but instead relies upon rules and an economic mechanism implemented in code running on the network": "SLA protocol = a part of the Vega protocol that creates similar incentives within the decentralised system to those achieved by a Service Level Agreement between parties in traditional finance. The SLA protocol involves no discussion, agreement, or contracts between parties but instead relies upon rules and an economic mechanism implemented in code running on the network",
"Specifications": "Specifications",
"Specifies the minimum fraction of time LPs must spend 'on the book' providing their committed liquidity. This is a market parameter.": "Specifies the minimum fraction of time LPs must spend 'on the book' providing their committed liquidity. This is a market parameter.",
"Status": "Status",
"Succession line": "Succession line",
"Termination": "Termination",
"Termination oracle": "Termination oracle",
"The aggregated volume being bid at the best bid price on the market.": "The aggregated volume being bid at the best bid price on the market.",
"The aggregated volume being bid at the best static bid price on the market.": "The aggregated volume being bid at the best static bid price on the market.",
"The aggregated volume being offered at the best offer price on the market.": "The aggregated volume being offered at the best offer price on the market.",
"The aggregated volume being offered at the best static offer price on the market.": "The aggregated volume being offered at the best static offer price on the market.",
"The classification of the product. Examples: shares, commodities, crypto, FX.": "The classification of the product. Examples: shares, commodities, crypto, FX.",
"The current amount of liquidity supplied for this market.": "The current amount of liquidity supplied for this market.",
"The current state of the market": "The current state of the market",
"The first currency in a pair for a currency-based derivatives market.": "The first currency in a pair for a currency-based derivatives market.",
"The fraction of the insurance pool balance that is carried over from the parent market to the successor.": "The fraction of the insurance pool balance that is carried over from the parent market to the successor.",
"The ID of the market this market succeeds.": "The ID of the market this market succeeds.",
"The length of time over which open interest is measured.": "The length of time over which open interest is measured.",
"The liquidity price range is a {{{liquidityPriceRange}} difference from the mid price.": "The liquidity price range is a {{{liquidityPriceRange}} difference from the mid price.",
"The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.": "The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.",
"The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.": "The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.",
"The maximum amount, as a fraction, that an LP's bond can be slashed by if they fail to reach the minimum SLA. This is a network parameter.": "The maximum amount, as a fraction, that an LP's bond can be slashed by if they fail to reach the minimum SLA. This is a network parameter.",
"The percentage of their bond an LP forfeits if they reduce their commitment while the market is below target stake. If 100%, an LP's entire bond is forfeited when they cancel their full commitment. This is a network parameter.": "The percentage of their bond an LP forfeits if they reduce their commitment while the market is below target stake. If 100%, an LP's entire bond is forfeited when they cancel their full commitment. This is a network parameter.",
"The scaling between the liquidity demand estimate, based on open interest and target stake.": "The scaling between the liquidity demand estimate, based on open interest and target stake.",
"The second currency in a pair for a currency-based derivatives market.": "The second currency in a pair for a currency-based derivatives market.",
"The smallest price increment on the book.": "The smallest price increment on the book.",
"The total number of contracts traded in the last 24 hours.": "The total number of contracts traded in the last 24 hours.",
"The trading mode the market is currently running.": "The trading mode the market is currently running.",
"The triggering ratio for entering liquidity auction.": "The triggering ratio for entering liquidity auction.",
"The underlying that is being priced by the market, described by the market's oracle.": "The underlying that is being priced by the market, described by the market's oracle.",
"The volume at which all trades would occur if the auction was uncrossed now (when in auction mode).": "The volume at which all trades would occur if the auction was uncrossed now (when in auction mode).",
"The volume of all open positions in a given market (the sum of the size of all positions greater than 0).": "The volume of all open positions in a given market (the sum of the size of all positions greater than 0).",
"There is 1 unit of the settlement asset ({{assetSymbol}}) to every 1 quote unit ({{quoteUnit}}).": "There is 1 unit of the settlement asset ({{assetSymbol}}) to every 1 quote unit ({{quoteUnit}}).",
"This market": "This market",
"This oracle has not proven ownership of any accounts.": "This oracle has not proven ownership of any accounts.",
"This public key has been observed acting in bad faith.": "This public key has been observed acting in bad faith.",
"This public key is no longer in the control of its original owners.": "This public key is no longer in the control of its original owners.",
"This public key is no longer in use.": "This public key is no longer in use.",
"This public key is suspected to be acting in bad faith, pending investigation.": "This public key is suspected to be acting in bad faith, pending investigation.",
"This public key's proofs have been verified.": "This public key's proofs have been verified.",
"This public key's proofs have not been verified yet, or no proofs have been provided yet.": "This public key's proofs have not been verified yet, or no proofs have been provided yet.",
"Time horizon of the price projection in seconds.": "Time horizon of the price projection in seconds.",
"Updated": "Updated",
"Used to calculate the penalty to liquidity providers when they cannot support their open position with the assets in their margin and general accounts. This is a network parameter.": "Used to calculate the penalty to liquidity providers when they cannot support their open position with the assets in their margin and general accounts. This is a network parameter.",
"Verified since {{lastVerified}}": "Verified since {{lastVerified}}",
"verifyProofs_one": "Verify {{count}} proof of ownership",
"verifyProofs_other": "Verify {{count}} proofs of ownership",
"View governance proposal": "View governance proposal",
"View liquidity provision table": "View liquidity provision table",
"View on Etherscan": "View on Etherscan",
"View settlement data specification": "View settlement data specification",
"View settlement schedule specification": "View settlement schedule specification",
"View termination specification": "View termination specification",
"Within %s seconds": "Within %s seconds"
}
+49
View File
@@ -0,0 +1,49 @@
{
"{{tifLabel}}. Post Only": "{{tifLabel}}. Post Only",
"{{tifLabel}}. Reduce only": "{{tifLabel}}. Reduce only",
"Cancel": "Cancel",
"Cancel all": "Cancel all",
"Cancel order": "Cancel order",
"Cancels": "Cancels",
"Copy": "Copy",
"Copy order ID": "Copy order ID",
"Created": "Created",
"Edit order": "Edit order",
"Expires": "Expires",
"Expires at": "Expires at",
"Filled": "Filled",
"Iceberg order": "Iceberg order",
"Liquidity provision": "Liquidity provision",
"Market": "Market",
"MAX": "MAX",
"Minimum size": "Minimum size",
"No orders": "No orders",
"No stop orders": "No stop orders",
"One Cancels the Other": "One Cancels the Other",
"Order details": "Order details",
"Order ID": "Order ID",
"Peak size": "Peak size",
"Pegged": "Pegged",
"Post only": "Post only",
"Price": "Price",
"Reduce only": "Reduce only",
"Remaining": "Remaining",
"Reserved remaining": "Reserved remaining",
"Side": "Side",
"Size": "Size",
"Something went wrong: {{errorMessage}}": "Something went wrong: {{errorMessage}}",
"Status": "Status",
"Submit": "Submit",
"The maximum volume that can be traded at once. Must be less than the total size of the order.": "The maximum volume that can be traded at once. Must be less than the total size of the order.",
"The price cannot be negative": "The price cannot be negative",
"The size cannot be negative": "The size cannot be negative",
"Trigger": "Trigger",
"Type": "Type",
"Update": "Update",
"Updated": "Updated",
"View order details": "View order details",
"When the order trades and its size falls below this threshold, it will be reset to the peak size and moved to the back of the priority order. Must be less than or equal to peak size, and greater than 0.": "When the order trades and its size falls below this threshold, it will be reset to the peak size and moved to the back of the priority order. Must be less than or equal to peak size, and greater than 0.",
"Yes": "Yes",
"You need to provide a price": "You need to provide a price",
"You need to provide a size": "You need to provide a size"
}
+30
View File
@@ -0,0 +1,30 @@
{
"Best case": "Best case",
"Close position": "Close position",
"Entry / Mark": "Entry / Mark",
"Lifetime loss socialisation deductions: {{losses}}": "Lifetime loss socialisation deductions: {{losses}}",
"Maintained by network": "Maintained by network",
"Margin / Leverage": "Margin / Leverage",
"Market": "Market",
"No positions": "No positions",
"Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.": "Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.",
"Read more about loss socialisation": "Read more about loss socialisation",
"Read more about position resolution": "Read more about position resolution",
"Realised PNL": "Realised PNL",
"Realised PNL: {{value}}": "Realised PNL: {{value}}",
"Size / Notional": "Size / Notional",
"Status: {{status}}": "Status: {{status}}",
"The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.": "The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.",
"The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.": "The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.",
"Unrealised PNL": "Unrealised PNL",
"Unrealised profit is the current profit on your open position. Margin is still allocated to your position.": "Unrealised profit is the current profit on your open position. Margin is still allocated to your position.",
"Vega key": "Vega key",
"View settlement asset details": "View settlement asset details",
"Worst case": "Worst case",
"Worst case liquidation price": "Worst case liquidation price",
"You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.": "You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.",
"You received less {{assetSymbol}} in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.": "You received less {{assetSymbol}} in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.",
"Your open orders were cancelled.": "Your open orders were cancelled.",
"Your position is distressed.": "Your position is distressed.",
"Your position was closed.": "Your position was closed."
}
+15
View File
@@ -0,0 +1,15 @@
{
"Expired on {{date}}": "Expired on {{date}}",
"Not time-based": "Not time-based",
"Expired": "Expired",
"Mark": "Mark",
"Required": "Required",
"Invalid Ethereum address": "Invalid Ethereum address",
"Invalid Vega key": "Invalid Vega key",
"Value is below minimum": "Value is below minimum",
"Value is above maximum": "Value is above maximum",
"Must be valid JSON": "Must be valid JSON",
"{{field}} must be a multiple of {{step}} for this market": "{{field}} must be a multiple of {{step}} for this market",
"{{field}} must be whole numbers for this market": "{{field}} must be whole numbers for this market",
"{{field}} accepts up to {{decimals}} decimal places": "{{field}} accepts up to {{decimals}} decimal places"
}
+110
View File
@@ -0,0 +1,110 @@
{
"{{title}} complete": "{{title}} complete",
"{{title}} failed": "{{title}} failed",
"{{title}} pending": "{{title}} pending",
"Action required": "Action required",
"Approved": "Approved",
"Await Ethereum transaction": "Await Ethereum transaction",
"Awaiting confirmation": "Awaiting confirmation",
"Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}": "Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}",
"Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...": "Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...",
"Batch market instruction": "Batch market instruction",
"Cancel all orders": "Cancel all orders",
"Cancel all orders for <strong>{{marketName}}</strong>": "Cancel all orders for <strong>{{marketName}}</strong>",
"Cancel all stop orders": "Cancel all stop orders",
"Cancel all stop orders for <strong>{{marketName}}</strong>": "Cancel all stop orders for <strong>{{marketName}}</strong>",
"Cancel order": "Cancel order",
"Cancel order - {{status}}": "Cancel order - {{status}}",
"Cancel stop order": "Cancel stop order",
"Cannot be completed until {{time}}": "Cannot be completed until {{time}}",
"Change network": "Change network",
"Close": "Close",
"Close position for <strong>{{marketName}}</strong>": "Close position for <strong>{{marketName}}</strong>",
"Coinbase": "Coinbase",
"Complete withdrawal": "Complete withdrawal",
"Confirm transaction": "Confirm transaction",
"Confirm transaction in wallet": "Confirm transaction in wallet",
"Confirmed": "Confirmed",
"Confirmed in wallet": "Confirmed in wallet",
"Connect to your Ethereum wallet": "Connect to your Ethereum wallet",
"Connect wallet": "Connect wallet",
"Connect wallet to withdraw": "Connect wallet to withdraw",
"Copy": "Copy",
"Delayed": "Delayed",
"Deposit": "Deposit",
"Edit order": "Edit order",
"Edit order - {{status}}": "Edit order - {{status}}",
"Error": "Error",
"Error occurred": "Error occurred",
"Error: {{errorMessage}}": "Error: {{errorMessage}}",
"Ethereum transaction complete": "Ethereum transaction complete",
"Filled": "Filled",
"Funds unlocked": "Funds unlocked",
"Go to your Ethereum wallet and connect to the network {{networkName}}": "Go to your Ethereum wallet and connect to the network {{networkName}}",
"If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.": "If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.",
"Invalid asset source: {{source}}": "Invalid asset source: {{source}}",
"Loading": "Loading",
"MetaMask": "MetaMask",
"MetaMask, Brave or other injected web wallet": "MetaMask, Brave or other injected web wallet",
"No data": "No data",
"Order cancelled'": "Order cancelled'",
"Order expired'": "Order expired'",
"Order filled": "Order filled",
"Order parked": "Order parked",
"Order partially filled": "Order partially filled",
"Order rejected": "Order rejected",
"Order stopped": "Order stopped",
"Order submitted": "Order submitted",
"Pending approval": "Pending approval",
"Please go to your Vega wallet application and approve or reject the transaction.": "Please go to your Vega wallet application and approve or reject the transaction.",
"Please go to your wallet application and approve or reject the transaction.": "Please go to your wallet application and approve or reject the transaction.",
"Please wait for your transaction to be confirmed": "Please wait for your transaction to be confirmed",
"Please wait for your transaction to be confirmed.": "Please wait for your transaction to be confirmed.",
"Processing": "Processing",
"Processing deposit": "Processing deposit",
"Return": "Return",
"Save withdrawal details": "Save withdrawal details",
"save your withdrawal details": "save your withdrawal details",
"Something went wrong": "Something went wrong",
"Submission failed": "Submission failed",
"Submit order": "Submit order",
"Submit order - {{status}}": "Submit order - {{status}}",
"Submit stop order": "Submit stop order",
"The amount you're withdrawing has triggered a time delay": "The amount you're withdrawing has triggered a time delay",
"The connection to your Vega Wallet has been lost.": "The connection to your Vega Wallet has been lost.",
"The withdrawal has been approved.": "The withdrawal has been approved.",
"To {{address}}": "To {{address}}",
"To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}": "To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}",
"Transaction confirmed": "Transaction confirmed",
"Transfer": "Transfer",
"Transfer complete": "Transfer complete",
"Unknown": "Unknown",
"Vega confirmation": "Vega confirmation",
"Vega is confirming your transaction...": "Vega is confirming your transaction...",
"Verifying withdrawal approval": "Verifying withdrawal approval",
"Verifying...": "Verifying...",
"View in block explorer": "View in block explorer",
"View on Etherscan": "View on Etherscan",
"View transaction on Etherscan": "View transaction on Etherscan",
"Waiting for deposit confirmation.": "Waiting for deposit confirmation.",
"Wallet disconnected": "Wallet disconnected",
"WalletConnect": "WalletConnect",
"WalletConnect Legacy": "WalletConnect Legacy",
"WalletConnect v1": "WalletConnect v1",
"WalletConnect v2": "WalletConnect v2",
"Withdraw": "Withdraw",
"Withdraw {{amount}} {{symbol}}": "Withdraw {{amount}} {{symbol}}",
"Withdraw dependencies not met.": "Withdraw dependencies not met.",
"Withdraw failure": "Withdraw failure",
"Your {{timeInForce}} order was not filled and it has been stopped": "Your {{timeInForce}} order was not filled and it has been stopped",
"Your Ethereum wallet is connected to the wrong network.": "Your Ethereum wallet is connected to the wrong network.",
"Your funds have been unlocked for withdrawal.": "Your funds have been unlocked for withdrawal.",
"Your order has been rejected": "Your order has been rejected",
"Your order has been rejected because: {{rejectionReason}}": "Your order has been rejected because: {{rejectionReason}}",
"Your order has been stopped": "Your order has been stopped",
"Your order has been stopped because: {{rejectionReason}}": "Your order has been stopped because: {{rejectionReason}}",
"Your order was rejected.": "Your order was rejected.",
"Your transaction has been completed.": "Your transaction has been completed.",
"Your transaction has been confirmed": "Your transaction has been confirmed",
"Your transaction has been confirmed.": "Your transaction has been confirmed."
}
+14
View File
@@ -0,0 +1,14 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
let translatedLabel = label;
if (typeof replacements === 'object' && replacements !== null) {
Object.keys(replacements).forEach((key) => {
translatedLabel = translatedLabel.replace(
`{{${key}}}`,
replacements[key]
);
});
}
return translatedLabel;
},
});
+66 -56
View File
@@ -3,8 +3,8 @@ import type { ReactNode } from 'react';
import { useCallback, useEffect } from 'react';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { useToasts, Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { subscribeWithSelector } from 'zustand/middleware';
import { useT } from './use-t';
type DownloadSettings = {
title: string;
@@ -52,24 +52,31 @@ export const useLedgerDownloadFile = create<LedgerDownloadFileStore>()(
}))
);
const ErrorContent = ({ message }: { message?: string }) => (
<>
<h4 className="mb-1 text-sm">{t('Something went wrong')}</h4>
<p>{message || t('Try again later')}</p>
</>
);
const ErrorContent = ({ message }: { message?: string }) => {
const t = useT();
return (
<>
<h4 className="mb-1 text-sm">{t('Something went wrong')}</h4>
<p>{message || t('Try again later')}</p>
</>
);
};
const InfoContent = ({ progress = false }) => (
<>
<p>{t('Please note this can take several minutes.')}</p>
<p>{t('You will be notified here when your file is ready.')}</p>
<h4 className="my-2">
{progress ? t('Still in progress') : t('Download has been started')}
</h4>
</>
);
const InfoContent = ({ progress = false }) => {
const t = useT();
return (
<>
<p>{t('Please note this can take several minutes.')}</p>
<p>{t('You will be notified here when your file is ready.')}</p>
<h4 className="my-2">
{progress ? t('Still in progress') : t('Download has been started')}
</h4>
</>
);
};
export const useLedgerDownloadManager = () => {
const t = useT();
const queue = useLedgerDownloadFile((store) => store.queue);
const updateQueue = useLedgerDownloadFile((store) => store.updateQueue);
const removeItem = useLedgerDownloadFile((store) => store.removeItem);
@@ -88,48 +95,51 @@ export const useLedgerDownloadManager = () => {
[removeToast, removeItem]
);
const createToast = (item: DownloadSettings) => {
let content: ReactNode;
switch (true) {
case item.isError:
content = <ErrorContent message={item.errorMessage} />;
break;
case Boolean(item.blob):
content = (
const createToast = useCallback(
(item: DownloadSettings) => {
let content: ReactNode;
switch (true) {
case item.isError:
content = <ErrorContent message={item.errorMessage} />;
break;
case Boolean(item.blob):
content = (
<>
<h4 className="mb-1 text-sm">{t('Your file is ready')}</h4>
<a
onClick={() => onDownloadClose(item.link)}
href={URL.createObjectURL(item.blob as Blob)}
download={item.filename}
className="underline"
>
{t('Get file here')}
</a>
</>
);
break;
default:
content = <InfoContent progress={item.isDelayed} />;
}
const toast: Toast = {
id: item.link,
intent: item.intent || Intent.Primary,
content: (
<>
<h4 className="mb-1 text-sm">{t('Your file is ready')}</h4>
<a
onClick={() => onDownloadClose(item.link)}
href={URL.createObjectURL(item.blob as Blob)}
download={item.filename}
className="underline"
>
{t('Get file here')}
</a>
<h3 className="mb-1 text-md uppercase">{item.title}</h3>
{content}
</>
);
break;
default:
content = <InfoContent progress={item.isDelayed} />;
}
const toast: Toast = {
id: item.link,
intent: item.intent || Intent.Primary,
content: (
<>
<h3 className="mb-1 text-md uppercase">{item.title}</h3>
{content}
</>
),
onClose: () => onDownloadClose(item.link),
loader: !item.isDownloaded && !item.isError,
};
if (hasToast(toast.id)) {
updateToast(toast.id, toast);
} else {
setToast(toast);
}
};
),
onClose: () => onDownloadClose(item.link),
loader: !item.isDownloaded && !item.isError,
};
if (hasToast(toast.id)) {
updateToast(toast.id, toast);
} else {
setToast(toast);
}
},
[hasToast, setToast, onDownloadClose, updateToast, t]
);
useEffect(() => {
queue.forEach((item) => {
+12 -8
View File
@@ -14,9 +14,9 @@ import {
toNanoSeconds,
VEGA_ID_REGEX,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { localLoggerFactory } from '@vegaprotocol/logger';
import { useLedgerDownloadFile } from './ledger-download-store';
import { useT } from './use-t';
const DEFAULT_EXPORT_FILE_NAME = 'ledger_entries.csv';
@@ -70,6 +70,7 @@ interface Props {
}
export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
const t = useT();
const now = useRef(new Date());
const [dateFrom, setDateFrom] = useState(() => {
return formatForInput(subDays(now.current, 7));
@@ -116,11 +117,14 @@ export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
const startDownload = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const title = t('Downloading for %s from %s till %s', [
assets[assetId],
format(new Date(dateFrom), 'dd MMMM yyyy HH:mm'),
format(new Date(dateTo || Date.now()), 'dd MMMM yyyy HH:mm'),
]);
const title = t(
'Downloading for {{asset}} from {{startDate}} till {{endDate}}',
{
asset: assets[assetId],
startDate: format(new Date(dateFrom), 'dd MMMM yyyy HH:mm'),
endDate: format(new Date(dateTo || Date.now()), 'dd MMMM yyyy HH:mm'),
}
);
const downloadStoreItem = {
title,
@@ -225,8 +229,8 @@ export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
{offset && (
<p className="text-xs text-neutral-400 mt-1">
{t(
'The downloaded file uses the UTC time zone for all listed times. Your time zone is UTC%s.',
[toHoursAndMinutes(offset)]
'The downloaded file uses the UTC time zone for all listed times. Your time zone is UTC{{offset}}.',
{ offset: toHoursAndMinutes(offset) }
)}
</p>
)}
+3
View File
@@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const useT = () => useTranslation('ledger').t;
+14
View File
@@ -0,0 +1,14 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
let translatedLabel = label;
if (typeof replacements === 'object' && replacements !== null) {
Object.keys(replacements).forEach((key) => {
translatedLabel = translatedLabel.replace(
`{{${key}}}`,
replacements[key]
);
});
}
return translatedLabel;
},
});
+14 -13
View File
@@ -4,7 +4,6 @@ import {
addDecimalsFormatNumberQuantum,
getDateTimeFormat,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type {
TypedDataAgGrid,
VegaICellRendererParams,
@@ -27,6 +26,7 @@ import BigNumber from 'bignumber.js';
import { LiquidityProvisionStatus } from '@vegaprotocol/types';
import { LiquidityProvisionStatusMapping } from '@vegaprotocol/types';
import type { LiquidityProvisionData } from './liquidity-data-provider';
import { useT } from './use-t';
const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
const decimalPlaces =
@@ -81,6 +81,7 @@ export const LiquidityTable = ({
quantum,
...props
}: LiquidityTableProps) => {
const t = useT();
const colDefs = useMemo(() => {
const assetDecimalsFormatter = ({ value }: ITooltipParams) => {
if (!value) return '-';
@@ -125,32 +126,32 @@ export const LiquidityTable = ({
}
if (lessThanMinimum) {
return t(
`This LP's time on the book in the current epoch (%s) is less than the minimum required (%s), so they could lose all fee revenue for this epoch.`,
[
formatNumberPercentage(
"This LP's time on the book in the current epoch ({{currentEpoch}}) is less than the minimum required ({{minimumRequired}}), so they could lose all fee revenue for this epoch.",
{
currentEpoch: formatNumberPercentage(
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).times(
100
),
4
),
formatNumberPercentage(
minimumRequired: formatNumberPercentage(
new BigNumber(data.commitmentMinTimeFraction).times(100),
4
),
]
}
);
}
if (lessThanFull) {
return t(
`This LP's time on the book in the current epoch (%s) is less than 100%, so they could lose some fees to a better performing LP.`,
[
formatNumberPercentage(
"This LP's time on the book in the current epoch ({{currentEpoch}}) is less than 100%, so they could lose some fees to a better performing LP.",
{
currentEpoch: formatNumberPercentage(
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).times(
100
),
4
),
]
}
);
}
return addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0);
@@ -219,7 +220,7 @@ export const LiquidityTable = ({
},
},
{
headerName: t(`Commitment (${symbol})`),
headerName: t(`Commitment ({{symbol}})`, { symbol }),
field: 'commitmentAmount',
type: 'rightAligned',
headerTooltip: t(
@@ -399,7 +400,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: stakeToCcyVolumeQuantumFormatter,
valueFormatter: assetDecimalsQuantumFormatter,
tooltipValueGetter: feesAccruedTooltip,
cellClassRules: {
'text-warning': ({ data }: { data: LiquidityProvisionData }) => {
@@ -495,7 +496,7 @@ export const LiquidityTable = ({
},
];
return defs;
}, [assetDecimalPlaces, quantum, stakeToCcyVolume, symbol]);
}, [assetDecimalPlaces, quantum, stakeToCcyVolume, symbol, t]);
return (
<AgGrid
overlayNoRowsTemplate={t('No liquidity provisions')}
+3
View File
@@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const useT = () => useTranslation('liquidity').t;
@@ -0,0 +1,15 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
const replace =
replacements?.replace && typeof replacements === 'object'
? replacements?.replace
: replacements;
let translatedLabel = replacements?.defaultValue || label;
if (typeof replace === 'object' && replace !== null) {
Object.keys(replace).forEach((key) => {
translatedLabel = translatedLabel.replace(`{{${key}}}`, replace[key]);
});
}
return translatedLabel;
},
});
+2 -1
View File
@@ -2,7 +2,6 @@ import { DepthChart } from 'pennant';
import throttle from 'lodash/throttle';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { addDecimal, getNumberFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketDepthProvider } from './market-depth-provider';
@@ -16,6 +15,7 @@ import {
} from './__generated__/MarketDepth';
import { type DepthChartProps } from 'pennant';
import { parseLevel, updateLevels } from './depth-chart-utils';
import { useT } from './use-t';
interface DepthChartManagerProps {
marketId: string;
@@ -39,6 +39,7 @@ const getMidPrice = (
type DepthData = Pick<DepthChartProps, 'data' | 'midPrice'>;
export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
const t = useT();
const { theme } = useThemeSwitcher();
const variables = useMemo(() => ({ marketId }), [marketId]);
const [depthData, setDepthData] = useState<DepthData | null>(null);
+3 -1
View File
@@ -1,7 +1,6 @@
import { useMemo, useRef, useState } from 'react';
import ReactVirtualizedAutoSizer from 'react-virtualized-auto-sizer';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { usePrevious } from '@vegaprotocol/react-helpers';
import { OrderbookRow } from './orderbook-row';
import type { OrderbookRowData } from './orderbook-data';
@@ -10,6 +9,7 @@ import { Splash, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { PriceLevelFieldsFragment } from './__generated__/MarketDepth';
import { OrderbookControls } from './orderbook-controls';
import { useT } from './use-t';
// Sets row height, will be used to calculate number of rows that can be
// displayed each side of the book without overflow
@@ -85,6 +85,7 @@ export const OrderbookMid = ({
bestAskPrice: string;
bestBidPrice: string;
}) => {
const t = useT();
const previousLastTradedPrice = usePrevious(lastTradedPrice);
const priceChangeRef = useRef<'up' | 'down' | 'none'>('none');
const spread = (BigInt(bestAskPrice) - BigInt(bestBidPrice)).toString();
@@ -153,6 +154,7 @@ export const Orderbook = ({
bids,
assetSymbol,
}: OrderbookProps) => {
const t = useT();
const [resolution, setResolution] = useState(1);
const groupedAsks = useMemo(() => {
+3
View File
@@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const useT = () => useTranslation('market-depth').t;
@@ -7,10 +7,10 @@ import {
} from '@vegaprotocol/utils';
import { PriceChangeCell, signedNumberCssClass } from '@vegaprotocol/datagrid';
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useCandles } from '../../hooks/use-candles';
import BigNumber from 'bignumber.js';
import classNames from 'classnames';
import { useT } from '../../use-t';
interface Props {
marketId?: string;
@@ -25,6 +25,7 @@ export const Last24hPriceChange = ({
decimalPlaces,
initialValue,
}: Props) => {
const t = useT();
const { oneDayCandles, error, fiveDaysCandles } = useCandles({
marketId,
});
@@ -1,8 +1,8 @@
import { calcCandleVolume } from '../../market-utils';
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import { useCandles } from '../../hooks';
import { useT } from '../../use-t';
interface Props {
marketId?: string;
@@ -17,6 +17,7 @@ export const Last24hVolume = ({
formatDecimals,
initialValue,
}: Props) => {
const t = useT();
const { oneDayCandles, fiveDaysCandles } = useCandles({
marketId,
});
@@ -41,8 +42,8 @@ export const Last24hVolume = ({
<div>
<span className="flex flex-col">
{t(
'24 hour change is unavailable at this time. The volume change in the last 120 hours is %s',
[candleVolumeValue]
'24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}}',
{ candleVolumeValue }
)}
</span>
</div>
@@ -2,7 +2,6 @@ import {
addDecimalsFormatNumber,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
Intent,
KeyValueTable,
@@ -13,9 +12,10 @@ import {
import BigNumber from 'bignumber.js';
import startCase from 'lodash/startCase';
import { tooltipMapping } from './tooltip-mapping';
import { useTooltipMapping } from './tooltip-mapping';
import type { ReactNode } from 'react';
import { useT } from '../../use-t';
interface RowProps {
field: string;
value: ReactNode;
@@ -39,6 +39,8 @@ export const Row = ({
parentValue,
hasParentData,
}: RowProps) => {
const t = useT();
const tooltipMapping = useTooltipMapping();
// Note: we need both 'parentValue' and 'hasParentData' to do a conditional
// check to differentiate between when parentData itself is missing and when
// a specific parentValue is missing. These values are only used when we
@@ -4,7 +4,6 @@ import {
useEnvironment,
} from '@vegaprotocol/environment';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
import * as Schema from '@vegaprotocol/types';
import {
@@ -52,6 +51,7 @@ import {
isFuture,
getSigners,
} from '../../product';
import { useT } from '../../use-t';
export interface MarketInfoAccordionProps {
market: MarketInfo;
@@ -66,6 +66,7 @@ export const MarketInfoAccordionContainer = ({
marketId,
onSelect,
}: MarketInfoContainerProps) => {
const t = useT();
const { data, loading, error, reload } = useDataProvider({
dataProvider: marketInfoProvider,
skipUpdates: true,
@@ -89,6 +90,7 @@ export const MarketInfoAccordion = ({
market,
onSelect,
}: MarketInfoAccordionProps) => {
const t = useT();
const { VEGA_TOKEN_URL } = useEnvironment();
const headerClassName = 'uppercase text-lg';
@@ -252,7 +254,9 @@ export const MarketInfoAccordion = ({
<AccordionItem
key={id}
itemId={id}
title={t(`Price monitoring bounds ${triggerIndex + 1}`)}
title={t('Price monitoring bounds {{index}}', {
index: triggerIndex + 1,
})}
content={
<PriceMonitoringBoundsInfoPanel
market={market}
@@ -2,7 +2,6 @@ import isEqual from 'lodash/isEqual';
import type { ReactNode } from 'react';
import { Fragment, useMemo, useState } from 'react';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { marketDataProvider } from '../../market-data-provider';
import { totalFeesFactorsPercentage } from '../../market-utils';
import {
@@ -77,6 +76,7 @@ import {
import type { DataSourceFragment } from './__generated__/MarketInfo';
import { formatDuration } from 'date-fns';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { useT } from '../../use-t';
type MarketInfoProps = {
market: MarketInfo;
@@ -84,26 +84,30 @@ type MarketInfoProps = {
children?: ReactNode;
};
export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => (
<>
<MarketInfoTable
data={{
makerFee: market.fees.factors.makerFee,
infrastructureFee: market.fees.factors.infrastructureFee,
liquidityFee: market.fees.factors.liquidityFee,
totalFees: totalFeesFactorsPercentage(market.fees.factors),
}}
asPercentage={true}
/>
<p className="text-xs">
{t(
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
)}
</p>
</>
);
export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => {
const t = useT();
return (
<>
<MarketInfoTable
data={{
makerFee: market.fees.factors.makerFee,
infrastructureFee: market.fees.factors.infrastructureFee,
liquidityFee: market.fees.factors.liquidityFee,
totalFees: totalFeesFactorsPercentage(market.fees.factors),
}}
asPercentage={true}
/>
<p className="text-xs">
{t(
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
)}
</p>
</>
);
};
export const MarketPriceInfoPanel = ({ market }: MarketInfoProps) => {
const t = useT();
const assetSymbol = getAsset(market).symbol;
const quoteUnit = getQuoteName(market);
const { data } = useDataProvider({
@@ -123,8 +127,8 @@ export const MarketPriceInfoPanel = ({ market }: MarketInfoProps) => {
/>
<p className="mt-2 text-xs">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
'There is 1 unit of the settlement asset ({{assetSymbol}}) to every 1 quote unit ({{quoteUnit}}).',
{ assetSymbol, quoteUnit }
)}
</p>
</>
@@ -185,6 +189,7 @@ export const KeyDetailsInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
const t = useT();
const { data: parentMarketIdData } = useParentMarketIdQuery({
variables: {
marketId: market.id,
@@ -228,7 +233,7 @@ export const KeyDetailsInfoPanel = ({
<CopyWithTooltip text={market.id}>
<button
data-testid="copy-eth-oracle-address"
className="uppercase text-right"
className="text-right uppercase"
>
<span className="flex gap-1">
{truncateMiddle(market.id)}
@@ -303,6 +308,7 @@ const SuccessionLineItem = ({
marketId: string;
isCurrent?: boolean;
}) => {
const t = useT();
const { data } = useSuccessorMarketQuery({
variables: {
marketId,
@@ -319,7 +325,7 @@ const SuccessionLineItem = ({
<div
data-testid="succession-line-item"
className={classNames(
'rounded p-2 bg-vega-clight-700 dark:bg-vega-cdark-700',
'bg-vega-clight-700 dark:bg-vega-cdark-700 rounded p-2',
'font-alpha',
'flex flex-col '
)}
@@ -335,7 +341,7 @@ const SuccessionLineItem = ({
marketData.tradableInstrument.instrument.code
)
) : (
<span className="block w-20 h-4 mb-1 bg-vega-clight-500 dark:bg-vega-cdark-500 animate-pulse"></span>
<span className="bg-vega-clight-500 dark:bg-vega-cdark-500 mb-1 block h-4 w-20 animate-pulse"></span>
)}
</div>
{isCurrent && (
@@ -350,12 +356,12 @@ const SuccessionLineItem = ({
{marketData ? (
marketData.tradableInstrument.instrument.name
) : (
<span className="block h-4 w-28 bg-vega-clight-500 dark:bg-vega-cdark-500 animate-pulse"></span>
<span className="bg-vega-clight-500 dark:bg-vega-cdark-500 block h-4 w-28 animate-pulse"></span>
)}
</div>
<div
data-testid="succession-line-item-market-id"
className="mt-1 text-xs truncate"
className="mt-1 truncate text-xs"
>
{marketId}
</div>
@@ -364,7 +370,7 @@ const SuccessionLineItem = ({
};
const SuccessionLink = () => (
<div className="leading-none text-center" aria-hidden>
<div className="text-center leading-none" aria-hidden>
<VegaIcon name={VegaIconNames.ARROW_DOWN} size={12} />
</div>
);
@@ -442,6 +448,7 @@ export const InstrumentInfoPanel = ({
};
export const SettlementAssetInfoPanel = ({ market }: MarketInfoProps) => {
const t = useT();
const assetSymbol = getAsset(market).symbol;
const quoteUnit = getQuoteName(market);
const assetId = useMemo(() => getAsset(market).id, [market]);
@@ -458,8 +465,8 @@ export const SettlementAssetInfoPanel = ({ market }: MarketInfoProps) => {
/>
<p className="mt-4 text-xs">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
'There is 1 unit of the settlement asset ({{assetSymbol}}) to every 1 quote unit ({{quoteUnit}}).',
{ assetSymbol, quoteUnit }
)}
</p>
</>
@@ -686,6 +693,7 @@ export const PriceMonitoringBoundsInfoPanel = ({
}: MarketInfoProps & {
triggerIndex: number;
}) => {
const t = useT();
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
@@ -706,16 +714,18 @@ export const PriceMonitoringBoundsInfoPanel = ({
}
return (
<>
<div className="mb-2 text-sm grid grid-cols-2">
<div className="mb-2 grid grid-cols-2 text-sm">
<p className="col-span-1">
{t('%s probability price bounds', [
formatNumberPercentage(
{t('{{probability}} probability price bounds', {
probability: formatNumberPercentage(
new BigNumber(trigger.probability).times(100)
),
])}
})}
</p>
<p className="text-right col-span-1">
{t('Within %s seconds', [formatNumber(trigger.horizonSecs)])}
<p className="col-span-1 text-right">
{t('Within %s seconds', {
horizonSecs: formatNumber(trigger.horizonSecs),
})}
</p>
</div>
{bounds && (
@@ -729,9 +739,9 @@ export const PriceMonitoringBoundsInfoPanel = ({
/>
)}
<p className="mt-2 text-xs">
{t('Results in %s seconds auction if breached', [
trigger.auctionExtensionSecs.toString(),
])}
{t('Results in {{auctionExtensionSecs}} seconds auction if breached', {
auctionExtensionSecs: trigger.auctionExtensionSecs.toString(),
})}
</p>
</>
);
@@ -766,6 +776,7 @@ export const LiquidityMonitoringParametersInfoPanel = ({
};
export const EthOraclePanel = ({ sourceType }: { sourceType: EthCallSpec }) => {
const t = useT();
const abis = sourceType.abi?.map((abi) => JSON.parse(abi));
const header = 'uppercase my-1 text-left';
return (
@@ -779,7 +790,7 @@ export const EthOraclePanel = ({ sourceType }: { sourceType: EthCallSpec }) => {
<CopyWithTooltip text={sourceType.address}>
<button
data-testid="copy-eth-oracle-address"
className="uppercase text-right"
className="text-right uppercase"
>
<span className="flex gap-1">
{truncateMiddle(sourceType.address)}
@@ -820,9 +831,9 @@ export const EthOraclePanel = ({ sourceType }: { sourceType: EthCallSpec }) => {
<div
data-testid={`abi-dropdown`}
key={'value-dropdown'}
className="flex items-center gap-2 w-full"
className="flex w-full items-center gap-2"
>
<div className="underline underline-offset-4 mb-1 uppercase">
<div className="mb-1 uppercase underline underline-offset-4">
{t('ABI specification')}
</div>
<AccordionChevron size={14} />
@@ -860,6 +871,7 @@ export const LiquidityPriceRangeInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
const t = useT();
const marketLpPriceRange = market.liquiditySLAParameters?.priceRange;
const parentMarketLpPriceRange =
parentMarket?.liquiditySLAParameters?.priceRange;
@@ -898,7 +910,9 @@ export const LiquidityPriceRangeInfoPanel = ({
if (parentMarket && parentMarketData && quoteUnit === parentQuoteUnit) {
parentData = {
liquidityPriceRange: `${parentLiquidityPriceRange} of mid price`,
liquidityPriceRange: t(`{{parentLiquidityPriceRange}} of mid price`, {
parentLiquidityPriceRange,
}),
lowestPrice:
parentMarketLpPriceRange &&
parentMarketData?.midPrice &&
@@ -924,13 +938,16 @@ export const LiquidityPriceRangeInfoPanel = ({
return (
<>
<p className="text-xs mb-2 border-l-2 pl-2">
{`For liquidity orders to count towards a commitment, they must be
within the liquidity monitoring bounds.`}
<p className="mb-2 border-l-2 pl-2 text-xs">
{t(
`For liquidity orders to count towards a commitment, they must be within the liquidity monitoring bounds.`
)}
</p>
<MarketInfoTable
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
liquidityPriceRange: t(`{{liquidityPriceRange}} of mid price`, {
liquidityPriceRange,
}),
lowestPrice:
marketLpPriceRange &&
data?.midPrice &&
@@ -954,16 +971,18 @@ export const LiquidityPriceRangeInfoPanel = ({
}}
parentData={parentData}
/>
<p className="text-xs mb-2 border-l-2 pl-2 mt-2">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
<p className="mb-2 mt-2 border-l-2 pl-2 text-xs">
{t(
'The liquidity price range is a {{{liquidityPriceRange}} difference from the mid price.',
{ liquidityPriceRange }
)}
</p>
</>
);
};
const fromNanoSecondsToSeconds = (nanoseconds: number | string) =>
t('%ss', [new BigNumber(nanoseconds).dividedBy(1e9).toString()]);
`${new BigNumber(nanoseconds).dividedBy(1e9).toString()}s`;
export const LiquiditySLAParametersInfoPanel = ({
market,
@@ -1069,6 +1088,7 @@ export const FundingInfoPanel = ({
}: {
dataSource: DataSourceFragment;
}) => {
const t = useT();
const sourceType = dataSource.data.sourceType.sourceType;
if (
sourceType.__typename !== 'DataSourceSpecConfigurationTimeTrigger' ||
@@ -1079,13 +1099,16 @@ export const FundingInfoPanel = ({
const { every, initial } = sourceType.triggers[0];
const hours = Math.floor(every / (60 * 60));
const minutes = Math.floor(every / 60) % 60;
const initialLabel = initial
? ` ${t('from')} ${getDateTimeFormat().format(new Date(initial * 1000))}`
: '';
return `${t('every')} ${formatDuration({
const duration = formatDuration({
hours,
minutes,
})} ${initialLabel}`;
});
return initial
? t('every {{duration}} from {{initialTime}}', {
duration,
initialTime: getDateTimeFormat().format(new Date(initial * 1000)),
})
: t('every {{duration}}', { duration });
};
export const OracleInfoPanel = ({
@@ -1097,6 +1120,7 @@ export const OracleInfoPanel = ({
}) => {
// If this is a successor market, this component will only receive parent market
// data if the termination or settlement data is different from the parent.
const t = useT();
const product = market.tradableInstrument.instrument.product;
const parentProduct = parentMarket?.tradableInstrument?.instrument?.product;
const { VEGA_EXPLORER_URL, ORACLE_PROOFS_URL } = useEnvironment();
@@ -1137,7 +1161,7 @@ export const OracleInfoPanel = ({
parentDataSourceSpec &&
parentDataSourceSpecId &&
parentProduct && (
<div className="flex flex-col line-through gap-2 text-vega-dark-300">
<div className="text-vega-dark-300 flex flex-col gap-2 line-through">
<DataSourceProof
data-testid="oracle-proof-links"
data={parentDataSourceSpec}
@@ -1208,6 +1232,7 @@ export const DataSourceProof = ({
type: 'settlementData' | 'termination' | 'settlementSchedule';
dataSourceSpecId: string;
}) => {
const t = useT();
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
const signers =
('signers' in data.sourceType.sourceType &&
@@ -1375,12 +1400,12 @@ const NoOracleProof = ({
}: {
type: 'settlementData' | 'termination' | 'settlementSchedule';
}) => {
const t = useT();
return (
<p>
{t(
'No oracle proof for %s',
type === 'settlementData' ? 'settlement data' : 'termination'
)}
{type === 'settlementData'
? t('No oracle proof for settlement data')
: t('No oracle proof for termination')}
</p>
);
};
@@ -1,148 +1,160 @@
import { ExternalLinks } from '@vegaprotocol/environment';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import type { ReactNode } from 'react';
import { Trans } from 'react-i18next';
import { useT } from '../../use-t';
export const tooltipMapping: Record<string, ReactNode> = {
makerFee: t(
'Maker portion of the fee is transferred to the non-aggressive, or passive party in the trade (the maker, as opposed to the taker).'
),
liquidityFee: t(
'Liquidity portion of the fee is paid to liquidity providers, and is transferred to the liquidity fee pool for the market.'
),
infrastructureFee: t(
'Fees paid to validators as a reward for running the infrastructure of the network.'
),
export const useTooltipMapping: () => Record<string, ReactNode> = () => {
const t = useT();
return {
makerFee: t(
'Maker portion of the fee is transferred to the non-aggressive, or passive party in the trade (the maker, as opposed to the taker).'
),
liquidityFee: t(
'Liquidity portion of the fee is paid to liquidity providers, and is transferred to the liquidity fee pool for the market.'
),
infrastructureFee: t(
'Fees paid to validators as a reward for running the infrastructure of the network.'
),
markPrice: t(
'A concept derived from traditional markets. It is a calculated value for the current market price on a market.'
),
quoteUnit: t(
`The underlying that is being priced by the market, described by the market's oracle.`
),
openInterest: t(
'The volume of all open positions in a given market (the sum of the size of all positions greater than 0).'
),
indicativeVolume: t(
'The volume at which all trades would occur if the auction was uncrossed now (when in auction mode).'
),
bestBidVolume: t(
'The aggregated volume being bid at the best bid price on the market.'
),
bestOfferVolume: t(
'The aggregated volume being offered at the best offer price on the market.'
),
bestStaticBidVolume: t(
'The aggregated volume being bid at the best static bid price on the market.'
),
bestStaticOfferVolume: t(
'The aggregated volume being offered at the best static offer price on the market.'
),
marketDecimalPlaces: t('The smallest price increment on the book.'),
decimalPlaces: t('The smallest price increment on the book.'),
positionDecimalPlaces: t(
'How big the smallest order / position on the market can be.'
),
tradingMode: t('The trading mode the market is currently running.'),
state: t('The current state of the market'),
markPrice: t(
'A concept derived from traditional markets. It is a calculated value for the current market price on a market.'
),
quoteUnit: t(
`The underlying that is being priced by the market, described by the market's oracle.`
),
openInterest: t(
'The volume of all open positions in a given market (the sum of the size of all positions greater than 0).'
),
indicativeVolume: t(
'The volume at which all trades would occur if the auction was uncrossed now (when in auction mode).'
),
bestBidVolume: t(
'The aggregated volume being bid at the best bid price on the market.'
),
bestOfferVolume: t(
'The aggregated volume being offered at the best offer price on the market.'
),
bestStaticBidVolume: t(
'The aggregated volume being bid at the best static bid price on the market.'
),
bestStaticOfferVolume: t(
'The aggregated volume being offered at the best static offer price on the market.'
),
marketDecimalPlaces: t('The smallest price increment on the book.'),
decimalPlaces: t('The smallest price increment on the book.'),
positionDecimalPlaces: t(
'How big the smallest order / position on the market can be.'
),
tradingMode: t('The trading mode the market is currently running.'),
state: t('The current state of the market'),
base: t(
'The first currency in a pair for a currency-based derivatives market.'
),
quote: t(
'The second currency in a pair for a currency-based derivatives market.'
),
class: t(
'The classification of the product. Examples: shares, commodities, crypto, FX.'
),
sector: t(
'Data about the sector. Example: "automotive" for a market based on value of Tesla shares.'
),
base: t(
'The first currency in a pair for a currency-based derivatives market.'
),
quote: t(
'The second currency in a pair for a currency-based derivatives market.'
),
class: t(
'The classification of the product. Examples: shares, commodities, crypto, FX.'
),
sector: t(
'Data about the sector. Example: "automotive" for a market based on value of Tesla shares.'
),
short: t(
'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.'
),
long: t(
'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.'
),
short: t(
'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.'
),
long: t(
'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.'
),
tau: (
<span>
{t('Projection horizon measured as a year fraction used in ')}
<ExternalLink href={ExternalLinks.MARGIN_CREDIT_RISK}>
{t('Expected Shortfall')}
</ExternalLink>
{t(' calculation when obtaining Risk Factor Long and Risk Factor Short')}
</span>
),
riskAversionParameter: (
<span>
{t('Probability level used in ')}
<ExternalLink href={ExternalLinks.MARGIN_CREDIT_RISK}>
{t('Expected Shortfall')}
</ExternalLink>
{t(' calculation when obtaining Risk Factor Long and Risk Factor Short')}
</span>
),
tau: (
<span>
<Trans
defaults="Projection horizon measured as a year fraction used in <0>Expected Shortfall</0> calculation when obtaining Risk Factor Long and Risk Factor Short"
components={[
<ExternalLink href={ExternalLinks.MARGIN_CREDIT_RISK}>
Expected Shortfall
</ExternalLink>,
]}
/>
</span>
),
riskAversionParameter: (
<span>
<Trans
defaults="Probability level used in <0>Expected Shortfall</0> calculation when obtaining Risk Factor Long and Risk Factor Short"
components={[
<ExternalLink href={ExternalLinks.MARGIN_CREDIT_RISK}>
Expected Shortfall
</ExternalLink>,
]}
/>
</span>
),
horizonSecs: t('Time horizon of the price projection in seconds.'),
probability: t(
'Probability level for price projection, e.g. value of 0.95 will result in a price range such that over the specified projection horizon, the prices observed in the market should be in that range 95% of the time.'
),
auctionExtensionSecs: t(
'Auction extension duration in seconds, should the price breach its theoretical level over the specified horizon at the specified probability level.'
),
horizonSecs: t('Time horizon of the price projection in seconds.'),
probability: t(
'Probability level for price projection, e.g. value of 0.95 will result in a price range such that over the specified projection horizon, the prices observed in the market should be in that range 95% of the time.'
),
auctionExtensionSecs: t(
'Auction extension duration in seconds, should the price breach its theoretical level over the specified horizon at the specified probability level.'
),
triggeringRatio: t('The triggering ratio for entering liquidity auction.'),
timeWindow: t('The length of time over which open interest is measured.'),
scalingFactor: t(
'The scaling between the liquidity demand estimate, based on open interest and target stake.'
),
targetStake: t(
`The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.`
),
suppliedStake: t('The current amount of liquidity supplied for this market.'),
parentMarketID: t('The ID of the market this market succeeds.'),
insurancePoolFraction: t(
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
),
commitmentMinTimeFraction: t(
`Specifies the minimum fraction of time LPs must spend 'on the book' providing their committed liquidity. This is a market parameter.`
),
feeCalculationTimeStep: t(
'How often the quality of liquidity supplied by each liquidity provider is evaluated and the fees arising from that period are earmarked for specific providers. This is a market parameter. '
),
performanceHysteresisEpochs: t(
'Number of epochs over which past performance will continue to affect rewards. This is a market parameter.'
),
SLACompetitionFactor: t(
`Maximum fraction of an LP's accrued fees that an LP would lose to liquidity providers that achieved a higher SLA performance than them. This is a market parameter. `
),
bondPenaltyParameter: t(
'Used to calculate the penalty to liquidity providers when they cannot support their open position with the assets in their margin and general accounts. This is a network parameter.'
),
nonPerformanceBondPenaltySlope: t(
'A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.'
),
nonPerformanceBondPenaltyMax: t(
`The maximum amount, as a fraction, that an LP's bond can be slashed by if they fail to reach the minimum SLA. This is a network parameter.`
),
maxLiquidityFeeFactorLevel: t(
'Maximum value that a proposed fee amount can be, which is submitted as part of the LP commitment transaction. Note that a value of 0.05 = 5%. This is a network parameter.'
),
stakeToCCYVolume: t(
`Multiplier used to translate an LP's commitment amount to their liquidity obligation. This is a network parameter.`
),
epochLength: t(
'How long an epoch is. LP rewards from liquidity fees are paid out once per epoch. How much they receive depends on whether they met the liquidity SLA and their previous performance in recent epochs. This is a network parameter.'
),
earlyExitPenalty: t(
`The percentage of their bond an LP forfeits if they reduce their commitment while the market is below target stake. If 100%, an LP's entire bond is forfeited when they cancel their full commitment. This is a network parameter.`
),
probabilityOfTradingTauScaling: t(
`Determines how the probability of trading is scaled from the risk model, and is used to measure the relative competitiveness of an LP's supplied volume. This is a network parameter.`
),
minProbabilityOfTradingLPOrders: t(
'The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.'
),
triggeringRatio: t('The triggering ratio for entering liquidity auction.'),
timeWindow: t('The length of time over which open interest is measured.'),
scalingFactor: t(
'The scaling between the liquidity demand estimate, based on open interest and target stake.'
),
targetStake: t(
`The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.`
),
suppliedStake: t(
'The current amount of liquidity supplied for this market.'
),
parentMarketID: t('The ID of the market this market succeeds.'),
insurancePoolFraction: t(
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
),
commitmentMinTimeFraction: t(
`Specifies the minimum fraction of time LPs must spend 'on the book' providing their committed liquidity. This is a market parameter.`
),
feeCalculationTimeStep: t(
'How often the quality of liquidity supplied by each liquidity provider is evaluated and the fees arising from that period are earmarked for specific providers. This is a market parameter. '
),
performanceHysteresisEpochs: t(
'Number of epochs over which past performance will continue to affect rewards. This is a market parameter.'
),
SLACompetitionFactor: t(
`Maximum fraction of an LP's accrued fees that an LP would lose to liquidity providers that achieved a higher SLA performance than them. This is a market parameter. `
),
bondPenaltyParameter: t(
'Used to calculate the penalty to liquidity providers when they cannot support their open position with the assets in their margin and general accounts. This is a network parameter.'
),
nonPerformanceBondPenaltySlope: t(
'A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.'
),
nonPerformanceBondPenaltyMax: t(
`The maximum amount, as a fraction, that an LP's bond can be slashed by if they fail to reach the minimum SLA. This is a network parameter.`
),
maxLiquidityFeeFactorLevel: t(
'Maximum value that a proposed fee amount can be, which is submitted as part of the LP commitment transaction. Note that a value of 0.05 = 5%. This is a network parameter.'
),
stakeToCCYVolume: t(
`Multiplier used to translate an LP's commitment amount to their liquidity obligation. This is a network parameter.`
),
epochLength: t(
'How long an epoch is. LP rewards from liquidity fees are paid out once per epoch. How much they receive depends on whether they met the liquidity SLA and their previous performance in recent epochs. This is a network parameter.'
),
earlyExitPenalty: t(
`The percentage of their bond an LP forfeits if they reduce their commitment while the market is below target stake. If 100%, an LP's entire bond is forfeited when they cancel their full commitment. This is a network parameter.`
),
probabilityOfTradingTauScaling: t(
`Determines how the probability of trading is scaled from the risk model, and is used to measure the relative competitiveness of an LP's supplied volume. This is a network parameter.`
),
minProbabilityOfTradingLPOrders: t(
'The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.'
),
};
};
@@ -1,5 +1,4 @@
import { useState } from 'react';
import { t } from '@vegaprotocol/i18n';
import { useMarketOracle } from '../../hooks';
import {
Intent,
@@ -7,9 +6,11 @@ import {
ButtonLink,
} from '@vegaprotocol/ui-toolkit';
import { OracleDialog } from '../oracle-dialog';
import { oracleStatuses } from './oracle-statuses';
import { useOracleStatuses } from './oracle-statuses';
import { Trans } from 'react-i18next';
export const OracleBanner = ({ marketId }: { marketId: string }) => {
const oracleStatuses = useOracleStatuses();
const [open, onChange] = useState(false);
const { data: settlementOracle } = useMarketOracle(marketId);
const { data: tradingTerminationOracle } = useMarketOracle(
@@ -30,17 +31,22 @@ export const OracleBanner = ({ marketId }: { marketId: string }) => {
<OracleDialog open={open} onChange={onChange} {...maliciousOracle} />
<NotificationBanner intent={Intent.Danger}>
<div>
Oracle status for this market is{' '}
<span data-testid="oracle-banner-status">
{provider.oracle.status}
</span>
. {oracleStatuses[provider.oracle.status]}{' '}
<ButtonLink
onClick={() => onChange(!open)}
data-testid="oracle-banner-dialog-trigger"
>
{t('Show more')}
</ButtonLink>
<Trans
defaults="Oracle status for this market is <0>{{status}}</0>. {{description}} <1>Show more</1>"
components={[
<span data-testid="oracle-banner-status">status</span>,
<ButtonLink
onClick={() => onChange(!open)}
data-testid="oracle-banner-dialog-trigger"
>
Show more
</ButtonLink>,
]}
values={{
status: provider.oracle.status,
description: oracleStatuses[provider.oracle.status],
}}
/>
</div>
</NotificationBanner>
</>
@@ -1,16 +1,19 @@
import { t } from '@vegaprotocol/i18n';
import { useT } from '../../use-t';
export const oracleStatuses = {
UNKNOWN: t(
"This public key's proofs have not been verified yet, or no proofs have been provided yet."
),
GOOD: t("This public key's proofs have been verified."),
SUSPICIOUS: t(
'This public key is suspected to be acting in bad faith, pending investigation.'
),
MALICIOUS: t('This public key has been observed acting in bad faith.'),
RETIRED: t('This public key is no longer in use.'),
COMPROMISED: t(
'This public key is no longer in the control of its original owners.'
),
export const useOracleStatuses = () => {
const t = useT();
return {
UNKNOWN: t(
"This public key's proofs have not been verified yet, or no proofs have been provided yet."
),
GOOD: t("This public key's proofs have been verified."),
SUSPICIOUS: t(
'This public key is suspected to be acting in bad faith, pending investigation.'
),
MALICIOUS: t('This public key has been observed acting in bad faith.'),
RETIRED: t('This public key is no longer in use.'),
COMPROMISED: t(
'This public key is no longer in the control of its original owners.'
),
};
};
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import type { Provider } from '../../oracle-schema';
import {
ButtonLink,
@@ -12,8 +11,10 @@ import type { IconName } from '@blueprintjs/icons';
import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import type { OracleMarketSpecFieldsFragment } from '../../__generated__/OracleMarketsSpec';
import { useT } from '../../use-t';
export const getVerifiedStatusIcon = (provider: Provider) => {
export const useVerifiedStatusIcon = (provider: Provider) => {
const t = useT();
const getIconIntent = () => {
switch (provider.oracle.status) {
case 'GOOD':
@@ -46,13 +47,12 @@ export const getVerifiedStatusIcon = (provider: Provider) => {
return {
...getIconIntent(),
message: t(
'Verified since %s',
lastVerified.toLocaleDateString(undefined, {
message: t('Verified since {{lastVerified}}', {
lastVerified: lastVerified.toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
})
),
}),
}),
};
};
@@ -65,7 +65,8 @@ export const OracleBasicProfile = ({
markets?: OracleMarketSpecFieldsFragment[] | undefined;
onClick?: (value?: boolean) => void;
}) => {
const { icon, message, intent } = getVerifiedStatusIcon(provider);
const t = useT();
const { icon, message, intent } = useVerifiedStatusIcon(provider);
const verifiedProofs = provider.proofs.filter(
(proof) => proof.available === true
@@ -121,10 +122,9 @@ export const OracleBasicProfile = ({
data-testid="signed-proofs"
className="mb-2 text-sm dark:text-vega-light-300 text-vega-dark-300"
>
{t('Involved in %s %s', [
oracleMarkets.length.toString(),
oracleMarkets.length !== 1 ? t('markets') : t('market'),
])}
{t('involvedInMarkets', 'Involved in {{count}} markets', {
count: oracleMarkets.length,
})}
</p>
)}
{links.length > 0 && (
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import type { Provider } from '../../oracle-schema';
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
import {
@@ -10,14 +9,15 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { oracleStatuses } from '../oracle-banner/oracle-statuses';
import { useOracleStatuses } from '../oracle-banner/oracle-statuses';
import type { IconName } from '@blueprintjs/icons';
import classNames from 'classnames';
import { getLinkIcon, getVerifiedStatusIcon } from '../oracle-basic-profile';
import { getLinkIcon, useVerifiedStatusIcon } from '../oracle-basic-profile';
import { useEnvironment } from '@vegaprotocol/environment';
import type { OracleMarketSpecFieldsFragment } from '../../__generated__/OracleMarketsSpec';
import ReactMarkdown from 'react-markdown';
import { useState } from 'react';
import { useT } from '../../use-t';
export const OracleProfileTitle = ({
provider,
@@ -26,10 +26,11 @@ export const OracleProfileTitle = ({
provider: Provider;
parentProvider?: Provider;
}) => {
const t = useT();
// If this is a successor market, the parent provider will only have been passed
// in if it differs from the current provider. If it is different, we'll just
// show the change in name, not icons and proofs.
const { icon, intent } = getVerifiedStatusIcon(provider);
const { icon, intent } = useVerifiedStatusIcon(provider);
const verifiedProofs = provider.proofs.filter(
(proof) => proof.available === true
);
@@ -55,10 +56,10 @@ export const OracleProfileTitle = ({
'text-gray-700 dark:text-gray-300': intent === Intent.None,
'text-vega-blue': intent === Intent.Primary,
'text-vega-green dark:text-vega-green': intent === Intent.Success,
'text-yellow-600 dark:text-yellow': intent === Intent.Warning,
'dark:text-yellow text-yellow-600': intent === Intent.Warning,
'text-vega-red': intent === Intent.Danger,
},
'flex items-start align-text-bottom p-1'
'flex items-start p-1 align-text-bottom'
)}
>
<Icon size={6} name={icon as IconName} />
@@ -67,23 +68,30 @@ export const OracleProfileTitle = ({
);
};
const OracleStatus = ({ oracle }: { oracle: Provider['oracle'] }) => (
<div>
{t(`Oracle status`)}: {oracle.status}. {oracleStatuses[oracle.status]}
{oracle.status_reason ? (
<div>
<ReactMarkdown
className="react-markdown-container"
skipHtml={true}
disallowedElements={['img']}
linkTarget="_blank"
>
{oracle.status_reason}
</ReactMarkdown>
</div>
) : null}
</div>
);
const OracleStatus = ({ oracle }: { oracle: Provider['oracle'] }) => {
const oracleStatuses = useOracleStatuses();
const t = useT();
return (
<div>
{t(`Oracle status: {{status}}. {{description}}`, {
status: oracle.status,
description: oracleStatuses[oracle.status],
})}
{oracle.status_reason ? (
<div>
<ReactMarkdown
className="react-markdown-container"
skipHtml={true}
disallowedElements={['img']}
linkTarget="_blank"
>
{oracle.status_reason}
</ReactMarkdown>
</div>
) : null}
</div>
);
};
export const OracleFullProfile = ({
provider,
@@ -94,7 +102,8 @@ export const OracleFullProfile = ({
dataSourceSpecId: string;
markets?: OracleMarketSpecFieldsFragment[] | undefined;
}) => {
const { message } = getVerifiedStatusIcon(provider);
const t = useT();
const { message } = useVerifiedStatusIcon(provider);
const { VEGA_EXPLORER_URL } = useEnvironment();
const [showMore, setShowMore] = useState(false);
@@ -140,10 +149,9 @@ export const OracleFullProfile = ({
className="dark:text-vega-light-300 text-vega-dark-300 uppercase"
data-testid="verified-accounts"
>
{t('%s %s of ownership', [
provider.proofs.length.toString(),
provider.proofs.length === 1 ? 'proof' : 'proofs',
])}
{t('proofsOfOwnership', '{{count}} proofs of ownership', {
count: provider.proofs.length,
})}
</p>
{provider.proofs.length > 0 ? (
<div className="flex flex-col gap-1">
@@ -151,12 +159,12 @@ export const OracleFullProfile = ({
<ExternalLink
key={link.url}
href={link.url}
className="flex align-items-bottom underline text-sm"
className="align-items-bottom flex text-sm underline"
>
<span className="pt-1 pr-1">
<span className="pr-1 pt-1">
<VegaIcon name={getLinkIcon(link.type)} />
</span>
<span className="underline capitalize">
<span className="capitalize underline">
{link.type}{' '}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</span>
@@ -166,22 +174,24 @@ export const OracleFullProfile = ({
<ExternalLink
key={'more-proofs'}
href={provider.github_link}
className="flex align-items-bottom underline text-sm pt-2"
className="align-items-bottom flex pt-2 text-sm underline"
>
{links.length > 0 ? (
<span className="underline">
{t('And %s more %s', [
signedMessageProofs.length.toString(),
signedMessageProofs.length === 1 ? 'proof' : 'proofs',
])}{' '}
{t('moreProofs', 'And {{count}} more proofs', {
count: signedMessageProofs.length,
})}{' '}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</span>
) : (
<span className="underline">
{t('Verify %s %s of ownership', [
signedMessageProofs.length.toString(),
signedMessageProofs.length === 1 ? 'proof' : 'proofs',
])}{' '}
{t(
'verifyProofs',
'Verify {{count}} proofs of ownership',
{
proofs: signedMessageProofs,
}
)}{' '}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</span>
)}
@@ -194,7 +204,7 @@ export const OracleFullProfile = ({
</p>
)}
</div>
<div className="col-span-1 gap-2 py-2 flex flex-col">
<div className="col-span-1 flex flex-col gap-2 py-2">
<p className="dark:text-vega-light-300 text-vega-dark-300 uppercase">
{t('Details')}
</p>
@@ -215,20 +225,19 @@ export const OracleFullProfile = ({
</div>
<div>
{oracleMarkets && (
<p className="dark:text-vega-light-300 text-vega-dark-300 uppercase mt-4">
{t('Oracle in %s %s', [
oracleMarkets.length.toString(),
oracleMarkets.length === 1 ? 'market' : 'markets',
])}
<p className="dark:text-vega-light-300 text-vega-dark-300 mt-4 uppercase">
{t('oracleInMarkets', 'Oracle in {{count}} markets', {
count: oracleMarkets.length,
})}
</p>
)}
</div>
{oracleMarkets && oracleMarkets.length > 0 && (
<div
data-testid="oracle-markets"
className="border-vega-light-200 dark:border-vega-dark-200 border-solid border-2 py-4 px-2 rounded-lg my-2"
className="border-vega-light-200 dark:border-vega-dark-200 my-2 rounded-lg border-2 border-solid px-2 py-4"
>
<div className="grid grid-cols-4 gap-1 uppercase mb-2 font-alpha calt dark:text-vega-light-300 text-vega-dark-300">
<div className="font-alpha calt dark:text-vega-light-300 text-vega-dark-300 mb-2 grid grid-cols-4 gap-1 uppercase">
<div className="col-span-1">{t('Market')}</div>
<div className="col-span-1">{t('Status')}</div>
<div className="col-span-1">{t('Specifications')}</div>
@@ -236,7 +245,7 @@ export const OracleFullProfile = ({
<div className="max-h-60 overflow-auto">
{oracleMarkets?.map((market) => (
<div
className="grid grid-cols-4 gap-1 capitalize mb-2 last:mb-0"
className="mb-2 grid grid-cols-4 gap-1 capitalize last:mb-0"
key={`oracle-market-${market.id}`}
>
<div className="col-span-1">
+16
View File
@@ -28,6 +28,22 @@ export const getAsset = (market: Partial<Market>) => {
throw new Error('Failed to retrieve asset. Invalid product type');
};
export const getProductType = (market: Partial<Market>) => {
if (!market.tradableInstrument?.instrument.product) {
throw new Error(
'Failed to retrieve product type. Invalid tradable instrument'
);
}
const type = market.tradableInstrument.instrument.product.__typename;
if (!type) {
throw new Error('Failed to retrieve asset. Invalid product type');
}
return type;
};
export const getQuoteName = (market: Partial<Market>) => {
if (!market.tradableInstrument?.instrument.product) {
throw new Error(
+3
View File
@@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const useT = () => useTranslation('funding-payments').t;
+13
View File
@@ -1,4 +1,17 @@
import '@testing-library/jest-dom';
import { locales } from '@vegaprotocol/i18n';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import ResizeObserver from 'resize-observer-polyfill';
// Set up i18n instance so that components have the correct default
// en translations
i18n.use(initReactI18next).init({
// we init with resources
resources: locales,
fallbackLng: 'en',
ns: ['markets'],
defaultNS: 'markets',
});
global.ResizeObserver = ResizeObserver;
@@ -1,8 +1,8 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaTransactionStore } from '@vegaprotocol/web3';
import { useHasAmendableOrder } from '../../order-hooks';
import { useT } from '../../use-t';
export const OpenOrdersMenu = () => {
const { isReadOnly } = useVegaWallet();
@@ -28,8 +28,11 @@ export const OpenOrdersMenu = () => {
);
};
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
<TradingButton size="extra-small" onClick={onClick} data-testid="cancelAll">
{t('Cancel all')}
</TradingButton>
);
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => {
const t = useT();
return (
<TradingButton size="extra-small" onClick={onClick} data-testid="cancelAll">
{t('Cancel all')}
</TradingButton>
);
};
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import { useCallback, useRef, useState, useEffect } from 'react';
import { type AgGridReact } from 'ag-grid-react';
import { Pagination, type useDataGridEvents } from '@vegaprotocol/datagrid';
@@ -12,6 +11,7 @@ import { type Order } from '../order-data-provider';
import { OrderViewDialog } from '../order-list/order-view-dialog';
import { OrderListTable } from '../order-list';
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
import { useT } from '../../use-t';
export enum Filter {
'Open' = 'Open',
@@ -38,6 +38,7 @@ export const OrderListManager = ({
gridProps,
noRowsMessage,
}: OrderListManagerProps) => {
const t = useT();
const gridRef = useRef<AgGridReact | null>(null);
const [editOrder, setEditOrder] = useState<Order | null>(null);
const [viewOrder, setViewOrder] = useState<Order | null>(null);
@@ -85,7 +86,13 @@ export const OrderListManager = ({
);
if (error) {
return <Splash>{t(`Something went wrong: ${error.message}`)}</Splash>;
return (
<Splash>
{t(`Something went wrong: {{errorMessage}}`, {
errorMessage: error.message,
})}
</Splash>
);
}
return (
@@ -1,4 +1,4 @@
import { act, render, screen, within } from '@testing-library/react';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { OrderEditDialog } from './order-edit-dialog';
@@ -7,16 +7,14 @@ import { limitOrder } from '../mocks';
describe('OrderEditDialog', () => {
it('must be warned (pre-submit) if the input price has too many digits after the decimal place for the market', async () => {
// 7003-MORD-013
await act(async () => {
render(
<OrderEditDialog
order={limitOrder}
onChange={jest.fn()}
isOpen={true}
onSubmit={jest.fn()}
/>
);
});
render(
<OrderEditDialog
order={limitOrder}
onChange={jest.fn()}
isOpen={true}
onSubmit={jest.fn()}
/>
);
const editOrder = await screen.findByTestId('edit-order');
const limitPrice = within(editOrder).getByLabelText('Price');
await userEvent.type(limitPrice, '0.111111');
@@ -3,9 +3,8 @@ import {
getDateTimeFormat,
addDecimal,
addDecimalsFormatNumber,
validateAmount,
useValidateAmount,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Size } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import {
@@ -19,6 +18,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { useForm } from 'react-hook-form';
import type { Order } from '../order-data-provider';
import { useT } from '../../use-t';
interface OrderEditDialogProps {
isOpen: boolean;
@@ -38,6 +38,8 @@ export const OrderEditDialog = ({
order,
onSubmit,
}: OrderEditDialogProps) => {
const t = useT();
const validateAmount = useValidateAmount();
const headerClassName = 'text-xs font-bold text-black dark:text-white';
const {
register,
@@ -60,7 +62,7 @@ export const OrderEditDialog = ({
title={t('Edit order')}
icon={<VegaIcon name={VegaIconNames.EDIT} />}
>
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div className="grid grid-cols-1 gap-8 md:grid-cols-4">
{order.market && (
<div className="md:col-span-2">
<p className={headerClassName}>{t(`Market`)}</p>
@@ -99,10 +101,10 @@ export const OrderEditDialog = ({
<form
onSubmit={handleSubmit(onSubmit)}
data-testid="edit-order"
className="w-full mt-4"
className="mt-4 w-full"
noValidate
>
<div className="flex flex-col md:flex-row gap-4">
<div className="flex flex-col gap-4 md:flex-row">
<TradingFormGroup
label={t('Price')}
labelFor="limitPrice"
@@ -6,7 +6,6 @@ import {
isNumeric,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import {
ActionsDropdown,
@@ -30,10 +29,11 @@ import {
type VegaValueFormatterParams,
type VegaValueGetterParams,
} from '@vegaprotocol/datagrid';
import { AgGridReact } from 'ag-grid-react';
import { type AgGridReact } from 'ag-grid-react';
import { type Order } from '../order-data-provider';
import { Filter } from '../order-list-manager/order-list-manager';
import { type ColDef } from 'ag-grid-community';
import { useT } from '../../use-t';
const defaultColDef = {
resizable: true,
@@ -68,6 +68,7 @@ export const OrderListTable = memo<
},
ref
) => {
const t = useT();
const showAllActions = props.isReadOnly
? false
: filter === undefined || filter === Filter.Open
@@ -252,11 +253,14 @@ export const OrderListTable = memo<
}
const tifLabel = value ? Schema.OrderTimeInForceCode[value] : '';
const label = `${tifLabel}${
data?.postOnly ? t('. Post Only') : ''
}${data?.reduceOnly ? t('. Reduce only') : ''}`;
if (data?.postOnly) {
return t('{{tifLabel}}. Post Only', { tifLabel });
}
if (data?.reduceOnly) {
return t('{{tifLabel}}. Reduce only', { tifLabel });
}
return label;
return tifLabel;
},
},
{
@@ -336,6 +340,7 @@ export const OrderListTable = memo<
onOrderTypeClick,
props.isReadOnly,
showAllActions,
t,
]
);
@@ -2,7 +2,6 @@ import {
addDecimalsFormatNumber,
getDateTimeFormat,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Size } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import {
@@ -19,6 +18,7 @@ import type { Order } from '../order-data-provider';
import CopyToClipboard from 'react-copy-to-clipboard';
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
import classNames from 'classnames';
import { useT } from '../../use-t';
interface OrderViewDialogProps {
isOpen: boolean;
@@ -33,6 +33,7 @@ export const OrderViewDialog = ({
onChange,
onMarketClick,
}: OrderViewDialogProps) => {
const t = useT();
const [, setCopied] = useCopyTimeout();
return (
<Dialog open={isOpen} title={t('Order details')} onChange={onChange}>
@@ -184,21 +185,21 @@ export const OrderViewDialog = ({
<KeyValueTableRow key={'order-post-only'}>
<div data-testid={'order-post-only-label'}>{t('Post only')}</div>
<div data-testid={`order-post-only-value`}>
{order.postOnly ? t('Yes') : t('-')}
{order.postOnly ? t('Yes') : '-'}
</div>
</KeyValueTableRow>
<KeyValueTableRow key={'order-reduce-only'}>
<div data-testid={'order-reduce-only-label'}>{t('Reduce only')}</div>
<div data-testid={`order-reduce-only-value`}>
{order.reduceOnly ? t('Yes') : t('-')}
{order.reduceOnly ? t('Yes') : '-'}
</div>
</KeyValueTableRow>
<KeyValueTableRow key={'order-pegged'}>
<div data-testid={'order-pegged-label'}>{t('Pegged')}</div>
<div data-testid={`order-pegged-value`}>
{order.peggedOrder ? t('Yes') : t('-')}
{order.peggedOrder ? t('Yes') : '-'}
</div>
</KeyValueTableRow>
@@ -207,7 +208,7 @@ export const OrderViewDialog = ({
{t('Liquidity provision')}
</div>
<div data-testid={`order-liquidity-provision-value`}>
{order.liquidityProvision ? t('Yes') : t('-')}
{order.liquidityProvision ? t('Yes') : '-'}
</div>
</KeyValueTableRow>
</KeyValueTable>
@@ -217,7 +218,7 @@ export const OrderViewDialog = ({
{t('Iceberg order')}
</div>
<div data-testid={`order-iceberg-order-value`}>
{order.icebergOrder ? t('Yes') : t('-')}
{order.icebergOrder ? t('Yes') : '-'}
</div>
</KeyValueTableRow>
{order.icebergOrder && (
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import { useCallback, useEffect, useState } from 'react';
import { StopOrdersTable } from '../stop-orders-table/stop-orders-table';
import { type useDataGridEvents } from '@vegaprotocol/datagrid';
@@ -11,6 +10,7 @@ import {
type StopOrdersQueryVariables,
} from '../order-data-provider';
import { useVegaTransactionStore } from '@vegaprotocol/web3';
import { useT } from '../../use-t';
export interface StopOrdersManagerProps {
partyId: string;
@@ -27,6 +27,7 @@ export const StopOrdersManager = ({
isReadOnly,
gridProps,
}: StopOrdersManagerProps) => {
const t = useT();
const create = useVegaTransactionStore((state) => state.create);
const [viewOrder, setViewOrder] = useState<Order | null>(null);
const variables: StopOrdersQueryVariables = {
@@ -191,6 +191,7 @@ describe('StopOrdersTable', () => {
expect(cells[i]).toHaveTextContent(expectedValue)
);
});
it('formats status column', async () => {
await act(async () => {
render(generateJsx({ rowData }));
@@ -260,14 +261,13 @@ describe('StopOrdersTable', () => {
await act(async () => {
render(generateJsx({ rowData, onView }));
});
const dropdownMenuButtons = screen.getByTestId('dropdown-menu');
dropdownMenuButtons.click();
await user.click(dropdownMenuButtons as HTMLButtonElement);
const menuItems = screen.getAllByRole('menuitem');
const button = screen.getByTestId('icon-kebab');
await user.click(button);
const menuItems = await screen.findAllByRole('menuitem');
expect(menuItems).toHaveLength(2);
expect(menuItems[0]).toHaveTextContent('Copy order ID');
expect(menuItems[1]).toHaveTextContent('View order details');
menuItems[1].click();
await user.click(menuItems[1]);
expect(onView).toBeCalled();
});
});
@@ -3,9 +3,8 @@ import {
getDateTimeFormat,
isNumeric,
toBigNum,
formatTrigger,
useFormatTrigger,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import {
ActionsDropdown,
@@ -35,6 +34,7 @@ import type {
import type { StopOrder } from '../order-data-provider/stop-orders-data-provider';
import type { ColDef } from 'ag-grid-community';
import type { Order } from '../order-data-provider';
import { useT } from '../../use-t';
const defaultColDef = {
resizable: true,
@@ -51,6 +51,8 @@ export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & {
export const StopOrdersTable = memo(
({ onCancel, onMarketClick, onView, ...props }: StopOrdersTableProps) => {
const t = useT();
const formatTrigger = useFormatTrigger();
const showAllActions = !props.isReadOnly;
const columnDefs: ColDef[] = useMemo(
() => [
@@ -176,7 +178,7 @@ export const StopOrdersTable = memo(
{data.ocoLinkId && (
<Pill
size="xxs"
className="uppercase ml-0.5"
className="ml-0.5 uppercase"
title={t('One Cancels the Other')}
>
OCO
@@ -281,7 +283,15 @@ export const StopOrdersTable = memo(
},
},
],
[onCancel, onMarketClick, onView, props.isReadOnly, showAllActions]
[
onCancel,
onMarketClick,
onView,
props.isReadOnly,
showAllActions,
t,
formatTrigger,
]
);
return (
-1
View File
@@ -1,3 +1,2 @@
export * from './components';
export * from './order-hooks';
export * from './utils';
+2
View File
@@ -0,0 +1,2 @@
import { useTranslation } from 'react-i18next';
export const useT = () => useTranslation('orders').t;
-28
View File
@@ -1,28 +0,0 @@
import { timeInForceLabel } from './utils';
import * as Types from '@vegaprotocol/types';
describe('utils', () => {
describe('timeInForceLabel', () => {
it('should return the correct label for time in force', () => {
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe(
`Fill or Kill (FOK)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe(
`Good 'til Cancelled (GTC)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe(
`Immediate or Cancel (IOC)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe(
`Good 'til Time (GTT)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe(
`Good for Auction (GFA)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(
`Good for Normal (GFN)`
);
expect(timeInForceLabel('')).toBe('');
});
});
});
-22
View File
@@ -1,22 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
// More detail in https://docs.vega.xyz/mainnet/graphql/enums/order-time-in-force
export const timeInForceLabel = (tif: string) => {
switch (tif) {
case Schema.OrderTimeInForce.TIME_IN_FORCE_GTC:
return t(`Good 'til Cancelled (GTC)`);
case Schema.OrderTimeInForce.TIME_IN_FORCE_IOC:
return t('Immediate or Cancel (IOC)');
case Schema.OrderTimeInForce.TIME_IN_FORCE_FOK:
return t('Fill or Kill (FOK)');
case Schema.OrderTimeInForce.TIME_IN_FORCE_GTT:
return t(`Good 'til Time (GTT)`);
case Schema.OrderTimeInForce.TIME_IN_FORCE_GFN:
return t('Good for Normal (GFN)');
case Schema.OrderTimeInForce.TIME_IN_FORCE_GFA:
return t('Good for Auction (GFA)');
default:
return t(tif);
}
};
+4 -3
View File
@@ -1,7 +1,7 @@
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import { useEstimatePositionQuery } from './__generated__/Positions';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useT } from '../use-t';
export const LiquidationPrice = ({
marketId,
@@ -14,6 +14,7 @@ export const LiquidationPrice = ({
collateralAvailable: string;
decimalPlaces: number;
}) => {
const t = useT();
const { data: currentData, previousData } = useEstimatePositionQuery({
variables: {
marketId,
@@ -43,11 +44,11 @@ export const LiquidationPrice = ({
<tbody>
<tr>
<th>{t('Worst case')}</th>
<td className="pl-2 font-mono text-right">{worstCase}</td>
<td className="pl-2 text-right font-mono">{worstCase}</td>
</tr>
<tr>
<th>{t('Best case')}</th>
<td className="pl-2 font-mono text-right">{bestCase}</td>
<td className="pl-2 text-right font-mono">{bestCase}</td>
</tr>
</tbody>
</table>
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import {
ActionsDropdown,
TradingDropdownItem,
@@ -6,8 +5,10 @@ import {
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { useT } from '../use-t';
export const PositionActionsDropdown = ({ assetId }: { assetId: string }) => {
const t = useT();
const open = useAssetDetailsDialogStore((store) => store.open);
return (
+2 -1
View File
@@ -3,7 +3,6 @@ import { PositionsTable } from './positions-table';
import * as Schema from '@vegaprotocol/types';
import { useVegaTransactionStore } from '@vegaprotocol/web3';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
import {
positionsMetricsProvider,
@@ -11,6 +10,7 @@ import {
} from './positions-data-providers';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { MAXGOINT64 } from '@vegaprotocol/utils';
import { useT } from '../use-t';
interface PositionsManagerProps {
partyIds: string[];
@@ -27,6 +27,7 @@ export const PositionsManager = ({
gridProps,
showClosed = false,
}: PositionsManagerProps) => {
const t = useT();
const { pubKeys, pubKey } = useVegaWallet();
const create = useVegaTransactionStore((store) => store.create);
const onClose = useCallback(
+32 -13
View File
@@ -28,7 +28,6 @@ import {
addDecimalsFormatNumber,
addDecimalsFormatNumberQuantum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { type Position } from './positions-data-providers';
import {
MarketTradingMode,
@@ -38,6 +37,7 @@ import {
import { DocsLinks } from '@vegaprotocol/environment';
import { PositionActionsDropdown } from './position-actions-dropdown';
import { LiquidationPrice } from './liquidation-price';
import { useT } from '../use-t';
interface Props extends TypedDataAgGrid<Position> {
onClose?: (data: Position) => void;
@@ -81,6 +81,7 @@ export const PositionsTable = ({
pubKey,
...props
}: Props) => {
const t = useT();
return (
<AgGrid
overlayNoRowsTemplate={t('No positions')}
@@ -193,8 +194,8 @@ export const PositionsTable = ({
switch (args.data.status) {
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
secondaryTooltip = t(
`You did not have enough %s collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
args.data.assetSymbol
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
{ assetSymbol: args.data.assetSymbol }
);
break;
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
@@ -218,10 +219,12 @@ export const PositionsTable = ({
<p className="mb-2">{primaryTooltip}</p>
<p className="mb-2">{secondaryTooltip}</p>
<p className="mb-2">
{t(
'Status: %s',
PositionStatusMapping[args.data.status]
)}
{t('Status: {{status}}', {
nsSeparator: '*',
replace: {
status: PositionStatusMapping[args.data.status],
},
})}
</p>
{POSITION_RESOLUTION_LINK && (
<ExternalLink href={POSITION_RESOLUTION_LINK}>
@@ -386,18 +389,26 @@ export const PositionsTable = ({
value={
<>
<p className="mb-2">
{t('Realised PNL: %s', args.value)}
{t('Realised PNL: {{value}}', {
nsSeparator: '*',
replace: { value: args.value },
})}
</p>
<p className="mb-2">
{t(
'Lifetime loss socialisation deductions: %s',
lossesFormatted
'Lifetime loss socialisation deductions: {{losses}}',
{
nsSeparator: '*',
replace: {
losses: lossesFormatted,
},
}
)}
</p>
<p className="mb-2">
{t(
`You received less %s in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.`,
args.data.assetSymbol
`You received less {{assetSymbol}} in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.`,
{ assetSymbol: args.data.assetSymbol }
)}
</p>
{LOSS_SOCIALIZATION_LINK && (
@@ -481,7 +492,15 @@ export const PositionsTable = ({
return columnDefs.filter<ColDef>(
(colDef: ColDef | null): colDef is ColDef => colDef !== null
);
}, [isReadOnly, multipleKeys, onClose, onMarketClick, pubKey, pubKeys])}
}, [
isReadOnly,
multipleKeys,
onClose,
onMarketClick,
pubKey,
pubKeys,
t,
])}
{...props}
/>
);
+13
View File
@@ -1,4 +1,17 @@
import '@testing-library/jest-dom';
import ResizeObserver from 'resize-observer-polyfill';
import { locales } from '@vegaprotocol/i18n';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
// Set up i18n instance so that components have the correct default
// en translations
i18n.use(initReactI18next).init({
// we init with resources
resources: locales,
fallbackLng: 'en',
ns: ['positions'],
defaultNS: 'positions',
});
global.ResizeObserver = ResizeObserver;
+3
View File
@@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const ns = 'positions';
export const useT = () => useTranslation(ns).t;
@@ -85,7 +85,6 @@ describe('ProposalsList', () => {
'Settlement asset',
'State',
'Parent market',
'Voting',
'Closing date',
'Enactment date',
'', // actions col
@@ -1,8 +1,6 @@
import { useMemo } from 'react';
import BigNumber from 'bignumber.js';
import type { ColDef } from 'ag-grid-community';
import {
CenteredGridCellWrapper,
COL_DEFS,
DateRangeFilter,
SetFilter,
@@ -11,33 +9,18 @@ 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 {
ProductTypeMapping,
ProductTypeShortName,
ProposalProductTypeShortName,
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([
{
@@ -54,27 +37,38 @@ export const useColumnDefs = () => {
}) => {
if (!value || !data) return '-';
// TODO: update when we switch to ProductConfiguration
const productType = 'Future';
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);
return (
<StackedCell
primary={value}
secondary={
<span
title={ProductTypeMapping[productType]}
className="uppercase"
>
{ProductTypeShortName[productType]}
</span>
}
/>
productType && (
<StackedCell
primary={value}
secondary={
<span
title={ProposalProductTypeShortName[productType]}
className="uppercase"
>
{ProposalProductTypeShortName[productType]}
</span>
}
/>
)
);
},
},
{
colId: 'asset',
headerName: t('Settlement asset'),
field: 'terms.change.instrument.futureProduct.settlementAsset.symbol',
field: 'terms.change.instrument.product.settlementAsset.symbol',
},
{
colId: 'state',
@@ -94,32 +88,6 @@ 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'),
@@ -156,7 +124,7 @@ export const useColumnDefs = () => {
},
},
]);
}, [requiredMajorityPercentage]);
}, []);
return columnDefs;
};
@@ -16,78 +16,90 @@ fragment NewMarketFields on NewMarket {
instrument {
name
code
futureProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
dataSourceSpecForSettlementData {
sourceType {
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
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
}
}
}
}
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
dataSourceSpecForTradingTermination {
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
}
}
}
}
}
}
}
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
}
}
dataSourceSpecBinding {
settlementDataProperty
tradingTerminationProperty
... on PerpetualProduct {
settlementAsset {
id
name
symbol
decimals
quantum
}
quoteName
}
}
}
File diff suppressed because one or more lines are too long
@@ -128,7 +128,7 @@ export const createProposalListFieldsFragment = (
instrument: {
code: 'ETHUSD',
name: 'ETHUSD',
futureProduct: {
product: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -262,7 +262,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'ETHUSD',
name: 'ETHUSD',
futureProduct: {
product: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -352,7 +352,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'LINKUSD',
name: 'LINKUSD',
futureProduct: {
product: {
settlementAsset: {
id: 'eb30d55e90e1f9e5c4727d6fa2a5a8cd36ab9ae9738eb8f3faf53e2bee4861ee',
name: 'mUSDT-II',
@@ -442,7 +442,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'LINKUSD',
name: 'LINKUSD',
futureProduct: {
product: {
settlementAsset: {
id: 'eb30d55e90e1f9e5c4727d6fa2a5a8cd36ab9ae9738eb8f3faf53e2bee4861ee',
name: 'mUSDT-II',
@@ -532,7 +532,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'ETHUSD',
name: 'ETHUSD',
futureProduct: {
product: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -622,7 +622,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'LINKUSD',
name: 'LINKUSD',
futureProduct: {
product: {
settlementAsset: {
id: 'eb30d55e90e1f9e5c4727d6fa2a5a8cd36ab9ae9738eb8f3faf53e2bee4861ee',
name: 'mUSDT-II',
@@ -712,7 +712,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'ETHDAI.MF21',
name: 'ETHDAI Monthly (Dec 2022)',
futureProduct: {
product: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -802,7 +802,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'AAPL.MF21',
name: 'Apple Monthly (Dec 2022)',
futureProduct: {
product: {
settlementAsset: {
id: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
name: 'tUSDC TEST',
@@ -892,7 +892,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'BTCUSD.MF21',
name: 'BTCUSD Monthly (Dec 2022)',
futureProduct: {
product: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -982,7 +982,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'TSLA.QM21',
name: 'Tesla Quarterly (Feb 2023)',
futureProduct: {
product: {
settlementAsset: {
id: '177e8f6c25a955bd18475084b99b2b1d37f28f3dec393fab7755a7e69c3d8c3b',
name: 'tEURO TEST',
@@ -1072,7 +1072,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'AAVEDAI.MF21',
name: 'AAVEDAI Monthly (Dec 2022)',
futureProduct: {
product: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
@@ -1162,7 +1162,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'ETHBTC.QM21',
name: 'ETHBTC Quarterly (Feb 2023)',
futureProduct: {
product: {
settlementAsset: {
id: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
name: 'tBTC TEST',
@@ -1252,7 +1252,7 @@ const proposalListFields: ProposalListFieldsFragment[] = [
instrument: {
code: 'UNIDAI.MF21',
name: 'UNIDAI Monthly (Dec 2022)',
futureProduct: {
product: {
settlementAsset: {
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
name: 'tDAI TEST',
+14
View File
@@ -0,0 +1,14 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
let translatedLabel = label;
if (typeof replacements === 'object' && replacements !== null) {
Object.keys(replacements).forEach((key) => {
translatedLabel = translatedLabel.replace(
`{{${key}}}`,
replacements[key]
);
});
}
return translatedLabel;
},
});
+33 -23
View File
@@ -1,27 +1,37 @@
import * as Schema from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import { addDecimalsFormatNumber } from './number';
import { useCallback } from 'react';
import { useT } from '../use-t';
export const formatTrigger = (
data: Pick<Schema.StopOrder, 'trigger' | 'triggerDirection'> | undefined,
marketDecimalPlaces: number,
defaultValue = '-'
) => {
if (data && data?.trigger?.__typename === 'StopOrderPrice') {
return `${t('Mark')} ${
data?.triggerDirection ===
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
? '<'
: '>'
} ${addDecimalsFormatNumber(data.trigger.price, marketDecimalPlaces)}`;
}
if (data && data?.trigger?.__typename === 'StopOrderTrailingPercentOffset') {
return `${t('Mark')} ${
data?.triggerDirection ===
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
? '+'
: '-'
}${(Number(data?.trigger.trailingPercentOffset) * 100).toFixed(1)}%`;
}
return defaultValue;
export const useFormatTrigger = () => {
const t = useT();
return useCallback(
(
data: Pick<Schema.StopOrder, 'trigger' | 'triggerDirection'> | undefined,
marketDecimalPlaces: number,
defaultValue = '-'
) => {
if (data && data?.trigger?.__typename === 'StopOrderPrice') {
return `${t('Mark')} ${
data?.triggerDirection ===
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
? '<'
: '>'
} ${addDecimalsFormatNumber(data.trigger.price, marketDecimalPlaces)}`;
}
if (
data &&
data?.trigger?.__typename === 'StopOrderTrailingPercentOffset'
) {
return `${t('Mark')} ${
data?.triggerDirection ===
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
? '+'
: '-'
}${(Number(data?.trigger.trailingPercentOffset) * 100).toFixed(1)}%`;
}
return defaultValue;
},
[t]
);
};
+8 -3
View File
@@ -1,7 +1,7 @@
import { MarketState } from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import { isValid, parseISO } from 'date-fns';
import { getDateTimeFormat } from './format';
import { useT } from './use-t';
export const getMarketExpiryDate = (
tags?: ReadonlyArray<string> | null
@@ -40,12 +40,15 @@ export const getExpiryDate = (
close: string | null,
state: MarketState
): string => {
const t = useT();
const metadataExpiryDate = getMarketExpiryDate(tags);
const marketTimestampCloseDate = close && new Date(close);
let content = null;
if (!metadataExpiryDate) {
content = marketTimestampCloseDate
? `Expired on ${getDateTimeFormat().format(marketTimestampCloseDate)}`
? t('Expired on {{date}}', {
date: getDateTimeFormat().format(marketTimestampCloseDate),
})
: t('Not time-based');
} else {
const isExpired =
@@ -54,7 +57,9 @@ export const getExpiryDate = (
state === MarketState.STATE_SETTLED);
if (isExpired) {
content = marketTimestampCloseDate
? `Expired on ${getDateTimeFormat().format(marketTimestampCloseDate)}`
? t('Expired on {{date}}', {
date: getDateTimeFormat().format(marketTimestampCloseDate),
})
: t('Expired');
} else {
content = getDateTimeFormat().format(metadataExpiryDate);
+3
View File
@@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const ns = 'utils';
export const useT = () => useTranslation(ns).t;
+8 -1
View File
@@ -1,6 +1,10 @@
import { ethereumAddress, vegaPublicKey } from './common';
import { renderHook } from '@testing-library/react';
import { useEthereumAddress, useVegaPublicKey } from './common';
it('ethereumAddress', () => {
const result = renderHook(useEthereumAddress);
const ethereumAddress = result.result.current;
const errorMessage = 'Invalid Ethereum address';
const validAddress = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
@@ -17,6 +21,9 @@ it('ethereumAddress', () => {
});
it('vegaPublicKey', () => {
const result = renderHook(useVegaPublicKey);
const vegaPublicKey = result.result.current;
const errorMessage = 'Invalid Vega key';
const validKey =
+70 -33
View File
@@ -1,40 +1,71 @@
import BigNumber from 'bignumber.js';
import { t } from '@vegaprotocol/i18n';
import { useT } from '../use-t';
import { useCallback } from 'react';
export const required = (value: string) => {
if (value === null || value === undefined || value === '') {
return t('Required');
}
return true;
export const useRequired = () => {
const t = useT();
return useCallback(
(value: string) => {
if (value === null || value === undefined || value === '') {
return t('Required');
}
return true;
},
[t]
);
};
export const ethereumAddress = (value: string) => {
if (!/^0x[0-9a-fA-F]{40}$/i.test(value)) {
return t('Invalid Ethereum address');
}
return true;
export const useEthereumAddress = () => {
const t = useT();
return useCallback(
(value: string) => {
if (!/^0x[0-9a-fA-F]{40}$/i.test(value)) {
return t('Invalid Ethereum address');
}
return true;
},
[t]
);
};
export const VEGA_ID_REGEX = /^[A-Fa-f0-9]{64}$/i;
export const vegaPublicKey = (value: string) => {
if (!VEGA_ID_REGEX.test(value)) {
return t('Invalid Vega key');
}
return true;
export const useVegaPublicKey = () => {
const t = useT();
return useCallback(
(value: string) => {
if (!VEGA_ID_REGEX.test(value)) {
return t('Invalid Vega key');
}
return true;
},
[t]
);
};
export const minSafe = (min: BigNumber) => (value: string) => {
if (new BigNumber(value).isLessThan(min)) {
return t('Value is below minimum');
}
return true;
export const useMinSafe = () => {
const t = useT();
return useCallback(
(min: BigNumber) => (value: string) => {
if (new BigNumber(value).isLessThan(min)) {
return t('Value is below minimum');
}
return true;
},
[t]
);
};
export const maxSafe = (max: BigNumber) => (value: string) => {
if (new BigNumber(value).isGreaterThan(max)) {
return t('Value is above maximum');
}
return true;
export const useMaxSafe = () => {
const t = useT();
return useCallback(
(max: BigNumber) => (value: string) => {
if (new BigNumber(value).isGreaterThan(max)) {
return t('Value is above maximum');
}
return true;
},
[t]
);
};
export const suitableForSyntaxHighlighter = (str: string) => {
@@ -46,11 +77,17 @@ export const suitableForSyntaxHighlighter = (str: string) => {
}
};
export const validateJson = (value: string) => {
try {
JSON.parse(value);
return true;
} catch (e) {
return t('Must be valid JSON');
}
export const useValidateJson = () => {
const t = useT();
return useCallback(
(value: string) => {
try {
JSON.parse(value);
return true;
} catch (e) {
return t('Must be valid JSON');
}
},
[t]
);
};
+37 -19
View File
@@ -1,22 +1,40 @@
import { t } from '@vegaprotocol/i18n';
import { useCallback } from 'react';
import { useT } from '../use-t';
export const validateAmount = (step: number | string, field: string) => {
const [, stepDecimals = ''] = String(step).split('.');
export const useValidateAmount = () => {
const t = useT();
return useCallback(
(step: number | string, field: string) => {
const [, stepDecimals = ''] = String(step).split('.');
return (value?: string) => {
if (Number(step) > 1) {
if (Number(value) % Number(step) > 0) {
return t(`${field} must be a multiple of ${step} for this market`);
}
return true;
}
const [, valueDecimals = ''] = (value || '').split('.');
if (stepDecimals.length < valueDecimals.length) {
if (stepDecimals === '') {
return t(`${field} must be whole numbers for this market`);
}
return t(`${field} accepts up to ${stepDecimals.length} decimal places`);
}
return true;
};
return (value?: string) => {
if (Number(step) > 1) {
if (Number(value) % Number(step) > 0) {
return t(
'{{field}} must be a multiple of {{step}} for this market',
{
field,
step,
}
);
}
return true;
}
const [, valueDecimals = ''] = (value || '').split('.');
if (stepDecimals.length < valueDecimals.length) {
if (stepDecimals === '') {
return t('{{field}} must be whole numbers for this market', {
field,
});
}
return t('{{field}} accepts up to {{decimals}} decimal places', {
field,
decimals: stepDecimals.length,
});
}
return true;
};
},
[t]
);
};
@@ -1,11 +1,12 @@
import { t } from '@vegaprotocol/i18n';
import { Link } from '@vegaprotocol/ui-toolkit';
import { EtherscanLink, useEnvironment } from '@vegaprotocol/environment';
import { EthTxStatus } from '../use-ethereum-transaction';
import { useT } from '../use-t';
const ACTIVE_CLASSES = 'text-black dark:text-white';
export const ConfirmRow = ({ status }: { status: EthTxStatus }) => {
const t = useT();
if (status === EthTxStatus.Requested) {
return (
<p className="text-black dark:text-white">
@@ -32,6 +33,7 @@ export const TxRow = ({
requiredConfirmations,
highlightComplete = true,
}: TxRowProps) => {
const t = useT();
const { ETHERSCAN_URL } = useEnvironment();
if (status === EthTxStatus.Pending) {
@@ -39,7 +41,8 @@ export const TxRow = ({
<p className={`flex justify-between ${ACTIVE_CLASSES}`}>
<span>
{t(
`Awaiting Ethereum transaction ${confirmations}/${requiredConfirmations} confirmations...`
`Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...`,
{ confirmations, requiredConfirmations }
)}
</span>
<Link
@@ -82,6 +85,7 @@ interface ConfirmationEventRowProps {
}
export const ConfirmationEventRow = ({ status }: ConfirmationEventRowProps) => {
const t = useT();
if (status !== EthTxStatus.Complete && status !== EthTxStatus.Confirmed) {
return <p>{t('Vega confirmation')}</p>;
}
@@ -1,9 +1,9 @@
import { t } from '@vegaprotocol/i18n';
import { Button, Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit';
import { isEthereumError } from '../ethereum-error';
import type { EthTxState, TxError } from '../use-ethereum-transaction';
import { EthTxStatus } from '../use-ethereum-transaction';
import { ConfirmRow, TxRow, ConfirmationEventRow } from './dialog-rows';
import { useT } from '../use-t';
export interface EthereumTransactionDialogProps {
title: string;
@@ -20,13 +20,14 @@ export const EthereumTransactionDialog = ({
onChange,
requiredConfirmations = 1,
}: EthereumTransactionDialogProps) => {
const t = useT();
const { status, error, confirmations, txHash } = transaction;
return (
<Dialog
open={transaction.dialogOpen}
onChange={onChange}
size="small"
{...getWrapperProps(title, status)}
{...getWrapperProps(title, status, t)}
>
<TransactionContent
status={status}
@@ -44,11 +45,13 @@ export const getTransactionContent = ({
transaction,
requiredConfirmations,
reset,
t,
}: {
title: string;
transaction: EthTxState;
requiredConfirmations?: number;
reset: () => void;
t: ReturnType<typeof useT>;
}) => {
const { status, error, confirmations, txHash } = transaction;
const content = ({ returnLabel }: { returnLabel?: string }) => (
@@ -75,7 +78,7 @@ export const getTransactionContent = ({
</>
);
return {
...getWrapperProps(title, status),
...getWrapperProps(title, status, t),
status,
Content: content,
};
@@ -94,6 +97,7 @@ export const TransactionContent = ({
confirmations: number;
requiredConfirmations?: number;
}) => {
const t = useT();
if (status === EthTxStatus.Error) {
let errorMessage = '';
@@ -105,7 +109,10 @@ export const TransactionContent = ({
return (
<p className="break-all">
{t('Error')}: {errorMessage}
{t('Error: {{errorMessage}}', {
nsSeparator: '*',
replace: { errorMessage },
})}
</p>
);
}
@@ -128,7 +135,8 @@ export const TransactionContent = ({
type WrapperProps = { title: string; icon?: JSX.Element; intent?: Intent };
export const getWrapperProps = (
title: string,
status: EthTxStatus
status: EthTxStatus,
t: ReturnType<typeof useT>
): WrapperProps => {
const propsMap = {
[EthTxStatus.Default]: {
@@ -137,17 +145,17 @@ export const getWrapperProps = (
intent: undefined,
},
[EthTxStatus.Error]: {
title: t(`${title} failed`),
title: t(`{{title}} failed`, { title }),
icon: <Icon name="warning-sign" />,
intent: Intent.Danger,
},
[EthTxStatus.Requested]: {
title: t('Confirm transaction'),
title: t('Confirm transaction', { title }),
icon: <Icon name="hand-up" />,
intent: Intent.Warning,
},
[EthTxStatus.Pending]: {
title: t(`${title} pending`),
title: t(`{{title}} pending`, { title }),
icon: (
<span className="mt-1">
<Loader size="small" />
@@ -156,12 +164,12 @@ export const getWrapperProps = (
intent: Intent.None,
},
[EthTxStatus.Complete]: {
title: t(`${title} pending`),
title: t(`{{title}} pending`, { title }),
icon: <Loader size="small" />,
intent: Intent.None,
},
[EthTxStatus.Confirmed]: {
title: t(`${title} complete`),
title: t(`{{title}} complete`, { title }),
icon: <Icon name="tick" />,
intent: Intent.Success,
},
@@ -3,7 +3,6 @@ import { useEffect } from 'react';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { EtherscanLink } from '@vegaprotocol/environment';
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { Panel } from '@vegaprotocol/ui-toolkit';
@@ -17,6 +16,7 @@ import { EthTxStatus } from './use-ethereum-transaction';
import { isEthereumError } from './ethereum-error';
import { TransactionContent } from './ethereum-transaction-dialog';
import { useEthTransactionStore } from './use-ethereum-transaction-store';
import { useT } from './use-t';
const intentMap: { [s in EthTxStatus]: Intent } = {
Default: Intent.Primary,
@@ -34,6 +34,7 @@ const isDepositTransaction = (tx: EthStoredTxState) =>
tx.methodName === 'deposit_asset';
const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
const t = useT();
const { data: assets } = useAssetsDataProvider();
if (!assets) return null;
@@ -64,8 +65,10 @@ const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
{tx.status === EthTxStatus.Pending && (
<>
<p className="mt-[2px]">
{t('Awaiting confirmations')}{' '}
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
{t(
'Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}',
tx
)}
</p>
<ProgressBar
value={(tx.confirmations / tx.requiredConfirmations) * 100}
@@ -84,6 +87,7 @@ type EthTxToastContentProps = {
};
const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
return (
<>
<ToastHeading>{t('Action required')}</ToastHeading>
@@ -98,6 +102,7 @@ const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
};
const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
return (
<>
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
@@ -111,6 +116,7 @@ const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
};
const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
let errorMessage = '';
if (isEthereumError(tx.error)) {
@@ -128,6 +134,7 @@ const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
};
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
return (
<>
<ToastHeading>{t('Transaction confirmed')}</ToastHeading>
@@ -141,11 +148,12 @@ const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
};
const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
const isDeposit = isDepositTransaction(tx);
return (
<>
<ToastHeading>
{t('Processing')} {isDeposit && t('deposit')}
{isDeposit ? t('Processing deposit') : t('Processing')}
</ToastHeading>
<p>
{t('Your transaction has been completed.')}{' '}
@@ -8,6 +8,7 @@ import {
EthereumTransactionDialog,
getTransactionContent,
} from './ethereum-transaction-dialog';
import { useT } from './use-t';
export enum EthTxStatus {
Default = 'Default',
@@ -51,6 +52,7 @@ export const useEthereumTransaction = <
requiredConfirmations = 1,
requiresConfirmation = false
) => {
const t = useT();
const [transaction, _setTransaction] = useState<EthTxState>(initialState);
const setTransaction = useCallback((update: Partial<EthTxState>) => {
@@ -173,8 +175,9 @@ export const useEthereumTransaction = <
transaction,
requiredConfirmations,
reset,
t,
}),
[methodName, requiredConfirmations, reset, transaction]
[methodName, requiredConfirmations, reset, transaction, t]
);
return { perform, transaction, reset, setConfirmed, Dialog, TxContent };
@@ -1,5 +1,4 @@
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { Panel } from '@vegaprotocol/ui-toolkit';
@@ -15,6 +14,7 @@ import {
} from './use-ethereum-withdraw-approvals-store';
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
import { VerificationStatus } from './withdrawal-approval-status';
import { useT } from './use-t';
const intentMap: { [s in ApprovalStatus]: Intent } = {
Pending: Intent.Warning,
@@ -29,6 +29,7 @@ const EthWithdrawalApprovalToastContent = ({
}: {
tx: EthWithdrawalApprovalState;
}) => {
const t = useT();
const isConnectionFailure =
tx.failureReason &&
[
@@ -54,14 +55,17 @@ const EthWithdrawalApprovalToastContent = ({
title = t('Approved');
}
const num = formatNumber(
const amount = formatNumber(
toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals),
tx.withdrawal.asset.decimals
);
const details = isConnectionFailure ? null : (
<Panel>
<strong>
{t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
{t('Withdraw {{amount}} {{symbol}}', {
amount,
symbol: tx.withdrawal.asset.symbol,
})}
</strong>
</Panel>
);
@@ -4,7 +4,6 @@ import { useEffect, useRef } from 'react';
import { addDecimal } from '@vegaprotocol/utils';
import { useGetWithdrawThreshold } from './use-get-withdraw-threshold';
import { useGetWithdrawDelay } from './use-get-withdraw-delay';
import { t } from '@vegaprotocol/i18n';
import { localLoggerFactory } from '@vegaprotocol/logger';
import { CollateralBridge } from '@vegaprotocol/smart-contracts';
@@ -21,8 +20,10 @@ import {
useEthWithdrawApprovalsStore,
WithdrawalFailure,
} from './use-ethereum-withdraw-approvals-store';
import { useT } from './use-t';
export const useEthWithdrawApprovalsManager = () => {
const t = useT();
const getThreshold = useGetWithdrawThreshold();
const getDelay = useGetWithdrawDelay();
const { query } = useApolloClient();
@@ -49,9 +50,12 @@ export const useEthWithdrawApprovalsManager = () => {
if (withdrawal.asset.source.__typename !== 'ERC20') {
update(transaction.id, {
status: ApprovalStatus.Error,
message: t(
`Invalid asset source: ${withdrawal.asset.source.__typename}`
),
message: t(`Invalid asset source: {{source}}`, {
nsSeparator: '*',
replace: {
source: withdrawal.asset.source.__typename,
},
}),
failureReason: WithdrawalFailure.InvalidAsset,
});
return;
@@ -162,5 +166,6 @@ export const useEthWithdrawApprovalsManager = () => {
transaction,
update,
chainId,
t,
]);
};
+3
View File
@@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const ns = 'web3';
export const useT = () => useTranslation(ns).t;
@@ -418,32 +418,33 @@ describe('getVegaTransactionContentIntent', () => {
});
});
describe('getOrderToastTitle', () => {
const t = (v: string) => v;
it('should return the correct title', () => {
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE, t)).toBe(
'Order submitted'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED, t)).toBe(
'Order filled'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED)).toBe(
'Order partially filled'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED)).toBe(
expect(
getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED, t)
).toBe('Order partially filled');
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED, t)).toBe(
'Order parked'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED, t)).toBe(
'Order stopped'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED, t)).toBe(
'Order cancelled'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED, t)).toBe(
'Order expired'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED, t)).toBe(
'Order rejected'
);
expect(getOrderToastTitle(undefined)).toBe(undefined);
expect(getOrderToastTitle(undefined, t)).toBe(undefined);
});
});
@@ -480,38 +481,42 @@ describe('getOrderToastIntent', () => {
describe('getRejectionReason', () => {
it('should return the correct rejection reason for insufficient asset balance', () => {
expect(
getRejectionReason({
rejectionReason:
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
status: Types.OrderStatus.STATUS_REJECTED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
remaining: '',
})
getRejectionReason(
{
rejectionReason:
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
status: Types.OrderStatus.STATUS_REJECTED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
remaining: '',
},
(v) => v
)
).toBe('Insufficient asset balance');
});
it('should return the correct rejection reason when order is stopped', () => {
expect(
getRejectionReason({
rejectionReason: null,
status: Types.OrderStatus.STATUS_STOPPED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
remaining: '',
})
).toBe(
'Your Fill or Kill (FOK) order was not filled and it has been stopped'
);
getRejectionReason(
{
rejectionReason: null,
status: Types.OrderStatus.STATUS_STOPPED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
remaining: '',
},
(v) => v
)
).toBe('Your {{timeInForce}} order was not filled and it has been stopped');
});
});
+122 -78
View File
@@ -40,10 +40,9 @@ import {
formatNumber,
toBigNum,
truncateByChars,
formatTrigger,
useFormatTrigger,
MAXGOINT64,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useAssetsMapProvider } from '@vegaprotocol/assets';
import { useEthWithdrawApprovalsStore } from './use-ethereum-withdraw-approvals-store';
import { DApp, EXPLORER_TX, useLinks } from '@vegaprotocol/environment';
@@ -58,26 +57,31 @@ import { OrderStatusMapping } from '@vegaprotocol/types';
import { Size } from '@vegaprotocol/datagrid';
import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
import * as Schema from '@vegaprotocol/types';
import { Trans } from 'react-i18next';
import { useT } from './use-t';
export const getRejectionReason = (
order: OrderTxUpdateFieldsFragment
order: OrderTxUpdateFieldsFragment,
t: ReturnType<typeof useT>
): string | null => {
switch (order.status) {
case Schema.OrderStatus.STATUS_STOPPED:
return t(
`Your ${
Schema.OrderTimeInForceMapping[order.timeInForce]
} order was not filled and it has been stopped`
`Your {{timeInForce}} order was not filled and it has been stopped`,
{
timeInForce: Schema.OrderTimeInForceMapping[order.timeInForce],
}
);
default:
return order.rejectionReason
? t(Schema.OrderRejectionReasonMapping[order.rejectionReason])
? Schema.OrderRejectionReasonMapping[order.rejectionReason]
: '';
}
};
export const getOrderToastTitle = (
status?: Schema.OrderStatus
status: Schema.OrderStatus | undefined,
t: ReturnType<typeof useT>
): string | undefined => {
if (!status) {
return;
@@ -212,6 +216,7 @@ const SubmitOrderDetails = ({
data: OrderSubmission;
order?: OrderTxUpdateFieldsFragment;
}) => {
const t = useT();
const { data: markets } = useMarketsMapProvider();
const market = markets?.[order?.marketId || ''];
if (!market) return null;
@@ -224,9 +229,9 @@ const SubmitOrderDetails = ({
<Panel>
<h4>
{order
? t(
`Submit order - ${OrderStatusMapping[order.status].toLowerCase()}`
)
? t(`Submit order - {{status}}`, {
status: OrderStatusMapping[order.status].toLowerCase(),
})
: t('Submit order')}
</h4>
<p>{market?.tradableInstrument.instrument.code}</p>
@@ -255,6 +260,7 @@ const SubmitStopOrderSetup = ({
triggerDirection: Schema.StopOrderTriggerDirection;
market: Market;
}) => {
const formatTrigger = useFormatTrigger();
if (!market || !stopOrderSetup) return null;
const { price, size, side } = stopOrderSetup.orderSubmission;
@@ -294,6 +300,7 @@ const SubmitStopOrderSetup = ({
};
const SubmitStopOrderDetails = ({ data }: { data: StopOrdersSubmission }) => {
const t = useT();
const { data: markets } = useMarketsMapProvider();
const marketId =
data.fallsBelow?.orderSubmission.marketId ||
@@ -335,6 +342,7 @@ const EditOrderDetails = ({
data: OrderAmendment;
order?: OrderTxUpdateFieldsFragment;
}) => {
const t = useT();
const { data: orderById } = useOrderByIdQuery({
variables: { orderId: data.orderId },
fetchPolicy: 'no-cache',
@@ -376,7 +384,9 @@ const EditOrderDetails = ({
<Panel title={data.orderId}>
<h4>
{order
? t(`Edit order - ${OrderStatusMapping[order.status].toLowerCase()}`)
? t(`Edit order - {{status}}`, {
status: OrderStatusMapping[order.status].toLowerCase(),
})
: t('Edit order')}
</h4>
<p>{market?.tradableInstrument.instrument.code}</p>
@@ -395,6 +405,7 @@ const CancelOrderDetails = ({
orderId: string;
order?: OrderTxUpdateFieldsFragment;
}) => {
const t = useT();
const { data: orderById } = useOrderByIdQuery({
variables: { orderId },
});
@@ -421,9 +432,9 @@ const CancelOrderDetails = ({
<Panel title={orderId}>
<h4>
{order
? t(
`Cancel order - ${OrderStatusMapping[order.status].toLowerCase()}`
)
? t(`Cancel order - {{status}}`, {
status: OrderStatusMapping[order.status].toLowerCase(),
})
: t('Cancel order')}
</h4>
<p>{market?.tradableInstrument.instrument.code}</p>
@@ -435,6 +446,8 @@ const CancelOrderDetails = ({
};
const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => {
const t = useT();
const formatTrigger = useFormatTrigger();
const { data: orderById } = useStopOrderByIdQuery({
variables: { stopOrderId },
});
@@ -473,6 +486,7 @@ const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => {
};
export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
const t = useT();
const { data: assets } = useAssetsMapProvider();
const { data: markets } = useMarketsMapProvider();
@@ -480,14 +494,17 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
const transactionDetails = tx.body;
const asset = assets?.[transactionDetails.withdrawSubmission.asset];
if (asset) {
const num = formatNumber(
const amount = formatNumber(
toBigNum(transactionDetails.withdrawSubmission.amount, asset.decimals),
asset.decimals
);
return (
<Panel>
<strong>
{t('Withdraw')} {num} {asset.symbol}
{t('Withdraw {{amount}} {{symbol}}', {
amount,
symbol: asset.symbol,
})}
</strong>
</Panel>
);
@@ -526,7 +543,10 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
if (marketName) {
return (
<Panel>
{t('Cancel all orders for')} <strong>{marketName}</strong>
<Trans
defaults="Cancel all orders for <strong>{{marketName}}</strong>"
values={{ marketName }}
/>
</Panel>
);
}
@@ -556,7 +576,10 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
if (marketName) {
return (
<Panel>
{t('Cancel all stop orders for')} <strong>{marketName}</strong>
<Trans
defaults="Cancel all stop orders for <strong>{{marketName}}</strong>"
values={{ marketName }}
/>
</Panel>
);
}
@@ -584,8 +607,12 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
if (market) {
return (
<Panel>
{t('Close position for')}{' '}
<strong>{market.tradableInstrument.instrument.code}</strong>
<Trans
defaults="Close position for <strong>{{instrumentCode}}</strong>"
values={{
instrumentCode: market.tradableInstrument.instrument.code,
}}
/>
{tx.order?.remaining && (
<p>
{t('Filled')}{' '}
@@ -621,9 +648,7 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
return (
<Panel>
<h4>{t('Transfer')}</h4>
<p>
{t('To')} {truncateByChars(to)}
</p>
<p>{t('To {{address}}', { address: truncateByChars(to) })}</p>
<p>
{value} {transferAsset.symbol}
</p>
@@ -637,19 +662,25 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
type VegaTxToastContentProps = { tx: VegaStoredTxState };
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => (
<>
<ToastHeading>{t('Action required')}</ToastHeading>
<p>
{t(
'Please go to your Vega wallet application and approve or reject the transaction.'
)}
</p>
<VegaTransactionDetails tx={tx} />
</>
);
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => {
const t = useT();
return (
<>
<ToastHeading>{t('Action required')}</ToastHeading>
<p>
{t(
'Please go to your Vega wallet application and approve or reject the transaction.'
)}
</p>
<VegaTransactionDetails tx={tx} />
</>
);
};
const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
const VegaTxPendingToastContentProps = (
{ tx }: VegaTxToastContentProps,
t: ReturnType<typeof useT>
) => {
const explorerLink = useLinks(DApp.Explorer);
return (
<>
@@ -671,6 +702,7 @@ const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
};
const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
const t = useT();
const { createEthWithdrawalApproval } = useEthWithdrawApprovalsStore(
(state) => ({
createEthWithdrawalApproval: state.create,
@@ -696,31 +728,13 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
</p>
);
const dialogTrigger = (
// It has to stay as <a> due to the word breaking issue
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a
href="#"
className="inline underline underline-offset-4 cursor-pointer text-inherit break-words"
data-testid="toast-withdrawal-details"
onClick={(e) => {
e.preventDefault();
if (tx.withdrawal?.id) {
useWithdrawalApprovalDialog.getState().open(tx.withdrawal?.id);
}
}}
>
{t('save your withdrawal details')}
</a>
);
return (
<>
<ToastHeading>{t('Funds unlocked')}</ToastHeading>
<p>{t('Your funds have been unlocked for withdrawal.')}</p>
{tx.txHash && (
<ExternalLink
className="block mb-[5px] break-all"
className="mb-[5px] block break-all"
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
rel="noreferrer"
>
@@ -729,7 +743,28 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
)}
{/* TODO: Delay message - This withdrawal is subject to a delay. Come back in 5 days to complete the withdrawal. */}
<p className="break-words">
{t('You can')} {dialogTrigger} {t('for extra security.')}
<Trans
defaults="You can <0>save your withdrawal details</0> for extra security."
components={[
// It has to stay as <a> due to the word breaking issue
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a
href="#"
className="inline underline underline-offset-4 cursor-pointer text-inherit break-words"
data-testid="toast-withdrawal-details"
onClick={(e) => {
e.preventDefault();
if (tx.withdrawal?.id) {
useWithdrawalApprovalDialog
.getState()
.open(tx.withdrawal?.id);
}
}}
>
save your withdrawal details
</a>,
]}
/>
</p>
<VegaTransactionDetails tx={tx} />
{completeWithdrawalButton}
@@ -738,26 +773,31 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
}
if (tx.order && tx.order.rejectionReason) {
const rejectionReason = getRejectionReason(tx.order);
const rejectionReason = getRejectionReason(tx.order, t);
return (
<>
<ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading>
<ToastHeading>{getOrderToastTitle(tx.order.status, t)}</ToastHeading>
{rejectionReason ? (
<p>
{t('Your order has been %s because: %s', [
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? 'stopped'
: 'rejected',
rejectionReason,
])}
{tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? t('Your order has been stopped because: {{rejectionReason}}', {
nsSeparator: '*',
replace: {
rejectionReason,
},
})
: t('Your order has been rejected because: {{rejectionReason}}', {
nsSeparator: '*',
replace: {
rejectionReason,
},
})}
</p>
) : (
<p>
{t('Your order has been %s.', [
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? 'stopped'
: 'rejected',
])}
{tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? t('Your order has been stopped')
: t('Your order has been rejected')}
</p>
)}
{tx.txHash && (
@@ -778,7 +818,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
if (isOrderSubmissionTransaction(tx.body) && tx.order?.rejectionReason) {
return (
<div>
<h3 className="font-bold">{getOrderToastTitle(tx.order.status)}</h3>
<h3 className="font-bold">{getOrderToastTitle(tx.order.status, t)}</h3>
<p>{t('Your order was rejected.')}</p>
{tx.txHash && (
<p className="break-all">
@@ -799,7 +839,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
return (
<div>
<h3 className="font-bold">{t('Transfer complete')}</h3>
<p>{t('Your transaction has been confirmed ')}</p>
<p>{t('Your transaction has been confirmed')}</p>
{tx.txHash && (
<p className="break-all">
<ExternalLink
@@ -819,10 +859,10 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
<>
<ToastHeading>
{tx.order?.status
? getOrderToastTitle(tx.order.status)
? getOrderToastTitle(tx.order.status, t)
: t('Confirmed')}
</ToastHeading>
<p>{t('Your transaction has been confirmed ')}</p>
<p>{t('Your transaction has been confirmed')}</p>
{tx.txHash && (
<p className="break-all">
<ExternalLink
@@ -839,6 +879,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
};
const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
const t = useT();
let label = t('Error occurred');
let errorMessage =
tx.error instanceof WalletError
@@ -847,7 +888,7 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
const reconnectVegaWallet = useReconnectVegaWallet();
const orderRejection = tx.order && getRejectionReason(tx.order);
const orderRejection = tx.order && getRejectionReason(tx.order, t);
const walletNoConnectionCodes = [
ClientErrors.NO_SERVICE.code,
ClientErrors.NO_CLIENT.code,
@@ -857,10 +898,13 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
walletNoConnectionCodes.includes(tx.error.code);
if (orderRejection) {
label = getOrderToastTitle(tx.order?.status) || t('Order rejected');
errorMessage = t('Your order has been rejected because: %s', [
orderRejection || tx.order?.rejectionReason || ' ',
]);
label = getOrderToastTitle(tx.order?.status, t) || t('Order rejected');
errorMessage = t(
'Your order has been rejected because: {{rejectionReason}}',
{
rejectionReason: orderRejection || tx.order?.rejectionReason || ' ',
}
);
}
if (walletError) {
label = t('Wallet disconnected');
+34 -30
View File
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import {
Dialog,
@@ -16,6 +15,7 @@ import type { Web3ReactHooks } from '@web3-react/core';
import { useWeb3ConnectStore } from './web3-connect-store';
import { theme } from '@vegaprotocol/tailwindcss-config';
import classNames from 'classnames';
import { useT } from './use-t';
interface Web3ConnectDialogProps {
dialogOpen: boolean;
@@ -29,33 +29,36 @@ export const Web3ConnectDialog = ({
setDialogOpen,
connectors,
desiredChainId,
}: Web3ConnectDialogProps) => (
<Dialog
open={dialogOpen}
onChange={setDialogOpen}
onInteractOutside={(e) => {
// do not close dialog when clicked outside (wallet connect modal)
e.preventDefault();
}}
intent={Intent.None}
title={t('Connect to your Ethereum wallet')}
size="small"
>
<ul className="grid grid-cols-2 gap-2" data-testid="web3-connector-list">
{connectors.map((connector, i) => (
<li key={i} className="mb-2 last:mb-0">
<ConnectButton
connector={connector}
desiredChainId={desiredChainId}
onClick={() => {
setDialogOpen(false);
}}
/>
</li>
))}
</ul>
</Dialog>
);
}: Web3ConnectDialogProps) => {
const t = useT();
return (
<Dialog
open={dialogOpen}
onChange={setDialogOpen}
onInteractOutside={(e) => {
// do not close dialog when clicked outside (wallet connect modal)
e.preventDefault();
}}
intent={Intent.None}
title={t('Connect to your Ethereum wallet')}
size="small"
>
<ul className="grid grid-cols-2 gap-2" data-testid="web3-connector-list">
{connectors.map((connector, i) => (
<li key={i} className="mb-2 last:mb-0">
<ConnectButton
connector={connector}
desiredChainId={desiredChainId}
onClick={() => {
setDialogOpen(false);
}}
/>
</li>
))}
</ul>
</Dialog>
);
};
const ConnectButton = ({
connector,
@@ -66,9 +69,10 @@ const ConnectButton = ({
desiredChainId?: number;
onClick?: () => void;
}) => {
const t = useT();
const [connectorInstance, { useIsActivating }] = connector;
const isActivating = useIsActivating();
const info = getConnectorInfo(connectorInstance);
const info = getConnectorInfo(connectorInstance, t);
const [, setEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
return (
<button
@@ -120,7 +124,7 @@ export const Web3ConnectUncontrolledDialog = () => {
);
};
function getConnectorInfo(connector: Connector) {
function getConnectorInfo(connector: Connector, t: ReturnType<typeof useT>) {
if (connector instanceof MetaMask) {
return {
icon: <VegaIcon name={VegaIconNames.METAMASK} size={32} />,
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import {
Button,
CopyWithTooltip,
@@ -13,6 +12,7 @@ import {
import { useWithdrawalApprovalQuery } from './__generated__/WithdrawalApproval';
import omit from 'lodash/omit';
import { create } from 'zustand';
import { useT } from './use-t';
type WithdrawalApprovalDialogProps = {
withdrawalId: string | undefined;
@@ -28,6 +28,7 @@ export const WithdrawalApprovalDialog = ({
onChange,
asJson,
}: WithdrawalApprovalDialogProps) => {
const t = useT();
return (
<Dialog
title={t('Save withdrawal details')}
@@ -48,8 +49,7 @@ export const WithdrawalApprovalDialog = ({
<div className="pr-8">
<p>
{t(
`If the network is reset or has an outage, records of your withdrawal
may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.`
`If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.`
)}
</p>
{withdrawalId ? (
@@ -80,16 +80,20 @@ type WithdrawalApprovalDialogContentProps = {
asJson: boolean;
};
const NoDataContent = ({ msg = t('No data') }) => (
<div className="py-12" data-testid="splash">
<Splash>{msg}</Splash>
</div>
);
const NoDataContent = ({ msg }: { msg?: string }) => {
const t = useT();
return (
<div className="py-12" data-testid="splash">
<Splash>{msg || t('No data')}</Splash>
</div>
);
};
const WithdrawalApprovalDialogContent = ({
withdrawalId,
asJson,
}: WithdrawalApprovalDialogContentProps) => {
const t = useT();
const { data, loading } = useWithdrawalApprovalQuery({
variables: {
withdrawalId,
@@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import {
getDateTimeFormat,
resolveNetworkName,
@@ -13,12 +12,14 @@ import {
import { useEthereumConfig } from './use-ethereum-config';
import { Button, useToasts } from '@vegaprotocol/ui-toolkit';
import { useWeb3ConnectStore } from './web3-connect-store';
import { useT } from './use-t';
export const VerificationStatus = ({
state,
}: {
state: EthWithdrawalApprovalState;
}) => {
const t = useT();
const { config } = useEthereumConfig();
const openDialog = useWeb3ConnectStore((state) => state.open);
const remove = useToasts((state) => state.remove);
@@ -38,9 +39,14 @@ export const VerificationStatus = ({
return state.failureReason === WithdrawalFailure.NoConnection ? (
<>
<p>
{t('To complete this withdrawal, connect the Ethereum wallet %s', [
truncateByChars(state.withdrawal.details?.receiverAddress || ' '),
])}
{t(
'To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}',
{
receiverAddress: truncateByChars(
state.withdrawal.details?.receiverAddress || ' '
),
}
)}
</p>
<Button
onClick={() => {
@@ -56,9 +62,12 @@ export const VerificationStatus = ({
<>
<p>{t('Your Ethereum wallet is connected to the wrong network.')}</p>
<p className="mt-2">
{t('Go to your Ethereum wallet and connect to the network %s', [
resolveNetworkName(config?.chain_id),
])}
{t(
'Go to your Ethereum wallet and connect to the network {{networkName}}',
{
networkName: resolveNetworkName(config?.chain_id),
}
)}
</p>
</>
);
@@ -75,7 +84,9 @@ export const VerificationStatus = ({
return (
<>
<p>{t("The amount you're withdrawing has triggered a time delay")}</p>
<p>{t(`Cannot be completed until ${formattedTime}`)}</p>
<p>
{t(`Cannot be completed until {{time}}`, { time: formattedTime })}
</p>
</>
);
}
+14
View File
@@ -1 +1,15 @@
import '@testing-library/jest-dom';
import { locales } from '@vegaprotocol/i18n';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
// Set up i18n instance so that components have the correct default
// en translations
i18n.use(initReactI18next).init({
// we init with resources
resources: locales,
fallbackLng: 'en',
ns: ['web3'],
defaultNS: 'web3',
});

Some files were not shown because too many files have changed in this diff Show More