@@ -171,21 +174,30 @@ export const Orderbook = ({
1,
Math.floor((height - midHeight) / 2 / (rowHeight + rowGap))
);
- const askRows = groupedAsks?.slice(limit * -1) ?? [];
- const bidRows = groupedBids?.slice(0, limit) ?? [];
+ const askRows = groupedAsks.slice(limit * -1);
+ const bidRows = groupedBids.slice(0, limit);
+
+ // this is used for providing a scale to render the volume
+ // bars based on the visible book
+ const deepestVisibleAsk = askRows[0];
+ const deepestVisibleBid = bidRows[bidRows.length - 1];
+ const maxVol = Math.max(
+ deepestVisibleAsk?.cumulativeVol || 0,
+ deepestVisibleBid?.cumulativeVol || 0
+ );
return (
{askRows.length || bidRows.length ? (
<>
-
-
- {midPrice && (
- <>
-
- {addDecimalsFormatNumber(midPrice, decimalPlaces)}
-
- {assetSymbol}
- {priceChangeIcon}
- >
- )}
-
-
+
>
) : (
-
+
{t('No data')}
)}
@@ -228,59 +235,13 @@ export const Orderbook = ({
}}
-
-
-
setOpen(open)}
- trigger={
- formatResolution(item).length)
- ) + 3
- }ch`,
- }}
- >
-
-
- {formatResolution(resolution)}
-
-
- }
- >
-
- {resolutions.map((r) => (
- setResolution(r)}>
- {formatResolution(r)}
-
- ))}
-
-
-
+
+
);
diff --git a/libs/markets/src/lib/__generated__/market-data.ts b/libs/markets/src/lib/__generated__/market-data.ts
index 2ce7201b7..385775ebc 100644
--- a/libs/markets/src/lib/__generated__/market-data.ts
+++ b/libs/markets/src/lib/__generated__/market-data.ts
@@ -3,23 +3,23 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
-export type MarketDataUpdateFieldsFragment = { __typename?: 'ObservableMarketData', marketId: string, auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, marketValueProxy: string, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null };
+export type MarketDataUpdateFieldsFragment = { __typename?: 'ObservableMarketData', marketId: string, auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, marketValueProxy: string, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null };
export type MarketDataUpdateSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
-export type MarketDataUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', marketId: string, auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, marketValueProxy: string, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null }> };
+export type MarketDataUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', marketId: string, auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, marketValueProxy: string, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null }> };
-export type MarketDataFieldsFragment = { __typename?: 'MarketData', auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, marketValueProxy: string, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string }, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null };
+export type MarketDataFieldsFragment = { __typename?: 'MarketData', auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, marketValueProxy: string, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, market: { __typename?: 'Market', id: string }, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null };
export type MarketDataQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
-export type MarketDataQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', data?: { __typename?: 'MarketData', auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, marketValueProxy: string, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string }, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null } | null } }> } | null };
+export type MarketDataQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', data?: { __typename?: 'MarketData', auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, marketValueProxy: string, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, market: { __typename?: 'Market', id: string }, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null } | null } }> } | null };
export const MarketDataUpdateFieldsFragmentDoc = gql`
fragment MarketDataUpdateFields on ObservableMarketData {
@@ -56,6 +56,7 @@ export const MarketDataUpdateFieldsFragmentDoc = gql`
suppliedStake
targetStake
trigger
+ lastTradedPrice
}
`;
export const MarketDataFieldsFragmentDoc = gql`
@@ -95,6 +96,7 @@ export const MarketDataFieldsFragmentDoc = gql`
suppliedStake
targetStake
trigger
+ lastTradedPrice
}
`;
export const MarketDataUpdateDocument = gql`
diff --git a/libs/markets/src/lib/market-data.graphql b/libs/markets/src/lib/market-data.graphql
index 35272316b..fb760b16a 100644
--- a/libs/markets/src/lib/market-data.graphql
+++ b/libs/markets/src/lib/market-data.graphql
@@ -32,6 +32,7 @@ fragment MarketDataUpdateFields on ObservableMarketData {
suppliedStake
targetStake
trigger
+ lastTradedPrice
}
subscription MarketDataUpdate($marketId: ID!) {
@@ -76,6 +77,7 @@ fragment MarketDataFields on MarketData {
suppliedStake
targetStake
trigger
+ lastTradedPrice
}
query MarketData($marketId: ID!) {
diff --git a/libs/markets/src/lib/market-data.mock.ts b/libs/markets/src/lib/market-data.mock.ts
index 496000768..d248acb92 100644
--- a/libs/markets/src/lib/market-data.mock.ts
+++ b/libs/markets/src/lib/market-data.mock.ts
@@ -62,6 +62,7 @@ const marketDataFields: MarketDataFieldsFragment = {
markPrice: '4612690058',
midPrice: '4612690000',
openInterest: '0',
+ lastTradedPrice: '4612690000',
priceMonitoringBounds: [
{
minValidPrice: '654701',
@@ -99,6 +100,7 @@ const marketDataUpdateFields: MarketDataUpdateFieldsFragment = {
marketValueProxy: '',
markPrice: '4612690058',
midPrice: '0',
+ lastTradedPrice: '0',
openInterest: '0',
staticMidPrice: '0',
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
diff --git a/libs/orders/src/lib/components/open-orders-menu/open-orders-menu.tsx b/libs/orders/src/lib/components/open-orders-menu/open-orders-menu.tsx
index 4ada22fb6..1f3879aa1 100644
--- a/libs/orders/src/lib/components/open-orders-menu/open-orders-menu.tsx
+++ b/libs/orders/src/lib/components/open-orders-menu/open-orders-menu.tsx
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
-import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
+import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { useVegaTransactionStore, useVegaWallet } from '@vegaprotocol/wallet';
import { useHasAmendableOrder } from '../../order-hooks';
@@ -28,12 +28,7 @@ export const OpenOrdersMenu = ({ marketId }: { marketId: string }) => {
};
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
-
+
{t('Cancel all')}
);
diff --git a/libs/orders/src/lib/components/order-list/order-list.spec.tsx b/libs/orders/src/lib/components/order-list/order-list.spec.tsx
index 8dde7f8a9..5cfa49863 100644
--- a/libs/orders/src/lib/components/order-list/order-list.spec.tsx
+++ b/libs/orders/src/lib/components/order-list/order-list.spec.tsx
@@ -6,7 +6,7 @@ import type { PartialDeep } from 'type-fest';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MockedProvider } from '@apollo/client/testing';
-import type { OrderFieldsFragment, OrderListTableProps } from '../';
+import type { Order, OrderFieldsFragment, OrderListTableProps } from '../';
import { OrderListTable } from '../';
import {
generateOrder,
@@ -164,6 +164,24 @@ describe('OrderListTable', () => {
);
});
+ it('negative positionDecimalPoints should be properly rendered in size column', async () => {
+ const localMarketOrder = {
+ ...marketOrder,
+ size: '3000',
+ market: {
+ ...marketOrder.market,
+ positionDecimalPlaces: -4,
+ },
+ } as Order;
+
+ await act(async () => {
+ render(generateJsx({ rowData: [localMarketOrder] }));
+ });
+
+ const cells = screen.getAllByRole('gridcell');
+ expect(cells[2]).toHaveTextContent('+30,000,000');
+ });
+
describe('amend cell', () => {
it('allows cancelling and editing for permitted orders', async () => {
const mockEdit = jest.fn();
diff --git a/libs/positions/src/lib/positions-table.spec.tsx b/libs/positions/src/lib/positions-table.spec.tsx
index 79fc39b6d..7dc85c5d1 100644
--- a/libs/positions/src/lib/positions-table.spec.tsx
+++ b/libs/positions/src/lib/positions-table.spec.tsx
@@ -201,6 +201,19 @@ describe('Positions', () => {
).not.toBeInTheDocument();
});
+ it('handle negative positionDecimalPlaces', async () => {
+ await renderComponent({
+ ...singleRow,
+ openVolume: '-2000',
+ positionDecimalPlaces: -4,
+ });
+ const cells = screen.getAllByRole('gridcell');
+ const cell = cells[1];
+ expect(within(cell).getByTestId('stack-cell-primary')).toHaveTextContent(
+ '-20,000,000'
+ );
+ });
+
describe('PNLCell', () => {
const props = {
data: undefined,
diff --git a/libs/proposals/src/components/protocol-upgrade-in-progress-notification.tsx b/libs/proposals/src/components/protocol-upgrade-in-progress-notification.tsx
index a9227289f..3330abb2c 100644
--- a/libs/proposals/src/components/protocol-upgrade-in-progress-notification.tsx
+++ b/libs/proposals/src/components/protocol-upgrade-in-progress-notification.tsx
@@ -26,22 +26,36 @@ export const ProtocolUpgradeInProgressNotification = () => {
const [nextUpgrade] = useLocalStorageSnapshot(
NEXT_PROTOCOL_UPGRADE_PROPOSAL_SNAPSHOT
);
- const { blocksRising, block } = useBlockRising();
const detailsLink = useProtocolUpgradeProposalLink();
let vegaReleaseTag: string | undefined;
let upgradeBlockHeight: string | undefined;
- if (error && !data && nextUpgrade && ALLOW_STORED_PROPOSAL_DATA) {
+ const hasData = data && !error;
+ const hasStoredData = nextUpgrade && ALLOW_STORED_PROPOSAL_DATA;
+
+ if (hasData) {
+ // gets tag and height from the data api
+ vegaReleaseTag = data.vegaReleaseTag;
+ upgradeBlockHeight = data.upgradeBlockHeight;
+ } else if (hasStoredData) {
+ // gets tag and height from stored value if data api is unavailable
try {
const stored = JSON.parse(nextUpgrade) as StoredNextProtocolUpgradeData;
vegaReleaseTag = stored.vegaReleaseTag;
upgradeBlockHeight = stored.upgradeBlockHeight;
} catch {
- // no op
+ // NOOP - could not parse stored data
}
}
+ const hasUpgradeInfo = vegaReleaseTag && upgradeBlockHeight;
+
+ const { blocksRising, block } = useBlockRising(
+ // skips querying blocks if there's no upgrade information available
+ !hasUpgradeInfo
+ );
+
/**
* If upgrade is in progress then none of the nodes should produce blocks,
* same should be with the tendermint block info otherwise it's a network
@@ -50,10 +64,7 @@ export const ProtocolUpgradeInProgressNotification = () => {
* Once the networks is back then the notification disappears.
*/
const upgradeInProgress =
- vegaReleaseTag &&
- upgradeBlockHeight &&
- !blocksRising &&
- block <= Number(upgradeBlockHeight);
+ hasUpgradeInfo && !blocksRising && block <= Number(upgradeBlockHeight);
if (!upgradeInProgress) return null;
diff --git a/libs/proposals/src/lib/protocol-upgrade-proposals/use-block-rising.ts b/libs/proposals/src/lib/protocol-upgrade-proposals/use-block-rising.ts
index f02be60aa..60d7bdc9c 100644
--- a/libs/proposals/src/lib/protocol-upgrade-proposals/use-block-rising.ts
+++ b/libs/proposals/src/lib/protocol-upgrade-proposals/use-block-rising.ts
@@ -15,31 +15,33 @@ const CHECK_INTERVAL = 5000; // ms
*/
const ALLOW_STALE = 2; // times -> MAX(this, 1) * CHECK_INTERVAL ~> min check time
-export const useBlockRising = () => {
+export const useBlockRising = (skip = false) => {
const [blocksRising, setBlocksRising] = useState(true);
const [block, setBlock] = useState(0);
const nodes = useEnvironment((state) => state.nodes);
const clients = useMemo(() => {
- return nodes.map(
- (n) =>
- n &&
- n.length > 0 &&
- createClient({
+ return nodes.map((n) => {
+ if (n && n.length > 0) {
+ const client = createClient({
url: n,
cacheConfig: undefined,
retry: false,
connectToDevTools: false,
connectToHeaderStore: true,
- })
- );
+ });
+ return client;
+ }
+ return undefined;
+ });
}, [nodes]);
const { refetch: fetchBlockInfo } = useBlockInfo();
useEffect(() => {
+ if (skip) return;
let stale = 0;
let prev = 0;
const check = async () => {
- const queries = clients.map((client, index) =>
+ const queries = clients.map((client) =>
client
? client
.query({
@@ -47,18 +49,16 @@ export const useBlockRising = () => {
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
})
- .catch((err) =>
- Promise.reject(
- `could not retrieve statistics from ${nodes[index]}`
- )
- )
+ .catch(() => {
+ // NOOP - could not retrieve statistics for that node (network error)
+ })
: undefined
);
const blockInfo = await fetchBlockInfo();
const results = (await Promise.allSettled(compact(queries))).map(
(res) => {
- if (res && res.status === 'fulfilled') {
+ if (res && res.status === 'fulfilled' && res.value) {
return res.value.data.statistics;
} else {
return undefined;
@@ -86,7 +86,7 @@ export const useBlockRising = () => {
return () => {
clearInterval(interval);
};
- }, [clients, fetchBlockInfo, blocksRising, nodes]);
+ }, [clients, fetchBlockInfo, blocksRising, nodes, skip]);
return { blocksRising, block };
};
diff --git a/libs/react-helpers/src/lib/format/number.tsx b/libs/react-helpers/src/lib/format/number.tsx
index a16c94551..299d03c52 100644
--- a/libs/react-helpers/src/lib/format/number.tsx
+++ b/libs/react-helpers/src/lib/format/number.tsx
@@ -1,9 +1,14 @@
import { BigNumber } from 'bignumber.js';
-import { getUserLocale, formatNumber } from '@vegaprotocol/utils';
+import {
+ getUserLocale,
+ formatNumber,
+ getUnlimitedThreshold,
+} from '@vegaprotocol/utils';
const INFINITY = '∞';
const DEFAULT_COMPACT_ABOVE = 1_000_000;
const DEFAULT_COMPACT_CAP = new BigNumber(1e24);
+
/**
* Compacts given number to human readable format.
* @param number
@@ -35,6 +40,10 @@ export const CompactNumber = ({
const decimalPlaces =
(decimals === 'infer' ? number.decimalPlaces() : decimals) || 0;
+ if (number.isGreaterThan(getUnlimitedThreshold(decimalPlaces))) {
+ return {INFINITY};
+ }
+
if (number.isLessThan(DEFAULT_COMPACT_ABOVE)) {
return (
{formatNumber(number, decimalPlaces)}
diff --git a/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-eye-off.tsx b/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-eye-off.tsx
new file mode 100644
index 000000000..31aae4fc8
--- /dev/null
+++ b/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-eye-off.tsx
@@ -0,0 +1,7 @@
+export const IconEyeOff = ({ size = 16 }: { size: number }) => {
+ return (
+
+ );
+};
diff --git a/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon-record.ts b/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon-record.ts
index e8069b90f..753984075 100644
--- a/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon-record.ts
+++ b/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon-record.ts
@@ -15,6 +15,7 @@ import { IconDeposit } from './svg-icons/icon-deposit';
import { IconEdit } from './svg-icons/icon-edit';
import { IconExclaimationMark } from './svg-icons/icon-exclaimation-mark';
import { IconEye } from './svg-icons/icon-eye';
+import { IconEyeOff } from './svg-icons/icon-eye-off';
import { IconForum } from './svg-icons/icon-forum';
import { IconGlobe } from './svg-icons/icon-globe';
import { IconInfo } from './svg-icons/icon-info';
@@ -55,6 +56,7 @@ export enum VegaIconNames {
EDIT = 'edit',
EXCLAIMATION_MARK = 'exclaimation-mark',
EYE = 'eye',
+ EYE_OFF = 'eye-off',
FORUM = 'forum',
GLOBE = 'globe',
INFO = 'info',
@@ -90,6 +92,7 @@ export const VegaIconNameMap: Record<
'chevron-down': IconChevronDown,
'chevron-left': IconChevronLeft,
'chevron-up': IconChevronUp,
+ 'eye-off': IconEyeOff,
'exclaimation-mark': IconExclaimationMark,
'open-external': IconOpenExternal,
'question-mark': IconQuestionMark,
diff --git a/libs/utils/src/lib/format/number.spec.ts b/libs/utils/src/lib/format/number.spec.ts
index 8ff305247..119087fca 100644
--- a/libs/utils/src/lib/format/number.spec.ts
+++ b/libs/utils/src/lib/format/number.spec.ts
@@ -5,7 +5,11 @@ import {
addDecimalsFormatNumberQuantum,
formatNumber,
formatNumberPercentage,
+ getUnlimitedThreshold,
isNumeric,
+ removeDecimal,
+ toBigNum,
+ quantumDecimalPlaces,
toDecimal,
toNumberParts,
} from './number';
@@ -151,8 +155,94 @@ describe('number utils', () => {
{ v: 7, o: '0.0000001' },
{ v: 8, o: '0.00000001' },
{ v: 9, o: '0.000000001' },
+ { v: -1, o: '10' },
+ { v: -2, o: '100' },
+ { v: -3, o: '1000' },
])('formats with toNumber given number correctly', ({ v, o }) => {
expect(toDecimal(v)).toStrictEqual(o);
});
});
+
+ describe('positive and negative decimals should be handled correctly', () => {
+ const baseNum = '2000';
+ const methods = [removeDecimal, toBigNum];
+ it.each([
+ { decimals: 0, result: ['2000', '2000'] },
+ { decimals: 1, result: ['20000', '200'] },
+ { decimals: -1, result: ['200', '20000'] },
+ { decimals: 2, result: ['200000', '20'] },
+ { decimals: -2, result: ['20', '200000'] },
+ { decimals: 3, result: ['2000000', '2'] },
+ { decimals: -3, result: ['2', '2000000'] },
+ { decimals: 4, result: ['20000000', '0.2'] },
+ { decimals: -4, result: ['0', '20000000'] }, // removeDecimal has toFixed(0) at the end
+ ])(
+ 'number methods should handle negative decimals',
+ ({ decimals, result }) => {
+ methods.forEach((method, i) => {
+ expect(method(baseNum, decimals).toString()).toEqual(result[i]);
+ });
+ }
+ );
+ });
+});
+
+describe('quantumDecimalPlaces', () => {
+ it.each([
+ ['1', 1, 3],
+ ['10', 1, 2],
+ ['100', 1, 1],
+ ['1000', 1, 0],
+ ['1', 2, 4],
+ ['10', 2, 3],
+ ['100', 2, 2],
+ ['1000', 2, 1],
+ ['1', 3, 5],
+ ['10', 3, 4],
+ ['100', 3, 3],
+ ['1000', 3, 2],
+ ['1', 18, 20],
+ ['1000000000', 18, 11],
+ ['5000000000', 18, 11],
+ ['1000000000000000000', 18, 2],
+ ])(
+ 'converts quantum %s of %d decimal places to %d quant. decimal places',
+ (quantum, decimals, output) => {
+ expect(quantumDecimalPlaces(quantum, decimals)).toEqual(output);
+ }
+ );
+});
+
+describe('getUnlimitedThreshold', () => {
+ it.each([
+ [
+ 0,
+ '9.26336713898529563388567880069503262826159877325124512315660672063305037119488e+76',
+ ],
+ [
+ 1,
+ '9.26336713898529563388567880069503262826159877325124512315660672063305037119488e+75',
+ ],
+ [
+ 2,
+ '9.26336713898529563388567880069503262826159877325124512315660672063305037119488e+74',
+ ],
+ [
+ 3,
+ '9.26336713898529563388567880069503262826159877325124512315660672063305037119488e+73',
+ ],
+ [
+ 10,
+ '9.26336713898529563388567880069503262826159877325124512315660672063305037119488e+66',
+ ],
+ [
+ 18,
+ '9.26336713898529563388567880069503262826159877325124512315660672063305037119488e+58',
+ ],
+ ])(
+ 'given %d decimal places it returns unlimited threshold %s',
+ (decimals, output) => {
+ expect(getUnlimitedThreshold(decimals).toString()).toEqual(output);
+ }
+ );
});
diff --git a/libs/utils/src/lib/format/number.ts b/libs/utils/src/lib/format/number.ts
index d3a8a2382..bf5a93ff4 100644
--- a/libs/utils/src/lib/format/number.ts
+++ b/libs/utils/src/lib/format/number.ts
@@ -4,12 +4,24 @@ import memoize from 'lodash/memoize';
import { getUserLocale } from '../get-user-locale';
+/**
+ * A raw unformatted value greater than this is considered and displayed
+ * as UNLIMITED.
+ */
+export const UNLIMITED_THRESHOLD = new BigNumber(2).pow(256).times(0.8);
+/**
+ * Gets the unlimited threshold value for given decimal places.
+ * @param decimalPlaces the asset's decimal places
+ */
+export const getUnlimitedThreshold = (decimalPlaces: number) =>
+ UNLIMITED_THRESHOLD.dividedBy(Math.pow(10, decimalPlaces));
+
const MIN_FRACTION_DIGITS = 2;
const MAX_FRACTION_DIGITS = 20;
export function toDecimal(numberOfDecimals: number) {
return new BigNumber(1)
- .dividedBy(Math.pow(10, numberOfDecimals))
+ .dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
.toString(10);
}
@@ -17,7 +29,8 @@ export function toBigNum(
rawValue: string | number,
decimals: number
): BigNumber {
- return new BigNumber(rawValue || 0).dividedBy(Math.pow(10, decimals));
+ const divides = new BigNumber(10).exponentiatedBy(decimals);
+ return new BigNumber(rawValue || 0).dividedBy(divides);
}
export function addDecimal(
@@ -36,7 +49,8 @@ export function removeDecimal(
value: string | BigNumber,
decimals: number
): string {
- return new BigNumber(value || 0).times(Math.pow(10, decimals)).toFixed(0);
+ const times = new BigNumber(10).exponentiatedBy(decimals);
+ return new BigNumber(value || 0).times(times).toFixed(0);
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
@@ -90,6 +104,25 @@ export const formatNumberFixed = (
return getFixedNumberFormat(formatDecimals).format(Number(rawValue));
};
+export const quantumDecimalPlaces = (
+ /** Raw asset's quantum value */
+ rawQuantum: number | string,
+ /** Asset's decimal places */
+ decimalPlaces: number
+) => {
+ // if raw quantum value is an empty string then it'll evaluate to 0
+ // this check ignores NaNs and zeroes
+ const formatDecimals =
+ isNaN(Number(rawQuantum)) || Number(rawQuantum) === 0
+ ? decimalPlaces
+ : Math.max(
+ 0,
+ Math.log10(100 / Number(addDecimal(rawQuantum, decimalPlaces)))
+ );
+
+ return Math.ceil(formatDecimals);
+};
+
export const addDecimalsFormatNumberQuantum = (
rawValue: string | number,
decimalPlaces: number,
diff --git a/libs/utils/src/lib/validate/validate-amount.ts b/libs/utils/src/lib/validate/validate-amount.ts
index ef45d3bba..95549ab1f 100644
--- a/libs/utils/src/lib/validate/validate-amount.ts
+++ b/libs/utils/src/lib/validate/validate-amount.ts
@@ -4,6 +4,12 @@ export const validateAmount = (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 === '') {
diff --git a/libs/wallet/src/connect-dialog/connect-dialog.tsx b/libs/wallet/src/connect-dialog/connect-dialog.tsx
index 7aa5c32e4..e3c0ed4dc 100644
--- a/libs/wallet/src/connect-dialog/connect-dialog.tsx
+++ b/libs/wallet/src/connect-dialog/connect-dialog.tsx
@@ -1,6 +1,7 @@
import classNames from 'classnames';
import {
Dialog,
+ ExternalLink,
Intent,
Pill,
TradingButton,
@@ -39,7 +40,7 @@ import { useVegaWallet } from '../use-vega-wallet';
import { InjectedConnectorForm } from './injected-connector-form';
import { isBrowserWalletInstalled } from '../utils';
import { useIsWalletServiceRunning } from '../use-is-wallet-service-running';
-import { useIsSnapRunning } from '../use-is-snap-running';
+import { SnapStatus, useSnapStatus } from '../use-snap-status';
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
export const CLOSE_DELAY = 1700;
@@ -158,7 +159,7 @@ const ConnectDialogContainer = ({
appChainId
);
- const isSnapRunning = useIsSnapRunning(
+ const snapStatus = useSnapStatus(
DEFAULT_SNAP_ID,
Boolean(connectors['snap'])
);
@@ -183,7 +184,7 @@ const ConnectDialogContainer = ({
setWalletUrl={setWalletUrl}
onSelect={handleSelect}
isDesktopWalletRunning={isDesktopWalletRunning}
- isSnapRunning={isSnapRunning}
+ snapStatus={snapStatus}
/>
)}
@@ -198,14 +199,14 @@ const ConnectorList = ({
walletUrl,
setWalletUrl,
isDesktopWalletRunning,
- isSnapRunning,
+ snapStatus,
}: {
connectors: Connectors;
onSelect: (type: WalletType) => void;
walletUrl: string;
setWalletUrl: (value: string) => void;
isDesktopWalletRunning: boolean | null;
- isSnapRunning: boolean | null;
+ snapStatus: SnapStatus;
}) => {
const { pubKey, links } = useVegaWallet();
const title = isBrowserWalletInstalled()
@@ -249,7 +250,7 @@ const ConnectorList = ({
) : null}
diff --git a/libs/wallet/src/connectors/snap-connector.ts b/libs/wallet/src/connectors/snap-connector.ts
index ede6fa70e..861967a09 100644
--- a/libs/wallet/src/connectors/snap-connector.ts
+++ b/libs/wallet/src/connectors/snap-connector.ts
@@ -118,14 +118,10 @@ export const getSnap = async (
snapId: string,
version?: string
): Promise