Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2304ce9763 | ||
|
|
79a16b8562 | ||
|
|
5661b74082 | ||
|
|
22703d937e | ||
|
|
f780013846 | ||
|
|
73f37c2477 | ||
|
|
607ad06971 | ||
|
|
7aee2a3a7b | ||
|
|
1fdf519780 | ||
|
|
9233778e85 |
@@ -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',
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ENV } from '../../config';
|
||||
|
||||
import noIcon from '../../images/token-no-icon.png';
|
||||
import vegaBlack from '../../images/vega_black.png';
|
||||
import vegaVesting from '../../images/vega_vesting.png';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import type { WalletCardAssetProps } from '../wallet-card';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
@@ -102,7 +103,10 @@ export const usePollForDelegations = () => {
|
||||
setAccounts(
|
||||
accounts
|
||||
.filter(
|
||||
(a) => a.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL
|
||||
(a) =>
|
||||
a.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL ||
|
||||
a.type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS ||
|
||||
a.type === Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS
|
||||
)
|
||||
.map((a) => {
|
||||
const isVega =
|
||||
@@ -115,14 +119,23 @@ export const usePollForDelegations = () => {
|
||||
subheading: isVega ? t('collateral') : a.asset.symbol,
|
||||
symbol: a.asset.symbol,
|
||||
decimals: a.asset.decimals,
|
||||
assetId: a.asset.id,
|
||||
balance: new BigNumber(
|
||||
addDecimal(a.balance, a.asset.decimals)
|
||||
),
|
||||
image: isVega ? vegaBlack : noIcon,
|
||||
image: isVega
|
||||
? vegaBlack
|
||||
: a.type ===
|
||||
Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS ||
|
||||
a.type ===
|
||||
Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS
|
||||
? vegaVesting
|
||||
: noIcon,
|
||||
border: isVega,
|
||||
address: isAssetTypeERC20(a.asset)
|
||||
? a.asset.source.contractAddress
|
||||
: undefined,
|
||||
type: a.type,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useAnimateValue } from '../../hooks/use-animate-value';
|
||||
import type { BigNumber } from '../../lib/bignumber';
|
||||
import { useNumberParts } from '@vegaprotocol/react-helpers';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AnchorButton, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
CONSOLE_TRANSFER_ASSET,
|
||||
DApp,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||
|
||||
interface WalletCardProps {
|
||||
children: React.ReactNode;
|
||||
@@ -100,8 +109,10 @@ export interface WalletCardAssetProps {
|
||||
symbol: string;
|
||||
balance: BigNumber;
|
||||
decimals: number;
|
||||
assetId?: string;
|
||||
border?: boolean;
|
||||
subheading?: string;
|
||||
type?: Schema.AccountType;
|
||||
}
|
||||
|
||||
export const WalletCardAsset = ({
|
||||
@@ -110,16 +121,37 @@ export const WalletCardAsset = ({
|
||||
symbol,
|
||||
balance,
|
||||
decimals,
|
||||
assetId,
|
||||
border,
|
||||
subheading,
|
||||
type,
|
||||
}: WalletCardAssetProps) => {
|
||||
const [integers, decimalsPlaces, separator] = useNumberParts(
|
||||
balance,
|
||||
decimals
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
const transferAssetLink = (assetId: string) =>
|
||||
consoleLink(CONSOLE_TRANSFER_ASSET.replace(':assetId', assetId));
|
||||
const { param: baseRate } = useNetworkParam('rewards_vesting_baseRate');
|
||||
|
||||
const isRedeemable =
|
||||
type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS && assetId;
|
||||
|
||||
const accountTypeTooltip = useMemo(() => {
|
||||
if (type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS) {
|
||||
return t('VestedRewardsTooltip');
|
||||
}
|
||||
if (type === Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS && baseRate) {
|
||||
return t('VestingRewardsTooltip', { baseRate });
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [baseRate, t, type]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap mt-2 mb-4">
|
||||
<div className="flex flex-nowrap gap-2 mt-2 mb-4">
|
||||
<img
|
||||
alt="Vega"
|
||||
src={image}
|
||||
@@ -129,15 +161,37 @@ export const WalletCardAsset = ({
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
className="flex align-center text-base"
|
||||
className="flex align-center items-baseline text-base gap-2"
|
||||
data-testid="currency-title"
|
||||
>
|
||||
<div className="mb-0 px-2 uppercase">{name}</div>
|
||||
<div className="mb-0 uppercase">{name}</div>
|
||||
<div className="mb-0 uppercase text-neutral-400">
|
||||
{subheading || symbol}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2 basis-full font-mono" data-testid="currency-value">
|
||||
{type ? (
|
||||
<div className="mb-[2px] flex gap-2 items-baseline">
|
||||
<Tooltip description={accountTypeTooltip}>
|
||||
<span className="px-2 py-1 leading-none text-xs bg-vega-cdark-700 rounded">
|
||||
{Schema.AccountTypeMapping[type]}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{isRedeemable ? (
|
||||
<Tooltip description={t('RedeemRewardsTooltip')}>
|
||||
<AnchorButton
|
||||
variant="primary"
|
||||
size="xs"
|
||||
href={transferAssetLink(assetId)}
|
||||
target="_blank"
|
||||
className="px-2 py-1 leading-none text-xs bg-vega-yellow text-black rounded"
|
||||
>
|
||||
{t('Redeem')}
|
||||
</AnchorButton>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="basis-full font-mono" data-testid="currency-value">
|
||||
<span>
|
||||
{integers}
|
||||
{separator}
|
||||
|
||||
@@ -953,5 +953,8 @@
|
||||
"ACCOUNT_TYPE_REWARD_RELATIVE_RETURN": "Relative return reward account",
|
||||
"ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY": "Return volatility reward account",
|
||||
"ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING": "Validator ranking reward account",
|
||||
"ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD": "Pending fee referral reward account"
|
||||
"ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD": "Pending fee referral reward account",
|
||||
"VestingRewardsTooltip": "Vesting rewards will be moved to vested account at a rate of {{baseRate}} per epoch.",
|
||||
"VestedRewardsTooltip": "Vested rewards can be redeemed using Console",
|
||||
"RedeemRewardsTooltip": "Click to redeem vested rewards in Console"
|
||||
}
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ describe('Proposal header', () => {
|
||||
__typename: 'InstrumentConfiguration',
|
||||
name: 'Some market',
|
||||
code: 'FX:BTCUSD/DEC99',
|
||||
futureProduct: {
|
||||
product: {
|
||||
__typename: 'FutureProduct',
|
||||
settlementAsset: {
|
||||
__typename: 'Asset',
|
||||
|
||||
+14
-2
@@ -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')}.
|
||||
</>
|
||||
|
||||
@@ -132,46 +132,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
|
||||
@@ -179,52 +158,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -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'];
|
||||
@@ -21,7 +21,7 @@ export type ProposalsQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, 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 {
|
||||
@@ -131,9 +131,16 @@ export const ProposalFieldsFragmentDoc = gql`
|
||||
instrument {
|
||||
name
|
||||
code
|
||||
futureProduct {
|
||||
settlementAsset {
|
||||
symbol
|
||||
product {
|
||||
... on FutureProduct {
|
||||
settlementAsset {
|
||||
symbol
|
||||
}
|
||||
}
|
||||
... on PerpetualProduct {
|
||||
settlementAsset {
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Route, Routes, useParams } from 'react-router-dom';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useMarket } from '@vegaprotocol/markets';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
SidebarButton,
|
||||
@@ -11,12 +9,7 @@ import {
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const MarketsSidebar = () => {
|
||||
const { marketId } = useParams();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { data } = useMarket(marketId);
|
||||
const active =
|
||||
data &&
|
||||
[MarketState.STATE_ACTIVE, MarketState.STATE_PENDING].includes(data.state);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -44,14 +37,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}
|
||||
|
||||
@@ -8,7 +8,7 @@ jest.mock('@vegaprotocol/accounts', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../../components/welcome-dialog/get-started.ts', () => ({
|
||||
jest.mock('../../components/welcome-dialog/get-started', () => ({
|
||||
GetStarted: () => <div>GetStarted</div>,
|
||||
}));
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ jest.mock('../../components/withdraw-container', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../../components/welcome-dialog/get-started.ts', () => ({
|
||||
jest.mock('../../components/welcome-dialog/get-started', () => ({
|
||||
GetStarted: () => <div>GetStarted</div>,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/bin/bash -e
|
||||
yarn --pure-lockfile
|
||||
app=${1:-trading}
|
||||
envCmd="envCmd="yarn env-cmd -f ./apps/${app}/.env.${2:-mainnet}"
|
||||
envCmd="envCmd="yarn -f ./apps/${app}/.env.${2:-mainnet}"
|
||||
yarn install
|
||||
if [ "${app}" = "trading" ]; then
|
||||
$envCmd yarn nx export trading
|
||||
DIST_LOCATION=dist/apps/trading/exported
|
||||
DIST_LOCATION=dist/apps/trading/exported/
|
||||
else
|
||||
$envCmd yarn nx build ${app}
|
||||
DIST_LOCATION=dist/apps/${app}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParam,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
@@ -21,7 +22,11 @@ export const ALLOWED_ACCOUNTS = [
|
||||
|
||||
export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
const { pubKey, pubKeys } = useVegaWallet();
|
||||
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.transfer_fee_factor,
|
||||
NetworkParams.transfer_minTransferQuantumMultiple,
|
||||
]);
|
||||
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: accountsDataProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
@@ -40,6 +45,7 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
const accounts = data
|
||||
? data.filter((account) => ALLOWED_ACCOUNTS.includes(account.type))
|
||||
: [];
|
||||
const sortedAccounts = sortBy(accounts, (a) => a.asset.symbol.toLowerCase());
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -59,9 +65,10 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
pubKey={pubKey}
|
||||
pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null}
|
||||
assetId={assetId}
|
||||
feeFactor={param}
|
||||
feeFactor={params.transfer_fee_factor}
|
||||
minQuantumMultiple={params.transfer_minTransferQuantumMultiple}
|
||||
submitTransfer={transfer}
|
||||
accounts={accounts}
|
||||
accounts={sortedAccounts}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { AddressField, TransferFee, TransferForm } from './transfer-form';
|
||||
import {
|
||||
AddressField,
|
||||
TransferFee,
|
||||
TransferForm,
|
||||
type TransferFormProps,
|
||||
} from './transfer-form';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { removeDecimal } from '@vegaprotocol/utils';
|
||||
|
||||
describe('TransferForm', () => {
|
||||
const renderComponent = (props: TransferFormProps) => {
|
||||
return render(<TransferForm {...props} />);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', { name: 'Confirm transfer' })
|
||||
@@ -37,6 +46,7 @@ describe('TransferForm', () => {
|
||||
symbol: '€',
|
||||
name: 'EUR',
|
||||
decimals: 2,
|
||||
quantum: '1',
|
||||
};
|
||||
const props = {
|
||||
pubKey,
|
||||
@@ -55,9 +65,10 @@ describe('TransferForm', () => {
|
||||
{
|
||||
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
asset,
|
||||
balance: '100000',
|
||||
balance: '10000',
|
||||
},
|
||||
],
|
||||
minQuantumMultiple: '1',
|
||||
};
|
||||
|
||||
it('form tooltips correctly displayed', async () => {
|
||||
@@ -66,7 +77,7 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-017
|
||||
// 1003-TRAN-018
|
||||
// 1003-TRAN-019
|
||||
render(<TransferForm {...props} />);
|
||||
renderComponent(props);
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('To Vega key'),
|
||||
@@ -113,9 +124,9 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-012
|
||||
// 1003-TRAN-013
|
||||
// 1003-TRAN-004
|
||||
render(<TransferForm {...props} />);
|
||||
renderComponent(props);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey is set as default value
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey is set as default value
|
||||
const toggle = screen.getByText('Enter manually');
|
||||
await userEvent.click(toggle);
|
||||
// has switched to input
|
||||
@@ -128,18 +139,18 @@ describe('TransferForm', () => {
|
||||
screen.getByLabelText('To Vega key'),
|
||||
'invalid-address'
|
||||
);
|
||||
expect(screen.getAllByTestId('input-error-text')[0]).toHaveTextContent(
|
||||
expect(screen.getAllByTestId('input-error-text')[1]).toHaveTextContent(
|
||||
'Invalid Vega key'
|
||||
);
|
||||
});
|
||||
|
||||
it('validates fields and submits', async () => {
|
||||
it('sends transfer from general accounts', async () => {
|
||||
// 1003-TRAN-002
|
||||
// 1003-TRAN-003
|
||||
// 1002-WITH-010
|
||||
// 1003-TRAN-011
|
||||
// 1003-TRAN-014
|
||||
render(<TransferForm {...props} />);
|
||||
renderComponent(props);
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect = screen.getByLabelText<HTMLSelectElement>('To Vega key');
|
||||
@@ -151,7 +162,7 @@ describe('TransferForm', () => {
|
||||
]);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey is set as default value
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey is set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
@@ -164,15 +175,20 @@ describe('TransferForm', () => {
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
// Test use max button
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
||||
expect(amountInput).toHaveValue('1000');
|
||||
|
||||
// Test amount validation
|
||||
await userEvent.type(amountInput, '0.00000001');
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, '0.001'); // Below quantum multiple amount
|
||||
expect(
|
||||
await screen.findByText('Value is below minimum')
|
||||
await screen.findByText(/Amount below minimum requirement/)
|
||||
).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(amountInput);
|
||||
@@ -193,7 +209,7 @@ describe('TransferForm', () => {
|
||||
await waitFor(() => {
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
@@ -203,10 +219,87 @@ describe('TransferForm', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sends transfer from vested accounts', async () => {
|
||||
const mockSubmit = jest.fn();
|
||||
renderComponent({
|
||||
...props,
|
||||
submitTransfer: mockSubmit,
|
||||
minQuantumMultiple: '100000',
|
||||
});
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key');
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('To Vega key'),
|
||||
props.pubKeys[1] // Use not current pubkey so we can check it switches to current pubkey later
|
||||
);
|
||||
|
||||
// Select asset
|
||||
await selectAsset(asset);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${asset.id}`
|
||||
);
|
||||
|
||||
// Check switch back to connected key
|
||||
expect(screen.getByLabelText('To Vega key')).toHaveValue(props.pubKey);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, '50');
|
||||
|
||||
expect(await screen.findByText(/Use max to bypass/)).toBeInTheDocument();
|
||||
|
||||
// Test use max button
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
||||
expect(amountInput).toHaveValue('100');
|
||||
|
||||
// If transfering from a vested account 'include fees' checkbox should
|
||||
// be disabled and fees should be 0
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(checkbox).toBeDisabled();
|
||||
const expectedFee = '0';
|
||||
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
||||
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
||||
|
||||
await submit();
|
||||
|
||||
await waitFor(() => {
|
||||
// 1003-TRAN-023
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKey,
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
oneOff: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IncludeFeesCheckbox', () => {
|
||||
it('validates fields and submits when checkbox is checked', async () => {
|
||||
const mockSubmit = jest.fn();
|
||||
render(<TransferForm {...props} submitTransfer={mockSubmit} />);
|
||||
renderComponent({ ...props, submitTransfer: mockSubmit });
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect = screen.getByLabelText<HTMLSelectElement>('To Vega key');
|
||||
@@ -217,7 +310,7 @@ describe('TransferForm', () => {
|
||||
);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey set as default value
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
@@ -230,7 +323,7 @@ describe('TransferForm', () => {
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
@@ -264,7 +357,7 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-023
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
@@ -275,7 +368,7 @@ describe('TransferForm', () => {
|
||||
});
|
||||
|
||||
it('validates fields when checkbox is not checked', async () => {
|
||||
render(<TransferForm {...props} />);
|
||||
renderComponent(props);
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key');
|
||||
@@ -286,7 +379,7 @@ describe('TransferForm', () => {
|
||||
);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey set as default value
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
@@ -297,6 +390,11 @@ describe('TransferForm', () => {
|
||||
// Select asset
|
||||
await selectAsset(asset);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
@@ -316,26 +414,28 @@ describe('TransferForm', () => {
|
||||
|
||||
describe('AddressField', () => {
|
||||
const props = {
|
||||
mode: 'select' as const,
|
||||
select: <div>select</div>,
|
||||
input: <div>input</div>,
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
|
||||
it('toggles content and calls onChange', async () => {
|
||||
it('renders correct content by mode prop and calls onChange', async () => {
|
||||
const mockOnChange = jest.fn();
|
||||
render(<AddressField {...props} onChange={mockOnChange} />);
|
||||
const { rerender } = render(
|
||||
<AddressField {...props} onChange={mockOnChange} />
|
||||
);
|
||||
|
||||
// select should be shown by default
|
||||
expect(screen.getByText('select')).toBeInTheDocument();
|
||||
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText('Enter manually'));
|
||||
expect(mockOnChange).toHaveBeenCalled();
|
||||
|
||||
rerender(<AddressField {...props} mode="input" />);
|
||||
|
||||
expect(screen.queryByText('select')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('input')).toBeInTheDocument();
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
||||
await userEvent.click(screen.getByText('Select from wallet'));
|
||||
expect(screen.getByText('select')).toBeInTheDocument();
|
||||
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import {
|
||||
minSafe,
|
||||
maxSafe,
|
||||
required,
|
||||
vegaPublicKey,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
addDecimalsFormatNumber,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -30,21 +30,30 @@ import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
|
||||
interface FormFields {
|
||||
toVegaKey: string;
|
||||
asset: string;
|
||||
asset: string; // This is used to simply filter the from account list, the fromAccount type should be used in the tx
|
||||
amount: string;
|
||||
fromAccount: AccountType;
|
||||
fromAccount: string; // AccountType-AssetId
|
||||
}
|
||||
|
||||
interface TransferFormProps {
|
||||
interface Asset {
|
||||
id: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
quantum: string;
|
||||
}
|
||||
|
||||
export interface TransferFormProps {
|
||||
pubKey: string | null;
|
||||
pubKeys: string[] | null;
|
||||
accounts: Array<{
|
||||
type: AccountType;
|
||||
balance: string;
|
||||
asset: { id: string; symbol: string; name: string; decimals: number };
|
||||
asset: Asset;
|
||||
}>;
|
||||
assetId?: string;
|
||||
feeFactor: string | null;
|
||||
minQuantumMultiple: string | null;
|
||||
submitTransfer: (transfer: Transfer) => void;
|
||||
}
|
||||
|
||||
@@ -55,6 +64,7 @@ export const TransferForm = ({
|
||||
feeFactor,
|
||||
submitTransfer,
|
||||
accounts,
|
||||
minQuantumMultiple,
|
||||
}: TransferFormProps) => {
|
||||
const {
|
||||
control,
|
||||
@@ -70,43 +80,58 @@ export const TransferForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const [toVegaKeyMode, setToVegaKeyMode] = useState<ToVegaKeyMode>('select');
|
||||
|
||||
const assets = sortBy(
|
||||
accounts
|
||||
.filter((a) => a.type === AccountType.ACCOUNT_TYPE_GENERAL)
|
||||
.filter(
|
||||
(a) =>
|
||||
a.type === AccountType.ACCOUNT_TYPE_GENERAL ||
|
||||
a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
)
|
||||
// Sum the general and vested account balances so the value shown in the asset
|
||||
// dropdown is correct for all transferable accounts
|
||||
.reduce((merged, account) => {
|
||||
const existing = merged.findIndex(
|
||||
(m) => m.asset.id === account.asset.id
|
||||
);
|
||||
if (existing > -1) {
|
||||
const balance = new BigNumber(merged[existing].balance)
|
||||
.plus(new BigNumber(account.balance))
|
||||
.toString();
|
||||
merged[existing] = { ...merged[existing], balance };
|
||||
return merged;
|
||||
}
|
||||
return [...merged, account];
|
||||
}, [] as typeof accounts)
|
||||
.map((account) => ({
|
||||
key: account.asset.id,
|
||||
...account.asset,
|
||||
balance: addDecimal(account.balance, account.asset.decimals),
|
||||
})),
|
||||
'name'
|
||||
(a) => a.symbol.toLowerCase()
|
||||
);
|
||||
|
||||
const selectedPubKey = watch('toVegaKey');
|
||||
const amount = watch('amount');
|
||||
const fromAccount = watch('fromAccount');
|
||||
const assetId = watch('asset');
|
||||
const selectedAssetId = watch('asset');
|
||||
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
// Convert the account type (Type-AssetId) into separate values
|
||||
const [accountType, accountAssetId] = fromAccount
|
||||
? parseFromAccount(fromAccount)
|
||||
: [undefined, undefined];
|
||||
const fromVested = accountType === AccountType.ACCOUNT_TYPE_VESTED_REWARDS;
|
||||
const asset = assets.find((a) => a.id === accountAssetId);
|
||||
|
||||
const account = accounts.find(
|
||||
(a) => a.asset.id === assetId && a.type === fromAccount
|
||||
(a) => a.asset.id === accountAssetId && a.type === accountType
|
||||
);
|
||||
const accountBalance =
|
||||
account && addDecimal(account.balance, account.asset.decimals);
|
||||
|
||||
// General account for the selected asset
|
||||
const generalAccount = accounts.find((a) => {
|
||||
return (
|
||||
a.asset.id === assetId && a.type === AccountType.ACCOUNT_TYPE_GENERAL
|
||||
);
|
||||
});
|
||||
|
||||
const [includeFee, setIncludeFee] = useState(false);
|
||||
|
||||
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
|
||||
const min = asset
|
||||
? new BigNumber(addDecimal('1', asset.decimals))
|
||||
: new BigNumber(0);
|
||||
|
||||
// Max amount given selected asset and from account
|
||||
const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0);
|
||||
|
||||
@@ -130,16 +155,21 @@ export const TransferForm = ({
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(fields: FormFields) => {
|
||||
if (!asset) {
|
||||
throw new Error('Submitted transfer with no asset selected');
|
||||
}
|
||||
if (!transferAmount) {
|
||||
throw new Error('Submitted transfer with no amount selected');
|
||||
}
|
||||
|
||||
const [type, assetId] = parseFromAccount(fields.fromAccount);
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
|
||||
if (!asset) {
|
||||
throw new Error('Submitted transfer with no asset selected');
|
||||
}
|
||||
|
||||
const transfer = normalizeTransfer(
|
||||
fields.toVegaKey,
|
||||
transferAmount,
|
||||
fields.fromAccount,
|
||||
type,
|
||||
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
|
||||
{
|
||||
id: asset.id,
|
||||
@@ -148,7 +178,7 @@ export const TransferForm = ({
|
||||
);
|
||||
submitTransfer(transfer);
|
||||
},
|
||||
[asset, submitTransfer, transferAmount]
|
||||
[submitTransfer, transferAmount, assets]
|
||||
);
|
||||
|
||||
// reset for placeholder workaround https://github.com/radix-ui/primitives/issues/1569
|
||||
@@ -164,55 +194,10 @@ export const TransferForm = ({
|
||||
className="text-sm"
|
||||
data-testid="transfer-form"
|
||||
>
|
||||
<TradingFormGroup label="To Vega key" labelFor="toVegaKey">
|
||||
<AddressField
|
||||
onChange={() => setValue('toVegaKey', '')}
|
||||
select={
|
||||
<TradingSelect {...register('toVegaKey')} id="toVegaKey">
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{pubKeys?.map((pk) => {
|
||||
const text = pk === pubKey ? t('Current key: ') + pk : pk;
|
||||
|
||||
return (
|
||||
<option key={pk} value={pk}>
|
||||
{text}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
}
|
||||
input={
|
||||
<TradingInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={true} // focus input immediately after is shown
|
||||
id="toVegaKey"
|
||||
type="text"
|
||||
{...register('toVegaKey', {
|
||||
validate: {
|
||||
required,
|
||||
vegaPublicKey,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{errors.toVegaKey?.message && (
|
||||
<TradingInputError forInput="toVegaKey">
|
||||
{errors.toVegaKey.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('Asset')} labelFor="asset">
|
||||
<Controller
|
||||
control={control}
|
||||
name="asset"
|
||||
rules={{
|
||||
validate: {
|
||||
required,
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TradingRichSelect
|
||||
data-testid="select-asset"
|
||||
@@ -220,13 +205,14 @@ export const TransferForm = ({
|
||||
name={field.name}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
setValue('fromAccount', '');
|
||||
}}
|
||||
placeholder={t('Please select an asset')}
|
||||
value={field.value}
|
||||
>
|
||||
{assets.map((a) => (
|
||||
<AssetOption
|
||||
key={a.id}
|
||||
key={a.key}
|
||||
asset={a}
|
||||
balance={
|
||||
<Balance
|
||||
@@ -246,10 +232,10 @@ export const TransferForm = ({
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('From account')} labelFor="fromAccount">
|
||||
<TradingSelect
|
||||
id="fromAccount"
|
||||
defaultValue=""
|
||||
{...register('fromAccount', {
|
||||
<Controller
|
||||
control={control}
|
||||
name="fromAccount"
|
||||
rules={{
|
||||
validate: {
|
||||
required,
|
||||
sameAccount: (value) => {
|
||||
@@ -264,48 +250,106 @@ export const TransferForm = ({
|
||||
return true;
|
||||
},
|
||||
},
|
||||
})}
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{accounts
|
||||
.filter((a) => {
|
||||
if (!assetId) return true;
|
||||
return assetId === a.asset.id;
|
||||
})
|
||||
.map((a) => {
|
||||
return (
|
||||
<option value={a.type} key={`${a.type}-${a.asset.id}`}>
|
||||
{AccountTypeMapping[a.type]} (
|
||||
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
|
||||
{a.asset.symbol})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TradingSelect
|
||||
id="fromAccount"
|
||||
defaultValue=""
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
|
||||
const [type] = parseFromAccount(e.target.value);
|
||||
|
||||
// Enforce that if transferring from a vested rewards account it must go to
|
||||
// the current connected general account
|
||||
if (
|
||||
type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS &&
|
||||
pubKey
|
||||
) {
|
||||
setValue('toVegaKey', pubKey);
|
||||
setToVegaKeyMode('select');
|
||||
setIncludeFee(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{accounts
|
||||
.filter((a) => {
|
||||
if (!selectedAssetId) return true;
|
||||
return selectedAssetId === a.asset.id;
|
||||
})
|
||||
.map((a) => {
|
||||
const id = `${a.type}-${a.asset.id}`;
|
||||
return (
|
||||
<option value={id} key={id}>
|
||||
{AccountTypeMapping[a.type]} (
|
||||
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
|
||||
{a.asset.symbol})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
)}
|
||||
/>
|
||||
{errors.fromAccount?.message && (
|
||||
<TradingInputError forInput="fromAccount">
|
||||
{errors.fromAccount.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('To account')} labelFor="toAccount">
|
||||
<TradingSelect
|
||||
id="toAccount"
|
||||
defaultValue={AccountType.ACCOUNT_TYPE_GENERAL}
|
||||
>
|
||||
<option value={AccountType.ACCOUNT_TYPE_GENERAL}>
|
||||
{generalAccount
|
||||
? `${
|
||||
AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]
|
||||
} (${addDecimalsFormatNumber(
|
||||
generalAccount.balance,
|
||||
generalAccount.asset.decimals
|
||||
)} ${generalAccount.asset.symbol})`
|
||||
: AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]}
|
||||
</option>
|
||||
</TradingSelect>
|
||||
<TradingFormGroup label="To Vega key" labelFor="toVegaKey">
|
||||
<AddressField
|
||||
onChange={() => {
|
||||
setValue('toVegaKey', '');
|
||||
setToVegaKeyMode((curr) => (curr === 'input' ? 'select' : 'input'));
|
||||
}}
|
||||
mode={toVegaKeyMode}
|
||||
select={
|
||||
<TradingSelect
|
||||
{...register('toVegaKey')}
|
||||
disabled={fromVested}
|
||||
id="toVegaKey"
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{pubKeys?.map((pk) => {
|
||||
const text = pk === pubKey ? t('Current key: ') + pk : pk;
|
||||
|
||||
return (
|
||||
<option key={pk} value={pk}>
|
||||
{text}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
}
|
||||
input={
|
||||
fromVested ? null : (
|
||||
<TradingInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={true} // focus input immediately after is shown
|
||||
id="toVegaKey"
|
||||
type="text"
|
||||
disabled={fromVested}
|
||||
{...register('toVegaKey', {
|
||||
validate: {
|
||||
required,
|
||||
vegaPublicKey,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{errors.toVegaKey?.message && (
|
||||
<TradingInputError forInput="toVegaKey">
|
||||
{errors.toVegaKey.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label="Amount" labelFor="amount">
|
||||
<TradingInput
|
||||
@@ -317,7 +361,43 @@ export const TransferForm = ({
|
||||
{...register('amount', {
|
||||
validate: {
|
||||
required,
|
||||
minSafe: (value) => minSafe(new BigNumber(min))(value),
|
||||
minSafe: (v) => {
|
||||
if (!asset || !minQuantumMultiple) return true;
|
||||
|
||||
const value = new BigNumber(v);
|
||||
|
||||
if (value.isZero()) {
|
||||
return t('Amount cannot be 0');
|
||||
}
|
||||
|
||||
const minByQuantumMultiple = toBigNum(
|
||||
minQuantumMultiple,
|
||||
asset.decimals
|
||||
);
|
||||
|
||||
if (fromVested) {
|
||||
// special conditions which let you bypass min transfer rules set by quantum multiple
|
||||
if (value.isGreaterThanOrEqualTo(max)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value.isLessThan(minByQuantumMultiple)) {
|
||||
return t(
|
||||
'Amount below minimum requirements for partial transfer. Use max to bypass'
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (value.isLessThan(minByQuantumMultiple)) {
|
||||
return t(
|
||||
'Amount below minimum requirement set by transfer.minTransferQuantumMultiple'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
maxSafe: (v) => {
|
||||
const value = new BigNumber(v);
|
||||
if (value.isGreaterThan(max)) {
|
||||
@@ -333,7 +413,9 @@ export const TransferForm = ({
|
||||
type="button"
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
onClick={() =>
|
||||
setValue('amount', parseFloat(accountBalance).toString())
|
||||
setValue('amount', parseFloat(accountBalance).toString(), {
|
||||
shouldValidate: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('Use max')}
|
||||
@@ -354,10 +436,10 @@ export const TransferForm = ({
|
||||
<div>
|
||||
<TradingCheckbox
|
||||
name="include-transfer-fee"
|
||||
disabled={!transferAmount}
|
||||
disabled={!transferAmount || fromVested}
|
||||
label={t('Include transfer fee')}
|
||||
checked={includeFee}
|
||||
onCheckedChange={() => setIncludeFee(!includeFee)}
|
||||
onCheckedChange={() => setIncludeFee((x) => !x)}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -367,7 +449,7 @@ export const TransferForm = ({
|
||||
amount={transferAmount}
|
||||
transferAmount={transferAmount}
|
||||
feeFactor={feeFactor}
|
||||
fee={fee}
|
||||
fee={fromVested ? '0' : fee}
|
||||
decimals={asset?.decimals}
|
||||
/>
|
||||
)}
|
||||
@@ -449,32 +531,38 @@ export const TransferFee = ({
|
||||
);
|
||||
};
|
||||
|
||||
type ToVegaKeyMode = 'input' | 'select';
|
||||
|
||||
interface AddressInputProps {
|
||||
select: ReactNode;
|
||||
input: ReactNode;
|
||||
mode: ToVegaKeyMode;
|
||||
onChange: () => void;
|
||||
}
|
||||
|
||||
export const AddressField = ({
|
||||
select,
|
||||
input,
|
||||
mode,
|
||||
onChange,
|
||||
}: AddressInputProps) => {
|
||||
const [isInput, setIsInput] = useState(false);
|
||||
|
||||
const isInput = mode === 'input';
|
||||
return (
|
||||
<>
|
||||
{isInput ? input : select}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsInput((curr) => !curr);
|
||||
onChange();
|
||||
}}
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
</button>
|
||||
{select && input && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onChange}
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const parseFromAccount = (fromAccountStr: string) => {
|
||||
return fromAccountStr.split('-') as [AccountType, string];
|
||||
};
|
||||
|
||||
@@ -72,15 +72,11 @@ export const DealTicketFeeDetails = ({
|
||||
|
||||
return (
|
||||
<KeyValue
|
||||
label={t('Fees')}
|
||||
value={
|
||||
totalDiscountedFeeAmount &&
|
||||
`~${formatValue(totalDiscountedFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
label={
|
||||
<>
|
||||
{t('Fees')}
|
||||
{totalDiscountFactor ? (
|
||||
<Pill size="xxs" intent={Intent.Warning} className="mr-1">
|
||||
<Pill size="xxs" intent={Intent.Info} className="ml-1">
|
||||
-
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(totalDiscountFactor).multipliedBy(100),
|
||||
@@ -88,10 +84,16 @@ export const DealTicketFeeDetails = ({
|
||||
)}
|
||||
</Pill>
|
||||
) : null}
|
||||
{totalDiscountedFeeAmount &&
|
||||
`~${formatValue(totalDiscountedFeeAmount, assetDecimals, quantum)}`}
|
||||
</>
|
||||
}
|
||||
value={
|
||||
totalDiscountedFeeAmount &&
|
||||
`~${formatValue(totalDiscountedFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
totalDiscountedFeeAmount &&
|
||||
`~${formatValue(totalDiscountedFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
|
||||
@@ -79,7 +79,11 @@ export const FeesBreakdown = ({
|
||||
volumeDiscountFactor
|
||||
);
|
||||
|
||||
const { volumeDiscount, referralDiscount } = getDiscountedFee(
|
||||
const {
|
||||
discountedFee: discountedTotalFeeAmount,
|
||||
volumeDiscount,
|
||||
referralDiscount,
|
||||
} = getDiscountedFee(
|
||||
totalFeeAmount,
|
||||
referralDiscountFactor,
|
||||
volumeDiscountFactor
|
||||
@@ -131,7 +135,7 @@ export const FeesBreakdown = ({
|
||||
<FeesBreakdownItem
|
||||
label={t('Total fees')}
|
||||
factor={feeFactors ? sumFeesFactors(feeFactors)?.toString() : undefined}
|
||||
value={totalFeeAmount}
|
||||
value={discountedTotalFeeAmount}
|
||||
symbol={symbol}
|
||||
decimals={decimals}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { MockedResponse } from '@apollo/react-testing';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
act,
|
||||
render,
|
||||
renderHook,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { RadioGroup } from '@vegaprotocol/ui-toolkit';
|
||||
import type {
|
||||
NodeCheckTimeUpdateSubscription,
|
||||
@@ -11,30 +17,33 @@ import {
|
||||
NodeCheckTimeUpdateDocument,
|
||||
} from '../../utils/__generated__/NodeCheck';
|
||||
import type { RowDataProps } from './row-data';
|
||||
import { POLL_INTERVAL } from './row-data';
|
||||
import {
|
||||
POLL_INTERVAL,
|
||||
Result,
|
||||
SUBSCRIPTION_TIMEOUT,
|
||||
useNodeBasicStatus,
|
||||
useNodeSubscriptionStatus,
|
||||
useResponseTime,
|
||||
} from './row-data';
|
||||
import { BLOCK_THRESHOLD, RowData } from './row-data';
|
||||
import type { HeaderEntry } from '@vegaprotocol/apollo-client';
|
||||
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
||||
import { CUSTOM_NODE_KEY } from '../../types';
|
||||
|
||||
jest.mock('@vegaprotocol/apollo-client', () => ({
|
||||
useHeaderStore: jest.fn().mockReturnValue({}),
|
||||
}));
|
||||
|
||||
const statsQueryMock: MockedResponse<NodeCheckQuery> = {
|
||||
const mockStatsQuery = (
|
||||
blockHeight = '1234'
|
||||
): MockedResponse<NodeCheckQuery> => ({
|
||||
request: {
|
||||
query: NodeCheckDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
statistics: {
|
||||
blockHeight: '1234', // the actual value used in the component is the value from the header store
|
||||
blockHeight,
|
||||
vegaTime: new Date().toISOString(),
|
||||
chainId: 'test-chain-id',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const subMock: MockedResponse<NodeCheckTimeUpdateSubscription> = {
|
||||
request: {
|
||||
@@ -59,18 +68,6 @@ global.performance.getEntriesByName = jest.fn().mockReturnValue([
|
||||
},
|
||||
]);
|
||||
|
||||
const mockHeaders = (
|
||||
url: string,
|
||||
headers: Partial<HeaderEntry> = {
|
||||
blockHeight: 100,
|
||||
timestamp: new Date(),
|
||||
}
|
||||
) => {
|
||||
(useHeaderStore as unknown as jest.Mock).mockReturnValue({
|
||||
[url]: headers,
|
||||
});
|
||||
};
|
||||
|
||||
const renderComponent = (
|
||||
props: RowDataProps,
|
||||
queryMock: MockedResponse<NodeCheckQuery>,
|
||||
@@ -86,6 +83,98 @@ const renderComponent = (
|
||||
);
|
||||
};
|
||||
|
||||
describe('useNodeSubscriptionStatus', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
const mockWrapper =
|
||||
(withData = false) =>
|
||||
({ children }: { children: React.ReactNode }) =>
|
||||
(
|
||||
<MockedProvider mocks={withData ? [subMock, subMock, subMock] : []}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
);
|
||||
it('results initially as loading', async () => {
|
||||
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||
wrapper: mockWrapper(true),
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Loading);
|
||||
});
|
||||
it('results as successful when data received', async () => {
|
||||
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||
wrapper: mockWrapper(true),
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Loading);
|
||||
await act(() => {
|
||||
jest.advanceTimersByTime(SUBSCRIPTION_TIMEOUT);
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Successful);
|
||||
});
|
||||
it('result as failed when no data received', async () => {
|
||||
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||
wrapper: mockWrapper(false),
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Loading);
|
||||
await act(() => {
|
||||
jest.advanceTimersByTime(SUBSCRIPTION_TIMEOUT);
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Failed);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useNodeBasicStatus', () => {
|
||||
const mockWrapper =
|
||||
(withData = false) =>
|
||||
({ children }: { children: React.ReactNode }) =>
|
||||
(
|
||||
<MockedProvider mocks={withData ? [mockStatsQuery('1234')] : []}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
);
|
||||
it('results initially as loading', async () => {
|
||||
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||
wrapper: mockWrapper(true),
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Loading);
|
||||
expect(result.current.currentBlockHeight).toBeNaN();
|
||||
});
|
||||
it('results as successful when data received', async () => {
|
||||
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||
wrapper: mockWrapper(true),
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current.status).toBe(Result.Successful);
|
||||
expect(result.current.currentBlockHeight).toBe(1234);
|
||||
});
|
||||
});
|
||||
it('result as failed when no data received', async () => {
|
||||
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||
wrapper: mockWrapper(false),
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current.status).toBe(Result.Failed);
|
||||
expect(result.current.currentBlockHeight).toBeNaN();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useResponseTime', () => {
|
||||
it('returns response time when url is valid', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useResponseTime('https://localhost:1234')
|
||||
);
|
||||
expect(result.current.responseTime).toBe(50);
|
||||
});
|
||||
it('does not return response time when url is invalid', () => {
|
||||
const { result } = renderHook(() => useResponseTime('nope'));
|
||||
expect(result.current.responseTime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RowData', () => {
|
||||
const props = {
|
||||
id: '0',
|
||||
@@ -94,9 +183,13 @@ describe('RowData', () => {
|
||||
onBlockHeight: jest.fn(),
|
||||
};
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('radio button enabled after stats query successful', async () => {
|
||||
mockHeaders(props.url);
|
||||
render(renderComponent(props, statsQueryMock, subMock));
|
||||
render(renderComponent(props, mockStatsQuery('100'), subMock));
|
||||
|
||||
// radio should be enabled until query resolves
|
||||
expect(
|
||||
@@ -127,8 +220,6 @@ describe('RowData', () => {
|
||||
});
|
||||
|
||||
it('radio button still enabled if query fails', async () => {
|
||||
mockHeaders(props.url, {});
|
||||
|
||||
const failedQueryMock: MockedResponse<NodeCheckQuery> = {
|
||||
request: {
|
||||
query: NodeCheckDocument,
|
||||
@@ -178,12 +269,11 @@ describe('RowData', () => {
|
||||
|
||||
it('highlights rows with a slow block height', async () => {
|
||||
const blockHeight = 100;
|
||||
mockHeaders(props.url, { blockHeight });
|
||||
|
||||
const { rerender } = render(
|
||||
renderComponent(
|
||||
{ ...props, highestBlock: blockHeight + BLOCK_THRESHOLD },
|
||||
statsQueryMock,
|
||||
mockStatsQuery(String(blockHeight)),
|
||||
subMock
|
||||
)
|
||||
);
|
||||
@@ -201,7 +291,7 @@ describe('RowData', () => {
|
||||
rerender(
|
||||
renderComponent(
|
||||
{ ...props, highestBlock: blockHeight + BLOCK_THRESHOLD + 1 },
|
||||
statsQueryMock,
|
||||
mockStatsQuery(String(blockHeight)),
|
||||
subMock
|
||||
)
|
||||
);
|
||||
@@ -216,7 +306,7 @@ describe('RowData', () => {
|
||||
...props,
|
||||
id: CUSTOM_NODE_KEY,
|
||||
},
|
||||
statsQueryMock,
|
||||
mockStatsQuery('1234'),
|
||||
subMock
|
||||
)
|
||||
);
|
||||
@@ -230,16 +320,17 @@ describe('RowData', () => {
|
||||
it('updates highest block after new header received', async () => {
|
||||
const mockOnBlockHeight = jest.fn();
|
||||
const blockHeight = 200;
|
||||
mockHeaders(props.url, { blockHeight });
|
||||
render(
|
||||
renderComponent(
|
||||
{ ...props, onBlockHeight: mockOnBlockHeight },
|
||||
statsQueryMock,
|
||||
mockStatsQuery(String(blockHeight)),
|
||||
subMock
|
||||
)
|
||||
);
|
||||
|
||||
expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight);
|
||||
await waitFor(() => {
|
||||
expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight);
|
||||
});
|
||||
});
|
||||
|
||||
it('should poll the query unless an errors is returned', async () => {
|
||||
@@ -275,7 +366,6 @@ describe('RowData', () => {
|
||||
};
|
||||
};
|
||||
|
||||
mockHeaders(props.url);
|
||||
const statsQueryMock1 = createStatsQueryMock('1234');
|
||||
const statsQueryMock2 = createStatsQueryMock('1235');
|
||||
const statsQueryMock3 = createFailedStatsQueryMock();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
||||
import { isValidUrl } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingRadio } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -12,6 +10,7 @@ import {
|
||||
import { LayoutCell } from './layout-cell';
|
||||
|
||||
export const POLL_INTERVAL = 1000;
|
||||
export const SUBSCRIPTION_TIMEOUT = 3000;
|
||||
export const BLOCK_THRESHOLD = 3;
|
||||
|
||||
export interface RowDataProps {
|
||||
@@ -21,15 +20,39 @@ export interface RowDataProps {
|
||||
onBlockHeight: (blockHeight: number) => void;
|
||||
}
|
||||
|
||||
export const RowData = ({
|
||||
id,
|
||||
url,
|
||||
highestBlock,
|
||||
onBlockHeight,
|
||||
}: RowDataProps) => {
|
||||
const [subFailed, setSubFailed] = useState(false);
|
||||
const [time, setTime] = useState<number>();
|
||||
// no use of data here as we need the data nodes reference to block height
|
||||
export enum Result {
|
||||
Successful,
|
||||
Failed,
|
||||
Loading,
|
||||
}
|
||||
|
||||
export const useNodeSubscriptionStatus = () => {
|
||||
const [status, setStatus] = useState<Result>(Result.Loading);
|
||||
const { data, error } = useNodeCheckTimeUpdateSubscription();
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
setStatus(Result.Failed);
|
||||
}
|
||||
if (data?.busEvents && data.busEvents.length > 0) {
|
||||
setStatus(Result.Successful);
|
||||
}
|
||||
// set as failed when no data received after SUBSCRIPTION_TIMEOUT ms
|
||||
const timeout = setTimeout(() => {
|
||||
if (!data || error) {
|
||||
setStatus(Result.Failed);
|
||||
}
|
||||
}, SUBSCRIPTION_TIMEOUT);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [data, error]);
|
||||
|
||||
return { status };
|
||||
};
|
||||
|
||||
export const useNodeBasicStatus = () => {
|
||||
const [status, setStatus] = useState<Result>(Result.Loading);
|
||||
|
||||
const { data, error, loading, startPolling, stopPolling } = useNodeCheckQuery(
|
||||
{
|
||||
pollInterval: POLL_INTERVAL,
|
||||
@@ -38,28 +61,7 @@ export const RowData = ({
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
const headerStore = useHeaderStore();
|
||||
const headers = headerStore[url];
|
||||
|
||||
const {
|
||||
data: subData,
|
||||
error: subError,
|
||||
loading: subLoading,
|
||||
} = useNodeCheckTimeUpdateSubscription();
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (!subData) {
|
||||
setSubFailed(true);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [subData]);
|
||||
|
||||
// handle polling
|
||||
useEffect(() => {
|
||||
const handleStartPoll = () => {
|
||||
if (error) return;
|
||||
@@ -83,56 +85,57 @@ export const RowData = ({
|
||||
};
|
||||
}, [startPolling, stopPolling, error]);
|
||||
|
||||
// measure response time
|
||||
const currentBlockHeight = parseInt(
|
||||
data?.statistics.blockHeight || 'NONE',
|
||||
10
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
setStatus(Result.Loading);
|
||||
return;
|
||||
}
|
||||
if (!error && !isNaN(currentBlockHeight)) {
|
||||
setStatus(Result.Successful);
|
||||
return;
|
||||
}
|
||||
setStatus(Result.Failed);
|
||||
}, [currentBlockHeight, error, loading]);
|
||||
|
||||
return {
|
||||
status,
|
||||
currentBlockHeight,
|
||||
};
|
||||
};
|
||||
|
||||
export const useResponseTime = (url: string, trigger?: unknown) => {
|
||||
const [responseTime, setResponseTime] = useState<number>();
|
||||
useEffect(() => {
|
||||
if (!isValidUrl(url)) return;
|
||||
if (typeof window.performance.getEntriesByName !== 'function') return; // protection for test environment
|
||||
// every time we get data measure response speed
|
||||
const requestUrl = new URL(url);
|
||||
const requests = window.performance.getEntriesByName(requestUrl.href);
|
||||
const { duration } =
|
||||
(requests.length && requests[requests.length - 1]) || {};
|
||||
setTime(duration);
|
||||
}, [url, data]);
|
||||
setResponseTime(duration);
|
||||
}, [url, trigger]);
|
||||
return { responseTime };
|
||||
};
|
||||
|
||||
export const RowData = ({
|
||||
id,
|
||||
url,
|
||||
highestBlock,
|
||||
onBlockHeight,
|
||||
}: RowDataProps) => {
|
||||
const { status: subStatus } = useNodeSubscriptionStatus();
|
||||
const { status, currentBlockHeight } = useNodeBasicStatus();
|
||||
const { responseTime } = useResponseTime(url, currentBlockHeight); // measure response time (ms) every time we get data (block height)
|
||||
useEffect(() => {
|
||||
if (headers?.blockHeight) {
|
||||
onBlockHeight(headers.blockHeight);
|
||||
if (!isNaN(currentBlockHeight)) {
|
||||
onBlockHeight(currentBlockHeight);
|
||||
}
|
||||
}, [headers?.blockHeight, onBlockHeight]);
|
||||
|
||||
const getHasError = () => {
|
||||
// the stats query errored
|
||||
if (error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we are still awaiting a header entry its not an error
|
||||
// we are still waiting for the query to resolve
|
||||
if (!headers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// highlight this node as 'error' if its more than BLOCK_THRESHOLD blocks behind the most
|
||||
// advanced node
|
||||
if (
|
||||
highestBlock !== null &&
|
||||
headers.blockHeight < highestBlock - BLOCK_THRESHOLD
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const getSubFailed = (
|
||||
subError: ApolloError | undefined,
|
||||
subFailed: boolean
|
||||
) => {
|
||||
if (subError) return true;
|
||||
if (subFailed) return true;
|
||||
return false;
|
||||
};
|
||||
}, [currentBlockHeight, onBlockHeight]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -143,72 +146,58 @@ export const RowData = ({
|
||||
)}
|
||||
<LayoutCell
|
||||
label={t('Response time')}
|
||||
isLoading={!error && loading}
|
||||
hasError={Boolean(error)}
|
||||
isLoading={status === Result.Loading}
|
||||
hasError={status === Result.Failed}
|
||||
dataTestId="response-time-cell"
|
||||
>
|
||||
{getResponseTimeDisplayValue(time, error)}
|
||||
{display(status, formatResponseTime(responseTime))}
|
||||
</LayoutCell>
|
||||
<LayoutCell
|
||||
label={t('Block')}
|
||||
isLoading={loading}
|
||||
hasError={getHasError()}
|
||||
isLoading={status === Result.Loading}
|
||||
hasError={
|
||||
status === Result.Failed ||
|
||||
(highestBlock != null &&
|
||||
!isNaN(currentBlockHeight) &&
|
||||
currentBlockHeight < highestBlock - BLOCK_THRESHOLD)
|
||||
}
|
||||
dataTestId="block-height-cell"
|
||||
>
|
||||
<span
|
||||
data-testid="query-block-height"
|
||||
data-query-block-height={
|
||||
error ? 'failed' : data?.statistics.blockHeight
|
||||
status === Result.Failed ? 'failed' : currentBlockHeight
|
||||
}
|
||||
>
|
||||
{getBlockDisplayValue(headers?.blockHeight, error)}
|
||||
{display(status, currentBlockHeight)}
|
||||
</span>
|
||||
</LayoutCell>
|
||||
<LayoutCell
|
||||
label={t('Subscription')}
|
||||
isLoading={subFailed ? false : subLoading}
|
||||
hasError={getSubFailed(subError, subFailed)}
|
||||
isLoading={subStatus === Result.Loading}
|
||||
hasError={subStatus === Result.Failed}
|
||||
dataTestId="subscription-cell"
|
||||
>
|
||||
{getSubscriptionDisplayValue(subFailed, subData?.busEvents, subError)}
|
||||
{display(subStatus, t('Yes'), t('No'))}
|
||||
</LayoutCell>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getResponseTimeDisplayValue = (
|
||||
responseTime?: number,
|
||||
error?: ApolloError
|
||||
) => {
|
||||
if (error) {
|
||||
return t('n/a');
|
||||
}
|
||||
if (typeof responseTime === 'number') {
|
||||
return `${Number(responseTime).toFixed(2)}ms`;
|
||||
}
|
||||
return '-';
|
||||
};
|
||||
const formatResponseTime = (time: number | undefined) =>
|
||||
time != null ? `${Number(time).toFixed(2)}ms` : '-';
|
||||
|
||||
const getBlockDisplayValue = (block?: number, error?: ApolloError) => {
|
||||
if (error) {
|
||||
return t('n/a');
|
||||
}
|
||||
if (block) {
|
||||
return block;
|
||||
}
|
||||
return '-';
|
||||
};
|
||||
|
||||
const getSubscriptionDisplayValue = (
|
||||
subFailed: boolean,
|
||||
events?: { id: string }[] | null,
|
||||
error?: ApolloError
|
||||
const display = (
|
||||
status: Result,
|
||||
yes: string | number | undefined,
|
||||
no = t('n/a')
|
||||
) => {
|
||||
if (subFailed || error) {
|
||||
return t('No');
|
||||
switch (status) {
|
||||
case Result.Successful:
|
||||
return yes;
|
||||
case Result.Failed:
|
||||
return no;
|
||||
default:
|
||||
return '-';
|
||||
}
|
||||
if (events?.length) {
|
||||
return t('Yes');
|
||||
}
|
||||
return '-';
|
||||
};
|
||||
|
||||
@@ -126,6 +126,11 @@ export const useEtherscanLink = () => {
|
||||
return link;
|
||||
};
|
||||
|
||||
// Console pages
|
||||
export const CONSOLE_TRANSFER = '#/portfolio/assets/transfer';
|
||||
export const CONSOLE_TRANSFER_ASSET =
|
||||
'#/portfolio/assets/transfer?assetId=:assetId';
|
||||
|
||||
// Governance pages
|
||||
export const TOKEN_NEW_MARKET_PROPOSAL = '/proposals/propose/new-market';
|
||||
export const TOKEN_NEW_NETWORK_PARAM_PROPOSAL =
|
||||
|
||||
@@ -289,17 +289,30 @@ describe('FeesDiscountBreakdownTooltip', () => {
|
||||
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />);
|
||||
const dt = container.querySelectorAll('dt');
|
||||
const dd = container.querySelectorAll('dd');
|
||||
const expected = [
|
||||
{ label: 'Infrastructure Fee Referral Discount', value: '0.05 BTC' },
|
||||
{ label: 'Infrastructure Fee Volume Discount', value: '0.06 BTC' },
|
||||
{ label: 'Liquidity Fee Referral Discount', value: '0.01 BTC' },
|
||||
{ label: 'Liquidity Fee Volume Discount', value: '0.02 BTC' },
|
||||
{ label: 'Maker Fee Referral Discount', value: '0.03 BTC' },
|
||||
{ label: 'Maker Fee Volume Discount', value: '0.04 BTC' },
|
||||
const expectedDt = [
|
||||
'Infrastructure Fee',
|
||||
'Referral Discount',
|
||||
'Volume Discount',
|
||||
'Liquidity Fee',
|
||||
'Referral Discount',
|
||||
'Volume Discount',
|
||||
'Maker Fee',
|
||||
'Referral Discount',
|
||||
'Volume Discount',
|
||||
];
|
||||
expected.forEach(({ label, value }, i) => {
|
||||
const expectedDD = [
|
||||
'0.05 BTC',
|
||||
'0.06 BTC',
|
||||
'0.01 BTC',
|
||||
'0.02 BTC',
|
||||
'0.03 BTC',
|
||||
'0.04 BTC',
|
||||
];
|
||||
expectedDt.forEach((label, i) => {
|
||||
expect(dt[i]).toHaveTextContent(label);
|
||||
expect(dd[i]).toHaveTextContent(value);
|
||||
});
|
||||
expectedDD.forEach((label, i) => {
|
||||
expect(dd[i]).toHaveTextContent(label);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -391,7 +391,7 @@ const FeesDiscountBreakdownTooltipItem = ({
|
||||
label: string;
|
||||
asset: ReturnType<typeof getAsset>;
|
||||
}) =>
|
||||
value ? (
|
||||
value && value !== '0' ? (
|
||||
<>
|
||||
<dt className="col-span-1">{label}</dt>
|
||||
<dd className="text-right col-span-1">
|
||||
@@ -418,34 +418,47 @@ export const FeesDiscountBreakdownTooltip = ({
|
||||
className="max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white"
|
||||
>
|
||||
<dl className="grid grid-cols-2 gap-x-1">
|
||||
{(fees.infrastructureFeeReferralDiscount || '0') !== '0' ||
|
||||
(fees.infrastructureFeeVolumeDiscount || '0') !== '0' ? (
|
||||
<dt className="col-span-2">{t('Infrastructure Fee')}</dt>
|
||||
) : null}
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.infrastructureFeeReferralDiscount}
|
||||
label={t('Infrastructure Fee Referral Discount')}
|
||||
label={t('Referral Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.infrastructureFeeVolumeDiscount}
|
||||
label={t('Infrastructure Fee Volume Discount')}
|
||||
label={t('Volume Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
{(fees.liquidityFeeReferralDiscount || '0') !== '0' ||
|
||||
(fees.liquidityFeeVolumeDiscount || '0') !== '0' ? (
|
||||
<dt className="col-span-2">{t('Liquidity Fee')}</dt>
|
||||
) : null}
|
||||
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.liquidityFeeReferralDiscount}
|
||||
label={t('Liquidity Fee Referral Discount')}
|
||||
label={t('Referral Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.liquidityFeeVolumeDiscount}
|
||||
label={t('Liquidity Fee Volume Discount')}
|
||||
label={t('Volume Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
{(fees.makerFeeReferralDiscount || '0') !== '0' ||
|
||||
(fees.makerFeeVolumeDiscount || '0') !== '0' ? (
|
||||
<dt className="col-span-2">{t('Maker Fee')}</dt>
|
||||
) : null}
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.makerFeeReferralDiscount}
|
||||
label={t('Maker Fee Referral Discount')}
|
||||
label={t('Referral Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.makerFeeVolumeDiscount}
|
||||
label={t('Maker Fee Volume Discount')}
|
||||
label={t('Volume Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
</dl>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useMemo } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
formatNumberPercentage,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -29,6 +28,12 @@ import { LiquidityProvisionStatus } from '@vegaprotocol/types';
|
||||
import { LiquidityProvisionStatusMapping } from '@vegaprotocol/types';
|
||||
import type { LiquidityProvisionData } from './liquidity-data-provider';
|
||||
|
||||
const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
|
||||
const decimalPlaces =
|
||||
typeof decimals === 'undefined' ? value.dp() || 0 : decimals;
|
||||
return `${value.toFixed(decimalPlaces, 1)}%`;
|
||||
};
|
||||
|
||||
const percentageFormatter = ({ value }: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
return formatNumberPercentage(new BigNumber(value).times(100), 2) || '-';
|
||||
@@ -126,11 +131,11 @@ export const LiquidityTable = ({
|
||||
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).times(
|
||||
100
|
||||
),
|
||||
2
|
||||
4
|
||||
),
|
||||
formatNumberPercentage(
|
||||
new BigNumber(data.commitmentMinTimeFraction).times(100),
|
||||
2
|
||||
4
|
||||
),
|
||||
]
|
||||
);
|
||||
@@ -143,7 +148,7 @@ export const LiquidityTable = ({
|
||||
new BigNumber(data.sla.currentEpochFractionOfTimeOnBook).times(
|
||||
100
|
||||
),
|
||||
2
|
||||
4
|
||||
),
|
||||
]
|
||||
);
|
||||
@@ -394,7 +399,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 }) => {
|
||||
|
||||
+1
-1
@@ -6,4 +6,4 @@ export * from './markets-candles';
|
||||
export * from './markets-data';
|
||||
export * from './OracleMarketsSpec';
|
||||
export * from './OracleSpecDataConnection';
|
||||
export * from './SuccessorMarket'
|
||||
export * from './SuccessorMarket'
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
fragment MarketCandlesFields on Candle {
|
||||
high
|
||||
low
|
||||
open
|
||||
close
|
||||
volume
|
||||
periodStart
|
||||
}
|
||||
|
||||
query MarketsCandles($interval: Interval!, $since: String!) {
|
||||
marketsConnection {
|
||||
edges {
|
||||
|
||||
@@ -12,6 +12,7 @@ export const NetworkParams = {
|
||||
'rewards_marketCreationQuantumMultiple',
|
||||
reward_staking_delegation_payoutDelay:
|
||||
'reward_staking_delegation_payoutDelay',
|
||||
rewards_vesting_baseRate: 'rewards_vesting_baseRate',
|
||||
governance_proposal_market_minVoterBalance:
|
||||
'governance_proposal_market_minVoterBalance',
|
||||
governance_proposal_market_minClose: 'governance_proposal_market_minClose',
|
||||
@@ -175,6 +176,7 @@ export const NetworkParams = {
|
||||
market_liquidity_feeCalculationTimeStep:
|
||||
'market_liquidity_feeCalculationTimeStep',
|
||||
transfer_fee_factor: 'transfer_fee_factor',
|
||||
transfer_minTransferQuantumMultiple: 'transfer_minTransferQuantumMultiple',
|
||||
network_validators_incumbentBonus: 'network_validators_incumbentBonus',
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -85,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-58
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',
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@
|
||||
"jsondiffpatch": "^0.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "13.3.0",
|
||||
"pennant": "1.14.0",
|
||||
"pennant": "^1.14.1",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-dom": "18.2.0",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
genesis.json
|
||||
genesis.tmp
|
||||
+32
-18
@@ -2,10 +2,11 @@
|
||||
"app_state": {
|
||||
"assets": {
|
||||
"73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "BTC (fake)",
|
||||
"quantum": "1",
|
||||
"symbol": "fBTC",
|
||||
"total_supply": "21000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "1000000"
|
||||
@@ -13,10 +14,11 @@
|
||||
}
|
||||
},
|
||||
"8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "DAI (fake)",
|
||||
"quantum": "1",
|
||||
"symbol": "fDAI",
|
||||
"total_supply": "1000000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "10000000000"
|
||||
@@ -24,10 +26,11 @@
|
||||
}
|
||||
},
|
||||
"e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "EURO (fake)",
|
||||
"symbol": "fEURO",
|
||||
"quantum": "1",
|
||||
"total_supply": "1000000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "10000000000"
|
||||
@@ -35,10 +38,11 @@
|
||||
}
|
||||
},
|
||||
"816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "USDC (fake)",
|
||||
"symbol": "fUSDC",
|
||||
"quantum": "1",
|
||||
"total_supply": "1000000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "1000000000000"
|
||||
@@ -46,10 +50,11 @@
|
||||
}
|
||||
},
|
||||
"62dfb1ab1cd488862b416cf163c75bc9a279226c65ac287c0dd67650daa444ee": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "XYZ (α alpha)",
|
||||
"quantum": "1",
|
||||
"symbol": "XYZalpha",
|
||||
"total_supply": "1000000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "100000000000"
|
||||
@@ -57,10 +62,11 @@
|
||||
}
|
||||
},
|
||||
"dedb3c42e7bc88d98a7fe0d73c7b9870b34c62c79a713071bd4d645ef7c216ee": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "XYZ (β beta)",
|
||||
"quantum": "1",
|
||||
"symbol": "XYZbeta",
|
||||
"total_supply": "1000000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "100000000000"
|
||||
@@ -68,10 +74,11 @@
|
||||
}
|
||||
},
|
||||
"a0ea8a48eb5cb024e66be5777bea75165ffcaf0be82d9047e376ce352e202f76": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "XYZ (γ gamma)",
|
||||
"quantum": "1",
|
||||
"symbol": "XYZgamma",
|
||||
"total_supply": "1000000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "100000000000"
|
||||
@@ -79,10 +86,11 @@
|
||||
}
|
||||
},
|
||||
"9ed5dd08c88e38f1c92cb8f056bd9d0507512c9ea4e6f308936f73646187b8e9": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "XYZ (δ delta)",
|
||||
"quantum": "1",
|
||||
"symbol": "XYZdelta",
|
||||
"total_supply": "1000000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "100000000000"
|
||||
@@ -90,10 +98,11 @@
|
||||
}
|
||||
},
|
||||
"bdcde894dcc4ffa11d6192065d84f9dcc75814cf24760ff4f3e50d64eaddbc02": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "XYZ (ε epsilon)",
|
||||
"quantum": "1",
|
||||
"symbol": "XYZepsilon",
|
||||
"total_supply": "1000000000",
|
||||
"source": {
|
||||
"builtin_asset": {
|
||||
"max_faucet_amount_mint": "100000000000"
|
||||
@@ -101,10 +110,11 @@
|
||||
}
|
||||
},
|
||||
"{{.GetVegaContractID "tBTC"}}": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "BTC (local)",
|
||||
"quantum": "1",
|
||||
"symbol": "tBTC",
|
||||
"total_supply": "0",
|
||||
"source": {
|
||||
"erc20": {
|
||||
"contract_address": "{{.GetEthContractAddr "tBTC"}}"
|
||||
@@ -112,10 +122,11 @@
|
||||
}
|
||||
},
|
||||
"{{.GetVegaContractID "tDAI"}}": {
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"quantum": "1",
|
||||
"name": "DAI (local)",
|
||||
"symbol": "tDAI",
|
||||
"total_supply": "0",
|
||||
"source": {
|
||||
"erc20": {
|
||||
"contract_address": "{{.GetEthContractAddr "tDAI"}}"
|
||||
@@ -123,10 +134,11 @@
|
||||
}
|
||||
},
|
||||
"{{.GetVegaContractID "tEURO"}}": {
|
||||
"quantum": "1",
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "EURO (local)",
|
||||
"symbol": "tEURO",
|
||||
"total_supply": "0",
|
||||
"source": {
|
||||
"erc20": {
|
||||
"contract_address": "{{.GetEthContractAddr "tEURO"}}"
|
||||
@@ -134,10 +146,11 @@
|
||||
}
|
||||
},
|
||||
"{{.GetVegaContractID "tUSDC"}}": {
|
||||
"quantum": "1",
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 5,
|
||||
"name": "USDC (local)",
|
||||
"symbol": "tUSDC",
|
||||
"total_supply": "0",
|
||||
"source": {
|
||||
"erc20": {
|
||||
"contract_address": "{{.GetEthContractAddr "tUSDC"}}"
|
||||
@@ -145,10 +158,11 @@
|
||||
}
|
||||
},
|
||||
"{{.GetVegaContractID "VEGA"}}": {
|
||||
"quantum": "1",
|
||||
"min_lp_stake": "1",
|
||||
"decimals": 18,
|
||||
"name": "Vega",
|
||||
"symbol": "VEGA",
|
||||
"total_supply": "64999723000000000000000000",
|
||||
"source": {
|
||||
"erc20": {
|
||||
"contract_address": "{{.GetEthContractAddr "VEGA"}}"
|
||||
@@ -157,7 +171,7 @@
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"replay_attack_threshold": 30
|
||||
"ReplayAttackThreshold": 30
|
||||
},
|
||||
"network_parameters": {
|
||||
"blockchains.ethereumConfig": "{\"network_id\": \"{{ .NetworkID }}\", \"chain_id\": \"{{ .ChainID }}\", \"collateral_bridge_contract\": { \"address\": \"{{.GetEthContractAddr "erc20_bridge_1"}}\" }, \"confirmations\": 3, \"staking_bridge_contract\": { \"address\": \"{{.GetEthContractAddr "staking_bridge"}}\", \"deployment_block_height\": 0}, \"token_vesting_contract\": { \"address\": \"{{.GetEthContractAddr "erc20_vesting"}}\", \"deployment_block_height\": 0 }, \"multisig_control_contract\": { \"address\": \"{{.GetEthContractAddr "MultisigControl"}}\", \"deployment_block_height\": 0 }}",
|
||||
@@ -180,17 +194,17 @@
|
||||
"market.auction.minimumDuration": "3s",
|
||||
"market.fee.factors.infrastructureFee": "0.001",
|
||||
"market.fee.factors.makerFee": "0.004",
|
||||
"market.stake.target.timeWindow": "1h0m0s",
|
||||
"market.stake.target.scalingFactor": "10",
|
||||
"market.liquidity.stakeToCcyVolume": "0.3",
|
||||
"market.liquidity.targetstake.triggering.ratio": "0.7",
|
||||
"market.liquidity.providersFeeCalculationTimeStep": "5s",
|
||||
"network.checkpoint.timeElapsedBetweenCheckpoints": "10s",
|
||||
"reward.asset": "{{.GetVegaContractID "VEGA"}}",
|
||||
"reward.staking.delegation.competitionLevel": "3.1",
|
||||
"reward.staking.delegation.delegatorShare": "0.883",
|
||||
"reward.staking.delegation.maxPayoutPerParticipant": "700000000000000000000",
|
||||
"reward.staking.delegation.maxPayoutPerEpoch": "7000000000000000000000",
|
||||
"reward.staking.delegation.minimumValidatorStake": "3000000000000000000000",
|
||||
"reward.staking.delegation.payoutDelay": "10s",
|
||||
"reward.staking.delegation.payoutFraction": "0.007",
|
||||
"spam.protection.delegation.min.tokens": "1000000000000000000",
|
||||
"spam.protection.max.delegations": "390",
|
||||
"spam.protection.max.proposals": "100",
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/bin/bash
|
||||
run_indented() {
|
||||
local indent=${INDENT:-" "}
|
||||
{ "$@" 2> >(sed "s/^/$indent/g" >&2); } | sed "s/^/$indent/g"
|
||||
}
|
||||
bold=$(tput bold)
|
||||
normal=$(tput sgr0)
|
||||
|
||||
# The name of the application to check.
|
||||
APPLICATION="vega"
|
||||
DEBUG=false
|
||||
|
||||
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "debug" ]; then
|
||||
# Set the flag to false if 'notidyup' is found
|
||||
DEBUG=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
echo "${bold}# Setup${normal}"
|
||||
# Check if the application exists.
|
||||
if command -v "$APPLICATION" &> /dev/null; then
|
||||
# Run the application if it exists.
|
||||
run_indented "$APPLICATION" version
|
||||
else
|
||||
# Print an error message if the application doesn't exist.
|
||||
echo "Error: $APPLICATION is not installed."
|
||||
# Exit with a non-zero exit code to indicate failure.
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "${bold}# Generating genesis${normal}"
|
||||
run_indented vegacapsule template genesis --path genesis.tmpl > genesis.tmp
|
||||
run_indented tail -n +3 genesis.tmp > genesis.json
|
||||
run_indented rm genesis.tmp
|
||||
|
||||
if jq empty "genesis.json" &> /dev/null; then
|
||||
run_indented echo ""
|
||||
run_indented echo "${bold}The genesis.json file is valid JSON.${normal}"
|
||||
else
|
||||
run_indented echo ""
|
||||
run_indented echo "${bold}The genesis.json file is invalid JSON.${normal}"
|
||||
# Exit with a non-zero exit code to indicate failure
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if $DEBUG; then
|
||||
run_indented cat genesis.json
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "${bold}# Verifying genesis${normal}"
|
||||
run_indented vega verify genesis genesis.json
|
||||
echo ""
|
||||
|
||||
# Tidy up
|
||||
if [[ "$DEBUG" == "false" ]]; then
|
||||
run_indented rm genesis.json
|
||||
fi
|
||||
@@ -20500,10 +20500,10 @@ pend@~1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
pennant@1.14.0:
|
||||
version "1.14.0"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.14.0.tgz#4100c25a6d836d6f0ff425181fb6f812f9fe5778"
|
||||
integrity sha512-9H0zWzFUSbD1BlDXnHFmKwkAxXGb1xTxjkUD+RwaMygtSwPXzQEyk2ScVyMqxdcz0RuJmI5HCVmZTOjdr1NwuA==
|
||||
pennant@^1.14.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.14.1.tgz#8e7a53256095e398b03397af31c831b02a56032b"
|
||||
integrity sha512-rjzo/tlFanO96OKhJiyjQtjug7sY6pjVZqVbieD3Tf4zt20bqIb6m4L2JfwOXEe0jqP/yddgCNPY+vlkVuJW1Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@d3fc/d3fc-technical-indicator" "^8.0.1"
|
||||
|
||||
Reference in New Issue
Block a user