Compare commits

..
Author SHA1 Message Date
Matthew Russell 1912054d14 fix: imports after rebase 2023-02-28 10:20:02 -08:00
Matthew Russell 51a4136fa9 chore: update wrong path for mock test 2023-02-28 10:07:06 -08:00
Matthew Russell 69f2a4885f chore: update test describe 2023-02-28 10:07:06 -08:00
Matthew Russell f87188e4be fix: export path for cypress mocks 2023-02-28 10:07:06 -08:00
Matthew Russell 9acd27d870 chore: fix lint 2023-02-28 10:07:06 -08:00
Matthew Russell 749b20735d test: update unit tests with correct imports and mocks 2023-02-28 10:07:06 -08:00
Matthew Russell ee50b76046 chore: rename token-e2e to governance-e2e 2023-02-28 10:07:06 -08:00
Matthew Russell 15ca94caad chore: rename apps/token to apps/governance 2023-02-28 10:07:03 -08:00
Matthew Russell 42b1a66833 chore: rename libs/governance to libs/proposals 2023-02-28 10:04:00 -08:00
Matthew Russell f1f1fd2cd7 chore: fixup import 2023-02-28 10:01:35 -08:00
Matthew Russell b4c329343d fix: incorrect imports 2023-02-28 10:01:35 -08:00
Matthew Russell 2b7a1067b2 chore: add i18n library 2023-02-28 10:01:35 -08:00
Matthew Russell 0b4d1e5c77 fix: import issues 2023-02-28 10:01:35 -08:00
Matthew Russell 1d8952c8d1 chore: add datagrid lib 2023-02-28 10:01:35 -08:00
Matthew Russell 0419ef5c4a chore: use input error in date range filter now circ dep is fixed 2023-02-28 10:01:35 -08:00
Matthew Russell 1113ba66af chore: convert compact number to component 2023-02-28 10:01:35 -08:00
Matthew Russell d8a525601d chore: move react components, fix imports 2023-02-28 10:01:35 -08:00
Matthew Russell fb24b9d914 chore: add utils lib and move non react libs 2023-02-28 10:01:33 -08:00
247 changed files with 3729 additions and 8233 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ on:
type: choice
options:
- explorer-e2e
- governance-e2e
- token-e2e
- trading-e2e
tags:
description: 'Test tags to run'
+1 -1
View File
@@ -10,5 +10,5 @@ jobs:
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: '["explorer-e2e","governance-e2e","trading-e2e"]'
projects: '["explorer-e2e","token-e2e","trading-e2e"]'
tags: '@smoke @regression @slow'
+2 -2
View File
@@ -42,10 +42,10 @@ jobs:
affected=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)
echo -n "Affected projects: $affected"
projects=""
if [[ $affected == *"governance"* ]]; then projects+='"governance-e2e" '; fi
if [[ $affected == *"token"* ]]; then projects+='"token-e2e" '; fi
if [[ $affected == *"trading"* ]]; then projects+='"trading-e2e" '; fi
if [[ $affected == *"explorer"* ]]; then projects+='"explorer-e2e" '; fi
if [[ -z "$projects" ]]; then projects+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi
if [[ -z "$projects" ]]; then projects+='"token-e2e" "trading-e2e" "explorer-e2e" '; fi
projects=${projects%?}
projects=[${projects// /,}]
echo PROJECTS=$projects >> $GITHUB_ENV
+1 -1
View File
@@ -87,7 +87,7 @@ jobs:
run: ls -alsh /home/runner/.vegacapsule/testnet/logs/
- uses: actions/upload-artifact@v3
if: ${{ failure() }}
if: ${{ always() }}
with:
name: logs-${{ matrix.project }}
path: /home/runner/.vegacapsule/testnet/logs
-2
View File
@@ -12,8 +12,6 @@ on:
- react-helpers
- tailwindcss-config
- types
- utils
- i18n
jobs:
publish:
+1 -1
View File
@@ -161,7 +161,7 @@ In order to setup and run vegawallet for e2e capsule tests, in a separate termin
bash setup-vegawallet.sh
```
3. copy generated `api-token` and paste the token into `CYPRESS_VEGA_WALLET_API_TOKEN` environment variable in either `apps/governance-e2e/.env` or `apps/explorer-e2e/.env` depending on which project needs testing.
3. copy generated `api-token` and paste the token into `CYPRESS_VEGA_WALLET_API_TOKEN` environment variable in either `apps/token-e2e/.env` or `apps/explorer-e2e/.env` depending on which project needs testing.
Note: The script is only needed if capsule was built for first time or fresh. To run existing wallet service for capsule:
+1 -1
View File
@@ -1,7 +1,7 @@
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_NETWORKS='{"SANDBOX":"https://sandbox.explorer.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
+2
View File
@@ -1,7 +1,9 @@
# App configuration variables
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=CUSTOM
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
# App flags
+3 -1
View File
@@ -1,8 +1,10 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.devnet1.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=DEVNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
+4 -2
View File
@@ -1,8 +1,10 @@
# App configuration variables
NX_TENDERMINT_URL=https://be.explorer.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=MAINNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
+3 -1
View File
@@ -1,8 +1,10 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=https://tm.be.mainnet-mirror.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mirror-network.json
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_ENV=MIRROR
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
+2 -1
View File
@@ -5,7 +5,8 @@ NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.sandbox.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.sandbox.vega.xyz/websocket
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
+1
View File
@@ -4,6 +4,7 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
+3 -1
View File
@@ -1,7 +1,9 @@
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.stagnet3.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
+5 -2
View File
@@ -1,10 +1,13 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.testnet.vega.xyz/websocket/
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=TESTNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_NETWORKS={}
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
+4 -2
View File
@@ -1,12 +1,14 @@
# App configuration variables
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
NX_VEGA_URL=https://api.validators-testnet.vega.xyz/graphql
NX_VEGA_URL=https://api.validators-testnet.vega.xyz/query
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.xyz
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.xyz/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.validators-testnet.vega.xyz/websocket/
+1
View File
@@ -2,5 +2,6 @@
NX_TENDERMINT_URL=http://localhost:26607/
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
NX_VEGA_ENV=CUSTOM
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
-5
View File
@@ -1,5 +0,0 @@
function ReactMarkdown({ children }) {
return <div>{children}</div>;
}
export default ReactMarkdown;
@@ -22,3 +22,29 @@ query ExplorerFutureEpoch {
}
}
}
# query ExplorerEpoch($id: ID!) {
#
##### This could be useful for calculating roughly when a future epoch will
##### occur, but epoch not exist results in a total error
# networkParameter(key: "validators.epoch.length") {
# value
# }
#
##### This could be useful for relating where we are in time, but as above
##### the total failure caused by epoch(id) not existing
##### means this is useful
# currentEpoch: epoch {
# id
# }
#
# epoch(id: $id) {
# id
# timestamps {
# start
# end
# firstBlock
# lastBlock
# }
# }
#}
@@ -4,14 +4,13 @@ import { ENV } from '../../../config/env';
import Hash from '../hash';
export type ProposalLinkProps = {
id: string;
text?: string;
};
/**
* Given a proposal ID, generates an external link over to
* the Governance page for more information
*/
const ProposalLink = ({ id, text }: ProposalLinkProps) => {
const ProposalLink = ({ id }: ProposalLinkProps) => {
const { data } = useExplorerProposalQuery({
variables: { id },
});
@@ -21,7 +20,7 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
return (
<ExternalLink href={`${base}/proposals/${id}`}>
{text ? text : <Hash text={label} />}
<Hash text={label} />
</ExternalLink>
);
};
@@ -4,8 +4,9 @@ import {
getMarketExpiryDateFormatted,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
import type { MarketInfoNoCandlesQuery } from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/market-info';
import pick from 'lodash/pick';
import {
MarketStateMapping,
MarketTradingModeMapping,
@@ -16,7 +17,11 @@ import BigNumber from 'bignumber.js';
import { useMemo } from 'react';
import { Link } from 'react-router-dom';
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
export const MarketDetails = ({
market,
}: {
market: MarketInfoNoCandlesQuery['market'];
}) => {
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
@@ -27,9 +32,7 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
if (!market) return null;
const keyDetails = {
decimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
tradingMode: market.tradingMode,
...pick(market, 'decimalPlaces', 'positionDecimalPlaces', 'tradingMode'),
state: MarketStateMapping[market.state],
};
const assetDecimals =
@@ -101,7 +101,7 @@ const DeterministicOrderDetails = ({
{t('Remaining')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
<SizeInMarket size={o.remaining} marketId={o.market.id} />
{o.remaining}
</h5>
</div>
)}
@@ -1,7 +0,0 @@
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
id
state
rejectionReason
}
}
@@ -1,19 +0,0 @@
query ExplorerNewAssetSignatureBundle($id: ID!) {
erc20ListAssetBundle(assetId: $id) {
signatures
nonce
}
asset(id: $id) {
status
}
}
query ExplorerUpdateAssetSignatureBundle($id: ID!) {
erc20SetAssetLimitsBundle(proposalId: $id) {
signatures
nonce
}
asset(id: $id) {
status
}
}
@@ -1,50 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerProposalStatusQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export const ExplorerProposalStatusDocument = gql`
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
id
state
rejectionReason
}
}
`;
/**
* __useExplorerProposalStatusQuery__
*
* To run a query within a React component, call `useExplorerProposalStatusQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerProposalStatusQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerProposalStatusQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerProposalStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>(ExplorerProposalStatusDocument, options);
}
export function useExplorerProposalStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>(ExplorerProposalStatusDocument, options);
}
export type ExplorerProposalStatusQueryHookResult = ReturnType<typeof useExplorerProposalStatusQuery>;
export type ExplorerProposalStatusLazyQueryHookResult = ReturnType<typeof useExplorerProposalStatusLazyQuery>;
export type ExplorerProposalStatusQueryResult = Apollo.QueryResult<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>;
@@ -1,98 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerNewAssetSignatureBundleQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerNewAssetSignatureBundleQuery = { __typename?: 'Query', erc20ListAssetBundle?: { __typename?: 'Erc20ListAssetBundle', signatures: string, nonce: string } | null, asset?: { __typename?: 'Asset', status: Types.AssetStatus } | null };
export type ExplorerUpdateAssetSignatureBundleQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerUpdateAssetSignatureBundleQuery = { __typename?: 'Query', erc20SetAssetLimitsBundle: { __typename?: 'ERC20SetAssetLimitsBundle', signatures: string, nonce: string }, asset?: { __typename?: 'Asset', status: Types.AssetStatus } | null };
export const ExplorerNewAssetSignatureBundleDocument = gql`
query ExplorerNewAssetSignatureBundle($id: ID!) {
erc20ListAssetBundle(assetId: $id) {
signatures
nonce
}
asset(id: $id) {
status
}
}
`;
/**
* __useExplorerNewAssetSignatureBundleQuery__
*
* To run a query within a React component, call `useExplorerNewAssetSignatureBundleQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerNewAssetSignatureBundleQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerNewAssetSignatureBundleQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerNewAssetSignatureBundleQuery(baseOptions: Apollo.QueryHookOptions<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>(ExplorerNewAssetSignatureBundleDocument, options);
}
export function useExplorerNewAssetSignatureBundleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>(ExplorerNewAssetSignatureBundleDocument, options);
}
export type ExplorerNewAssetSignatureBundleQueryHookResult = ReturnType<typeof useExplorerNewAssetSignatureBundleQuery>;
export type ExplorerNewAssetSignatureBundleLazyQueryHookResult = ReturnType<typeof useExplorerNewAssetSignatureBundleLazyQuery>;
export type ExplorerNewAssetSignatureBundleQueryResult = Apollo.QueryResult<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>;
export const ExplorerUpdateAssetSignatureBundleDocument = gql`
query ExplorerUpdateAssetSignatureBundle($id: ID!) {
erc20SetAssetLimitsBundle(proposalId: $id) {
signatures
nonce
}
asset(id: $id) {
status
}
}
`;
/**
* __useExplorerUpdateAssetSignatureBundleQuery__
*
* To run a query within a React component, call `useExplorerUpdateAssetSignatureBundleQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerUpdateAssetSignatureBundleQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerUpdateAssetSignatureBundleQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerUpdateAssetSignatureBundleQuery(baseOptions: Apollo.QueryHookOptions<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>(ExplorerUpdateAssetSignatureBundleDocument, options);
}
export function useExplorerUpdateAssetSignatureBundleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>(ExplorerUpdateAssetSignatureBundleDocument, options);
}
export type ExplorerUpdateAssetSignatureBundleQueryHookResult = ReturnType<typeof useExplorerUpdateAssetSignatureBundleQuery>;
export type ExplorerUpdateAssetSignatureBundleLazyQueryHookResult = ReturnType<typeof useExplorerUpdateAssetSignatureBundleLazyQuery>;
export type ExplorerUpdateAssetSignatureBundleQueryResult = Apollo.QueryResult<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>;
@@ -1,70 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
import type { components } from '../../../../../types/explorer';
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
type Terms = components['schemas']['vegaProposalTerms'];
export function format(date: string | undefined, def: string) {
if (!date) {
return def;
}
return new Date().toLocaleDateString() || def;
}
export function getDate(
data: ExplorerProposalStatusQuery | undefined,
terms: Terms
): string {
const DEFAULT = t('Unknown');
if (!data?.proposal?.state) {
return DEFAULT;
}
switch (data.proposal.state) {
case 'STATE_DECLINED':
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
case 'STATE_ENACTED':
return `${t('Vote passed on')}: ${format(
terms.enactmentTimestamp,
DEFAULT
)}`;
case 'STATE_FAILED':
return `${t('Failed on')}: ${format(terms.validationTimestamp, DEFAULT)}`;
case 'STATE_OPEN':
return `${t('Open until')}: ${format(terms.closingTimestamp, DEFAULT)}`;
case 'STATE_PASSED':
return `${t('Passed on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
case 'STATE_REJECTED':
return `${t('Rejected on submission')}`;
case 'STATE_WAITING_FOR_NODE_VOTE':
return `${t('Opening')}...`;
default:
return DEFAULT;
}
}
interface ProposalDateProps {
id: string;
terms: Terms;
}
/**
* Shows the most relevant date for the proposal summary view. Depending on the
* state returned by GraphQL, we show either the validation, closing or enactment
* timestamp
*/
export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
const { data } = useExplorerProposalStatusQuery({
variables: {
id,
},
});
return (
<Lozenge className="font-sans text-xs float-right">
{getDate(data, terms)}
</Lozenge>
);
};
@@ -1,117 +0,0 @@
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import type { IconProps } from '@vegaprotocol/ui-toolkit';
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
import type * as Apollo from '@apollo/client';
import type * as Types from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
type ProposalQueryResult = Apollo.QueryResult<
ExplorerProposalStatusQuery,
Types.Exact<{
id: string;
}>
>;
interface ProposalStatusIconProps {
id: string;
}
type IconAndLabel = {
icon: IconProps['name'];
label: string;
};
/**
* Select an icon for a given query result. Tolerates queries that don't return
* any data
*
* @param data a data result from useExplorerProposalStatusQuery
* @returns Icon name
*/
export function getIconAndLabelForStatus(
res: ProposalQueryResult
): IconAndLabel {
const DEFAULT: IconAndLabel = {
icon: 'error',
label: t('Proposal state unknown'),
};
if (res.loading) {
return {
icon: 'more',
label: t('Loading data'),
};
}
if (!res?.data?.proposal || res.error) {
return {
icon: 'error',
label: res.error?.message || DEFAULT.label,
};
}
switch (res.data.proposal.state) {
case 'STATE_DECLINED':
return {
icon: 'stop',
label: t('Proposal did not have enough participation to be valid'),
};
case 'STATE_ENACTED':
return {
icon: 'tick-circle',
label: t('Vote passed and the proposal has been enacted'),
};
case 'STATE_FAILED':
return {
icon: 'thumbs-down',
label: t('Proposal became invalid and was not processed'),
};
case 'STATE_OPEN':
return {
// A checklist to indicate in progress
icon: 'form',
label: t('Voting is in progress'),
};
case 'STATE_PASSED':
return {
icon: 'thumbs-up',
label: t(
'Voting is complete and this proposal was approved. It is not yet enacted.'
),
};
case 'STATE_REJECTED':
return {
icon: 'disable',
label: t('The proposal was invalid'),
};
case 'STATE_WAITING_FOR_NODE_VOTE':
return {
// A sparkly thing indicating it's new
icon: 'clean',
label: t('Proposal is being checked by validators'),
};
default:
return DEFAULT;
}
}
/**
*/
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
const { icon, label } = getIconAndLabelForStatus(
useExplorerProposalStatusQuery({
variables: {
id,
},
})
);
return (
<div className="float-left mr-3">
<Tooltip description={<p>{label}</p>}>
<Icon name={icon} />
</Tooltip>
</div>
);
};
@@ -1,42 +0,0 @@
import { Loader } from '@vegaprotocol/ui-toolkit';
import { BundleError } from './signature-bundle/bundle-error';
import { BundleExists } from './signature-bundle/bundle-exists';
import { useExplorerNewAssetSignatureBundleQuery } from './__generated__/SignatureBundle';
export interface ProposalSignatureBundleByTypeProps {
id: string;
}
/**
* If a proposal needs a signature bundle, AND that signature bundle exists
* AND that proposal is a New Asset Proposal, render an overview of the
* signature bundle. Or an error.
*
* There is an almost identical component, ProposalSignatureBundleUpdateAsset
*/
export const ProposalSignatureBundleNewAsset = ({
id,
}: ProposalSignatureBundleByTypeProps) => {
const { data, error, loading } = useExplorerNewAssetSignatureBundleQuery({
variables: {
id,
},
});
if (loading) {
return <Loader />;
}
if (data?.erc20ListAssetBundle?.signatures) {
return (
<BundleExists
signatures={data.erc20ListAssetBundle.signatures}
nonce={data.erc20ListAssetBundle.nonce}
status={data.asset?.status}
proposalId={id}
/>
);
} else {
return <BundleError status={data?.asset?.status} error={error} />;
}
};
@@ -1,39 +0,0 @@
import { Loader } from '@vegaprotocol/ui-toolkit';
import type { ProposalSignatureBundleByTypeProps } from './signature-bundle-new';
import { BundleError } from './signature-bundle/bundle-error';
import { BundleExists } from './signature-bundle/bundle-exists';
import { useExplorerUpdateAssetSignatureBundleQuery } from './__generated__/SignatureBundle';
/**
* If a proposal needs a signature bundle, AND that signature bundle exists
* AND that proposal is a Asset Limits Proposal, render an overview of the
* signature bundle. Or an error.
*
* There is an almost identical component, ProposalSignatureBundleNewAsset
*/
export const ProposalSignatureBundleUpdateAsset = ({
id,
}: ProposalSignatureBundleByTypeProps) => {
const { data, error, loading } = useExplorerUpdateAssetSignatureBundleQuery({
variables: {
id,
},
});
if (loading) {
return <Loader />;
}
if (data?.erc20SetAssetLimitsBundle?.signatures) {
return (
<BundleExists
signatures={data.erc20SetAssetLimitsBundle.signatures}
nonce={data.erc20SetAssetLimitsBundle.nonce}
status={data.asset?.status}
proposalId={id}
/>
);
} else {
return <BundleError status={data?.asset?.status} error={error} />;
}
};
@@ -1,31 +0,0 @@
import { ProposalSignatureBundleNewAsset } from './signature-bundle-new';
import { ProposalSignatureBundleUpdateAsset } from './signature-bundle-update';
export function format(date: string | undefined, def: string) {
if (!date) {
return def;
}
return new Date().toLocaleDateString() || def;
}
interface ProposalSignatureBundleProps {
id: string;
type: 'NewAsset' | 'UpdateAsset';
}
/**
* Some proposals, if enacted, generate a signature bundle.
* The queries have to be split due to the way the API returns
* errors, hence this slightly redundant feeling switcher.
*/
export const ProposalSignatureBundle = ({
id,
type,
}: ProposalSignatureBundleProps) => {
return type === 'NewAsset' ? (
<ProposalSignatureBundleNewAsset id={id} />
) : (
<ProposalSignatureBundleUpdateAsset id={id} />
);
};
@@ -1,67 +0,0 @@
import type { ApolloError } from '@apollo/client';
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { AssetStatus } from '@vegaprotocol/types';
import { MemoryRouter } from 'react-router-dom';
import { BundleError } from './bundle-error';
describe('Bundle Error', () => {
const NON_ENABLED_STATUS: AssetStatus[] = [
AssetStatus.STATUS_PENDING_LISTING,
AssetStatus.STATUS_PROPOSED,
AssetStatus.STATUS_REJECTED,
];
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
it.each(NON_ENABLED_STATUS)(
'shows the apollo error if not enabled and a message is provided',
(status) => {
const screen = render(
<MemoryRouter>
<MockedProvider>
<BundleError
error={{ message: 'test-error-message' } as ApolloError}
status={status}
/>
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('test-error-message')).toBeInTheDocument();
}
);
it.each(NON_ENABLED_STATUS)(
'shows some fallback error message if the bundle has not been used and there is no error',
(status) => {
const screen = render(
<MemoryRouter>
<MockedProvider>
<BundleError status={status} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('No bundle for proposal ID')).toBeInTheDocument();
}
);
it.each(ENABLED_STATUS)(
'hides ProposalLink if the status is enabled',
(status) => {
const screen = render(
<MemoryRouter>
<MockedProvider>
<BundleError
error={{ message: 'irrelevant ' } as ApolloError}
status={status}
/>
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Asset already enabled')).toBeInTheDocument();
}
);
});
@@ -1,34 +0,0 @@
import type { ApolloError } from '@apollo/client';
import type { AssetStatus } from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import Hash from '../../../../links/hash';
import { IconForBundleStatus } from './bundle-icon';
export interface BundleErrorProps {
status?: AssetStatus;
error?: ApolloError;
}
/**
* Renders if a proposal signature bundle cannot be found.
* It is also possible that a data node has dropped the bundle
* from its retention so there is a backup case where we check
* the status - if it's already enabled, pretend this isn't an error
*/
export const BundleError = ({ status, error }: BundleErrorProps) => {
return (
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
<IconForBundleStatus status={status} />
<h1 className="text-xl pb-1">{t('No signature bundle found')}</h1>
<p>
{status === 'STATUS_ENABLED' ? (
t('Asset already enabled')
) : (
<Hash text={error ? error.message : t('No bundle for proposal ID')} />
)}
</p>
</div>
);
};
@@ -1,64 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { AssetStatus } from '@vegaprotocol/types';
import { MemoryRouter } from 'react-router-dom';
import { BundleExists } from './bundle-exists';
describe('Bundle Exists', () => {
const NON_ENABLED_STATUS: AssetStatus[] = [
AssetStatus.STATUS_PENDING_LISTING,
AssetStatus.STATUS_PROPOSED,
AssetStatus.STATUS_REJECTED,
];
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
const MOCK_SIGNATURES =
'0x1760ce4efec01d5bb9fe57c0660a39a27e0b5a1bd39b4d3fc0452bf142a4ef733baaf4dbedd74cd676c759f96ac7f412ec05e136bbff8a81d9f0c2428df8e099004c4d166ece0527d86e202386e8758580790d0a46f6429baaa268b4bf5c11c9e3402e175a9022ae8295e543853c01383ef17cd58458ddfc9c706fca0ecffa0d3d0160eb5234e63a78b9649428ca7659eb693fdde86edfc6c2707ef2e8fbf4c76a9f0eebe183da571a58837e2fa995d7b10955ecf04c138dc0ce964f17c3de1f5c6d0060ec132cc515cf974ead1da38e2ff8d4b870335865e8d4d8c982182bddea18bb513e2d37fd32de7fedc0c4f694e6bcdcf20f8547f19e7d9d25ce32c6ca9ea51e01ad3b92475778d9da7251d3943071f59107c9cd7ad9dd1923c06e88d3352869d919a591bf30732bad2c3fcf30a9f664dbb7a9c65a64875a48cbeb5741bd0a853901';
const MOCK_NONCE =
'18250011763873610289536200551900545467959221115607409799241178172533618346952';
const MOCK_PROPOSAL_ID =
'285923fed8c66ffb416b163e8ec72d3a87b9b8e2570e7ee7fe97d7092a918bc8';
const PROPOSAL_LINK_TEXT = 'Visit our Governance site to submit this';
it.each(NON_ENABLED_STATUS)(
'shows a handy ProposalLink if the status is anything except enabled',
(status) => {
const screen = render(
<MemoryRouter>
<MockedProvider>
<BundleExists
nonce={MOCK_NONCE}
proposalId={MOCK_PROPOSAL_ID}
signatures={MOCK_SIGNATURES}
status={status}
/>
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText(PROPOSAL_LINK_TEXT)).toBeInTheDocument();
}
);
it.each(ENABLED_STATUS)(
'hides ProposalLink if the status is enabled',
(status) => {
const screen = render(
<MemoryRouter>
<MockedProvider>
<BundleExists
nonce={MOCK_NONCE}
proposalId={MOCK_PROPOSAL_ID}
signatures={MOCK_SIGNATURES}
status={status}
/>
</MockedProvider>
</MemoryRouter>
);
expect(screen.queryAllByText(PROPOSAL_LINK_TEXT)).toEqual([]);
}
);
});
@@ -1,46 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { AssetStatus } from '@vegaprotocol/types';
import ProposalLink from '../../../../links/proposal-link/proposal-link';
import { IconForBundleStatus } from './bundle-icon';
import { ProposalSignatureBundleDetails } from './details';
export interface BundleExistsProps {
signatures: string;
nonce: string;
status?: AssetStatus;
proposalId: string;
}
/**
* If a proposal needs a signature bundle, AND that signature bundle exists,
* this component renders that signature bundle.
*
*/
export const BundleExists = ({
signatures,
nonce,
status,
proposalId,
}: BundleExistsProps) => {
return (
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
<IconForBundleStatus status={status} />
<h1 className="text-xl pb-1">
{status === 'STATUS_ENABLED'
? t('Asset added to bridge')
: t('Signature bundle generated')}
</h1>
<ProposalSignatureBundleDetails signatures={signatures} nonce={nonce} />
{status !== 'STATUS_ENABLED' ? (
<p className="mt-5">
<ProposalLink
id={proposalId}
text={t('Visit our Governance site to submit this')}
/>
</p>
) : null}
</div>
);
};
@@ -1,33 +0,0 @@
import { render } from '@testing-library/react';
import { AssetStatus } from '@vegaprotocol/types';
import { IconForBundleStatus } from './bundle-icon';
describe('Bundle status icon', () => {
const NON_ENABLED_STATUS: AssetStatus[] = [
AssetStatus.STATUS_PENDING_LISTING,
AssetStatus.STATUS_PROPOSED,
AssetStatus.STATUS_REJECTED,
];
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
it.each(NON_ENABLED_STATUS)(
'show a sparkle icon if the bundle is unused',
(status) => {
const screen = render(<IconForBundleStatus status={status} />);
const i = screen.getByRole('img');
expect(i).toHaveAttribute('aria-label');
expect(i.getAttribute('aria-label')).toMatch(/clean/);
}
);
it.each(ENABLED_STATUS)(
'shows a tick if the bundle is already used',
(status) => {
const screen = render(<IconForBundleStatus status={status} />);
const i = screen.getByRole('img');
expect(i).toHaveAttribute('aria-label');
expect(i.getAttribute('aria-label')).toMatch(/tick-circle/);
}
);
});
@@ -1,17 +0,0 @@
import type { AssetStatus } from '@vegaprotocol/types';
import type { IconName } from '@vegaprotocol/ui-toolkit';
import { Icon } from '@vegaprotocol/ui-toolkit';
export interface IconForBundleStatusProps {
status?: AssetStatus;
}
/**
* Naively select an icon for an asset. If it is enabled, we show a tick - anything
* else is assumed to be 'in progress'. There should only be a signature bundle or the
* asset should not exist
*/
export const IconForBundleStatus = ({ status }: IconForBundleStatusProps) => {
const i: IconName = status === 'STATUS_ENABLED' ? 'tick-circle' : 'clean';
return <Icon className="float-left mt-2 mr-3" name={i} />;
};
@@ -1,41 +0,0 @@
import { t } from '@vegaprotocol/i18n';
export interface ProposalSignatureBundleDetailsProps {
signatures: string;
nonce: string;
}
export const ProposalSignatureBundleDetails = ({
signatures,
nonce,
}: ProposalSignatureBundleDetailsProps) => {
return (
<details className="mt-5">
<summary>{t('Signature bundle details')}</summary>
<div className="ml-4">
<h2 className="text-lg mt-2 mb-2">{t('Signatures')}</h2>
<p>
<textarea
className="font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]"
readOnly={true}
rows={12}
cols={120}
value={signatures}
/>
</p>
<h2 className="text-lg mt-5 mb-2">{t('Nonce')}</h2>
<p>
<textarea
className="font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]"
readOnly={true}
rows={2}
cols={120}
value={nonce}
/>
</p>
</div>
</details>
);
};
@@ -1,90 +0,0 @@
import type { ProposalTerms } from '../tx-proposal';
import { useState } from 'react';
import type { components } from '../../../../../types/explorer';
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
import ProposalLink from '../../../links/proposal-link/proposal-link';
import truncate from 'lodash/truncate';
import { ProposalStatusIcon } from './proposal-status-icon';
import ReactMarkdown from 'react-markdown';
import { ProposalDate } from './proposal-date';
import { t } from '@vegaprotocol/i18n';
type Rationale = components['schemas']['vegaProposalRationale'];
type ProposalTermsDialog = {
open: boolean;
title: string;
content: unknown;
};
interface ProposalSummaryProps {
id: string;
rationale?: Rationale;
terms?: ProposalTerms;
}
/**
* Effectively a 'preview' for what the proposal is about, and a link to the
* Token site for full breakdown of votes
*/
export const ProposalSummary = ({
id,
rationale,
terms,
}: ProposalSummaryProps) => {
const [dialog, setDialog] = useState<ProposalTermsDialog>({
open: false,
title: '',
content: null,
});
const openDialog = () => {
if (!terms) return;
setDialog({
open: true,
title: rationale?.title || t('Proposal details'),
content: terms ? terms : {},
});
};
const md =
rationale && rationale.description
? truncate(rationale.description, {
// Limits the description to roughly 5 lines, maximum
length: 350,
})
: '';
return (
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
{id && <ProposalStatusIcon id={id} />}
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
{rationale?.description && (
<p className="pt-2 text-sm leading-tight">
<ReactMarkdown
className="react-markdown-container"
skipHtml={true}
disallowedElements={['img']}
linkTarget="_blank"
>
{md}
</ReactMarkdown>
</p>
)}
<p className="pt-5">
<button className="underline max-md:hidden mr-5" onClick={openDialog}>
{t('View terms')}
</button>{' '}
<ProposalLink id={id} text={t('Full details')} />
{terms && <ProposalDate terms={terms} id={id} />}
</p>
<JsonViewerDialog
open={dialog.open}
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}
title={dialog.title}
content={dialog.content}
/>
</div>
);
};
@@ -55,7 +55,7 @@ export const TxDetailsShared = ({
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
<TableCell>
<Hash text={txData.hash.toLowerCase()} />
<Hash text={txData.hash} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -24,7 +24,6 @@ import { TxDetailsProtocolUpgrade } from './tx-details-protocol-upgrade';
import { TxDetailsIssueSignatures } from './tx-issue-signatures';
import { TxDetailsNodeAnnounce } from './tx-node-announce';
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
import { TxProposal } from './tx-proposal';
import { TxDetailsTransfer } from './tx-transfer';
interface TxDetailsWrapperProps {
@@ -89,8 +88,6 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsOrderAmend;
case 'Validator Heartbeat':
return TxDetailsHeartbeat;
case 'Proposal':
return TxProposal;
case 'Vote on Proposal':
return TxProposalVote;
case 'Batch Market Instructions':
@@ -1,115 +0,0 @@
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { sharedHeaderProps, TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import has from 'lodash/has';
import { ProposalSummary } from './proposal/summary';
import Hash from '../../links/hash';
import { ProposalSignatureBundle } from './proposal/signature-bundle';
import { t } from '@vegaprotocol/i18n';
export type Proposal = components['schemas']['v1ProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
interface TxProposalProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* Detects if a given proposal requires a signature bundle to be emitted by the validators for
* the proposal to be completed after it is accepted by the governance vote
*
* @param t The details of the proposal
* @returns boolean True if a signature bundle is required. Used to fetch a signature bundle
*/
export function proposalRequiresSignatureBundle(proposal?: Proposal): boolean {
if (!proposal?.terms) {
return false;
}
return !!['newAsset', 'updateAsset'].filter((requiredIfExists) =>
has(proposal.terms, requiredIfExists)
);
}
/**
* Given a proposal, returns a nice text label for the proposal type
*
* @param terms The details of the proposal
* @returns string Proposal type
*/
export function proposalTypeLabel(terms?: ProposalTerms): string {
if (has(terms, 'newAsset')) {
return t('New asset proposal');
} else if (has(terms, 'updateAsset')) {
return t('Update asset proposal');
} else if (has(terms, 'newMarket')) {
return t('New market proposal');
} else if (has(terms, 'updateMarket')) {
return t('Update market proposal');
} else if (has(terms, 'updateNetworkParameter')) {
return t('Update network parameter');
} else if (has(terms, 'newFreeform')) {
return t('Freeform proposal');
}
// The list above contains all currently known types. This will be triggered if a new
// unrecognised proposal type is added.
return t('Governance proposal');
}
/**
* A proposal. It's more simplistic than man other views because there are already other, better
* ways to view things about a proposal!
*
*/
export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
if (!txData || !txData.command.proposalSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
let deterministicId = '';
const proposal: Proposal = txData.command.proposalSubmission;
const sig = txData?.signature?.value;
if (sig) {
deterministicId = txSignatureToDeterministicId(sig);
}
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{proposalTypeLabel(proposal.terms)}</TableCell>
</TableRow>
{/* TODO: Disable type row */}
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
hideTypeRow={true}
/>
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
<TableCell>
<Hash text={deterministicId} />
</TableCell>
</TableRow>
</TableWithTbody>
<ProposalSummary
id={deterministicId}
rationale={proposal.rationale}
terms={proposal?.terms}
/>
{proposalRequiresSignatureBundle(proposal) && (
<ProposalSignatureBundle
id={deterministicId}
type={proposal.terms?.newAsset ? 'NewAsset' : 'UpdateAsset'}
/>
)}
</>
);
};
@@ -12,7 +12,6 @@ export const Proposals = () => {
const { data, loading, error } = useDataProvider({
dataProvider: proposalsDataProvider,
variables: {},
});
useDocumentTitle([t('Governance Proposals')]);
@@ -1,14 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { MarketDetails } from '../../components/markets/market-details';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import compact from 'lodash/compact';
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { marketInfoProvider } from '@vegaprotocol/market-info';
import { marketInfoNoCandlesDataProvider } from '@vegaprotocol/market-info';
import { PageTitle } from '../../components/page-helpers/page-title';
export const MarketPage = () => {
@@ -16,17 +16,24 @@ export const MarketPage = () => {
const { marketId } = useParams<{ marketId: string }>();
const variables = useMemo(
() => ({
marketId,
}),
[marketId]
);
const { data, loading, error } = useDataProvider({
dataProvider: marketInfoProvider,
dataProvider: marketInfoNoCandlesDataProvider,
skipUpdates: true,
variables: {
marketId: marketId || '',
skip: !marketId,
},
variables,
});
useDocumentTitle(
compact(['Market details', data?.tradableInstrument.instrument.name])
compact([
'Market details',
data?.market?.tradableInstrument.instrument.name,
])
);
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
@@ -36,10 +43,10 @@ export const MarketPage = () => {
<section className="relative">
<PageTitle
data-testid="markets-heading"
title={data?.tradableInstrument.instrument.name || ''}
title={data?.market?.tradableInstrument.instrument.name || ''}
actions={
<Button
disabled={!data}
disabled={!data?.market}
size="xs"
onClick={() => setDialogOpen(true)}
>
@@ -53,14 +60,14 @@ export const MarketPage = () => {
loading={loading}
error={error}
>
{data && <MarketDetails market={data} />}
<MarketDetails market={data?.market} />
</AsyncRenderer>
</section>
<JsonViewerDialog
open={dialogOpen}
onChange={(isOpen) => setDialogOpen(isOpen)}
title={data?.tradableInstrument.instrument.name || ''}
content={data}
title={data?.market?.tradableInstrument.instrument.name || ''}
content={data?.market}
/>
</>
);
@@ -13,7 +13,6 @@ export const MarketsPage = () => {
const { data, loading, error } = useDataProvider({
dataProvider: marketsProvider,
variables: undefined,
skipUpdates: true,
});
@@ -9,7 +9,7 @@ import type {
// Note: Long enough that there is a truncated output and a full output
const pubKey =
'67755549e43e95f0697f83b2bf419c6ccc18eee32a8a61b8ba6f59471b86fbef';
const hash = '7416753a30622a9e24a06f0172d6c33a95186b36806d96345c6dc5a23fa3f283';
const hash = '7416753A30622A9E24A06F0172D6C33A95186B36806D96345C6DC5A23FA3F283';
const height = '52987';
const txData: BlockExplorerTransactionResult = {
-9
View File
@@ -2,12 +2,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.react-markdown-container a {
color: #ff077f;
text-decoration: underline;
}
.react-markdown-container a:before {
content: '🔗 ';
}
+1 -1
View File
@@ -11,7 +11,7 @@
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"lib": ["es2021", "dom", "dom.iterable"]
"lib": ["es5", "es6", "dom", "dom.iterable"]
},
"exclude": ["./src/types/explorer.d.ts"],
"include": [],
+2 -2
View File
@@ -19,8 +19,8 @@ module.exports = defineConfig({
supportFile: './src/support/index.js',
video: false,
videoUploadOnPasses: false,
videosFolder: '../../dist/cypress/apps/governance-e2e/videos',
screenshotsFolder: '../../dist/cypress/apps/governance-e2e/screenshots',
videosFolder: '../../dist/cypress/apps/token-e2e/videos',
screenshotsFolder: '../../dist/cypress/apps/token-e2e/screenshots',
chromeWebSecurity: false,
viewportWidth: 1440,
viewportHeight: 900,
@@ -1,83 +0,0 @@
export const trancheData = {
'0': {
tranche_id: 0,
users: [],
initial_balance: 0,
current_balance: 0,
cliff_start: 0,
duration: 0,
},
'1': {
tranche_id: 1,
users: [],
initial_balance: 0,
current_balance: 0,
cliff_start: 1670422330,
duration: 1670453883,
},
'107': {
tranche_id: 107,
users: [],
initial_balance: 0,
current_balance: 0,
cliff_start: 0,
duration: 0,
},
'110': {
tranche_id: 110,
users: [],
initial_balance: 0,
current_balance: 0,
cliff_start: 0,
duration: 0,
},
'154': {
tranche_id: 154,
users: [],
initial_balance: 0,
current_balance: 0,
cliff_start: 0,
duration: 0,
},
'2': {
tranche_id: 2,
users: [
'0x8716c75bb3abe54b959adc529e6355f0422454ed',
'0xc704da0e96a6b910ffa8d3f9f667d3d35db8e5a1',
'0xbb82d4d6e4381ae98f8c33629285867b54d91a03',
'0x82ffe4ed818a6c7d4f0f044998611f24ec916c43',
'0xb0594132540ebc14f17863f8296f4546a7afe4e7',
'0x9015a3db9a922521007a9b495cafda14d8d4128e',
'0x5426f7f717fdbe331bb5a99b908ad2fb75dff711',
'0xf73e7ad8aa300a7f86bcc4d55e6c4ea25546e057',
],
initial_balance: 111300000000000000000,
current_balance: 111297025049610000000,
cliff_start: 1677578461,
duration: 15209600,
},
'3': {
tranche_id: 3,
users: [
'0xbb82d4d6e4381ae98f8c33629285867b54d91a03',
'0x82ffe4ed818a6c7d4f0f044998611f24ec916c43',
'0xb0594132540ebc14f17863f8296f4546a7afe4e7',
'0x9015a3db9a922521007a9b495cafda14d8d4128e',
'0x77a0b7e247b7b8ec99e068a24c401783d6c4dfff',
'0x5426f7f717fdbe331bb5a99b908ad2fb75dff711',
'0xc704da0e96a6b910ffa8d3f9f667d3d35db8e5a1',
],
initial_balance: 21000000000000000000,
current_balance: 21000000000000000000,
cliff_start: 1677578461,
duration: 18628800,
},
'66': {
tranche_id: 66,
users: [],
initial_balance: 0,
current_balance: 0,
cliff_start: 0,
duration: 0,
},
};
@@ -1,97 +0,0 @@
import { trancheData } from '../../fixtures/mocks/tranches';
const tranches = trancheData;
context(
'Tranches page - verify elements on the page',
{ tags: '@smoke' },
function () {
before('visit homepage', function () {
cy.intercept('GET', '**/tranches/stats', { tranches });
cy.visit('/');
});
it('Able to navigate to tranches page', function () {
cy.navigate_to('supply');
cy.url().should('include', '/token/tranches');
cy.get('h1').should('contain.text', 'Vesting tranches');
});
// 1005-VEST-001
// 1005-VEST-002
it('Able to view tranches', function () {
cy.getByTestId('tranche-item')
.should('have.length', 2)
.first()
.within(() => {
cy.get('a')
.should('have.text', 'Tranche 2') // 1005-VEST-003
.and('have.attr', 'href', '/token/tranches/2');
cy.get('span').eq(1).should('have.text', '111.30'); // 1005-VEST-005
cy.contains('Unlocking starts') // 1005-VEST-008
.parent()
.should('contain.text', '28 Feb 2023');
cy.contains('Fully unlocked')
.parent()
.should('contain.text', '23 Aug 2023');
cy.getByTestId('progress-bar').should('exist');
cy.getByTestId('currency-locked') // 1005-VEST-006
.invoke('text')
.should('not.be.empty');
cy.getByTestId('currency-unlocked') // 1005-VEST-007
.invoke('text')
.should('not.be.empty');
});
});
it('Able to see individual tranche data', function () {
cy.get('[href="/token/tranches/2"]').click();
cy.getByTestId('redeemed-tranche-tokens').within(() => {
cy.get('span').eq(1).should('have.text', 0);
});
cy.getByTestId('key-value-table').within(() => {
cy.getByTestId('link')
.should('have.length', 8)
.each((ethLink) => {
cy.wrap(ethLink)
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/');
});
cy.getByTestId('redeem-link')
.should('have.length', 8)
.each((redeemLink) => {
cy.wrap(redeemLink)
.should('have.attr', 'href')
.and('contain', '/token/redeem/');
});
});
});
it('Able to view tranches with less than 10 vega', function () {
cy.navigate_to('supply');
cy.getByTestId('show-all-tranches').click();
cy.getByTestId('tranche-item')
.should('have.length', 8)
.first()
.within(() => {
cy.get('a')
.should('have.text', 'Tranche 0')
.and('have.attr', 'href', '/token/tranches/0');
cy.get('span').eq(1).should('have.text', '0.00');
cy.contains('Unlocking starts')
.parent()
.should('contain.text', '01 Jan 1970');
cy.contains('Fully unlocked')
.parent()
.should('contain.text', '01 Jan 1970');
cy.getByTestId('progress-bar').should('exist');
cy.getByTestId('currency-locked')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('currency-unlocked')
.invoke('text')
.should('not.be.empty');
});
});
}
);
@@ -1,15 +1,13 @@
const connectButton = '[data-testid="connect-to-eth-btn"]';
const lockedTokensInVestingContract = '6,499,972.30';
context(
'Vesting Page - verify elements on page',
{ tags: '@smoke' },
function () {
before('navigate to vesting page', function () {
cy.visit('/').navigate_to('vesting');
});
describe('with wallets disconnected', function () {
before('navigate to vesting page', function () {
cy.visit('/').navigate_to('vesting');
});
it('should have vesting tab highlighted', function () {
cy.verify_tab_highlighted('token');
});
@@ -18,7 +16,10 @@ context(
cy.verify_page_header('Vesting');
});
// 1005-VEST-018
it('should have connect Eth wallet info', function () {
cy.get(connectButton).should('be.visible');
});
it('should have connect Eth wallet button', function () {
cy.get(connectButton)
.should('be.visible')
@@ -26,101 +27,18 @@ context(
});
});
describe('With Eth wallet connected', function () {
describe('with eth wallet connected', function () {
before('connect eth wallet', function () {
cy.ethereum_wallet_connect();
cy.getByTestId('view-connected-eth-btn').click();
cy.visit('/');
});
// 1005-VEST-020 1005-VEST-021
it('Tokens in vesting contract for eth wallet is displayed on wallet window', function () {
cy.getByTestId('vega-in-vesting-contract').within(() => {
cy.getByTestId('currency-title')
.should('contain.text', 'VEGA')
.and('contain.text', 'In vesting contract');
cy.getByTestId('currency-value').should(
'have.text',
lockedTokensInVestingContract
);
cy.getByTestId('currency-locked').should(
'have.text',
lockedTokensInVestingContract
);
cy.getByTestId('currency-unlocked').should('have.text', '0.00');
});
});
// 1005-VEST-022 1005-VEST-023
it('Tokens amount displayed in vesting page', function () {
cy.getByTestId(
'redemption-description',
Cypress.env('txTimeout')
).should('exist');
const redemptionText =
'The connected Ethereum wallet (0xEe7D…d94F) has 6,499,972.30 $VEGA tokens in 1 tranche(s) of the vesting contract.';
cy.getByTestId('redemption-description').should(
'have.text',
redemptionText
);
cy.getByTestId('vesting-table').within(() => {
cy.getByTestId('key-value-table-row')
.eq(0)
.within(() => {
cy.get('dt').should('have.text', 'Vesting VEGA');
cy.get('dd').should('have.text', lockedTokensInVestingContract);
});
cy.getByTestId('key-value-table-row')
.eq(1)
.within(() => {
cy.get('dt').should('have.text', 'Locked');
cy.get('dd').should('have.text', lockedTokensInVestingContract);
});
cy.getByTestId('key-value-table-row')
.eq(2)
.within(() => {
cy.get('dt').should('have.text', 'Unlocked');
cy.get('dd').should('have.text', '0.00');
});
cy.getByTestId('key-value-table-row')
.eq(3)
.within(() => {
cy.get('dt').should('have.text', 'Associated');
cy.get('dd').should('have.text', '0.00');
});
});
});
// 1005-VEST-024 1005-VEST-025 1005-VEST-026 1005-VEST-036 1005-VEST-037
it('Tokens locked in individual tranches are displayed', function () {
cy.getByTestId('tranche-table-footer').should(
'have.text',
'All the tokens in this tranche are locked and must be assigned to a tranche before they can be redeemed.'
);
cy.getByTestId('tranche-item').within(() => {
cy.get('a')
.should('have.text', 'Tranche 0')
.and('have.attr', 'href', '/token/tranches/0');
cy.get('span')
.eq(1)
.should('have.text', lockedTokensInVestingContract);
cy.contains('Unlocking starts') // 1005-VEST-008
.parent()
.should('contain.text', '01 Jan 1970');
cy.contains('Fully unlocked')
.parent()
.should('contain.text', '01 Jan 1970');
cy.getByTestId('progress-bar').should('exist');
cy.getByTestId('currency-locked') // 1005-VEST-006
.should('have.text', lockedTokensInVestingContract);
cy.getByTestId('currency-unlocked') // 1005-VEST-007
.invoke('text')
.should('not.be.empty');
cy.getByTestId('tranche-item-footer').should(
'have.text',
'All the tokens in this tranche are locked and can not be redeemed yet.'
);
});
cy.connectVegaWallet();
// 1005-VEST-001
// 1005-VEST-002
it('Able to view tranches', function () {
cy.navigate_to('supply');
cy.url().should('include', '/token/tranches');
cy.get('h1').should('contain.text', 'Vesting tranches');
});
});
}
+1 -1
View File
@@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
NX_VEGA_URL=https://api.validators-testnet.vega.xyz/graphql
NX_VEGA_URL=https://api.validators-testnet.vega.xyz/query
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
@@ -11,7 +11,7 @@ export const EthConnectPrompt = () => {
const { appDispatch } = useAppState();
return (
<Button
variant="default"
variant="primary"
onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
@@ -336,7 +336,6 @@
"withdrawLpNoneDeposited": "You have no SLP tokens deposited or rewards accumulated",
"withdrawAllLpSuccessCalloutTitle": "Your SLP tokens and rewards have been sent to your Ethereum address",
"Dissociate VEGA tokens": "Disassociate $VEGA tokens",
"DisassociateVegaTokensFromWallet": "Disassociate $VEGA tokens from your Vega wallet",
"Early Investors": "Early Investors",
"Team": "Team",
"Community": "Community",
@@ -483,7 +482,6 @@
"withdrawFormSubmitButtonIdle": "Withdraw {{amount}} {{symbol}} tokens",
"withdrawFormSubmitButtonPending": "Preparing",
"withdrawalsTitle": "Withdrawals",
"withdrawalsSubtitle": "Connect your Vega wallet to complete a withdrawal. These withdrawals will be completed with an Ethereum transaction.",
"withdrawalsText": "These withdrawals need to be completed with an Ethereum transaction.",
"withdrawalsNone": "You don't have any pending withdrawals.",
"withdrawalsCompleteButton": "Finish withdrawal",
@@ -554,6 +552,9 @@
"resourceNotFound": "Resource Not Found",
"Or": "Or",
"addTokenToWallet": "Show this token in your Ethereum wallet",
"chartBelow": "*Certain activities, for example community incentive tokens, do not have specific dates to be credited and so an approximation has been used. ",
"chartTitle": "Token unlocking schedule",
"chartAbove": "This chart shows the approximate* schedule for how tokens will unlock from vesting tranches over time.",
"date": "Date",
"noEthereumProviderError": "No Ethereum browser extension detected, install MetaMask on desktop or visit from a dApp browser on mobile",
"unsupportedChainIdError": "You're connected to an unsupported network",
@@ -84,7 +84,6 @@ const RedemptionRouter = () => {
fill={true}
variant="primary"
onClick={() => navigate(`${RoutesConfig.REDEEM}/${account}`)}
data-testid="view-connected-eth-btn"
>
{t('View connected Eth Wallet')}
</Button>
@@ -15,12 +15,7 @@ export const DisassociateContainer = () => {
<>
<Heading title={t('pageTitleDisassociate')} />
{!account ? (
<>
<p className="mb-8">{t('DisassociateVegaTokensFromWallet')}</p>
<div className="max-w-[400px]">
<EthConnectPrompt />
</div>
</>
<EthConnectPrompt />
) : (
<DisassociatePage address={account} vegaKey={pubKey ?? ''} />
)}
@@ -73,7 +73,6 @@ export const Tranche = () => {
className="underline"
title={t('View vesting information')}
to={`${Routes.REDEEM}/${user}`}
data-testid="redeem-link"
>
{t('View vesting information')}
</RouterLink>
@@ -1,12 +1,15 @@
import { useWeb3React } from '@web3-react/core';
import { useState } from 'react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { SubHeading } from '../../components/heading';
import { TrancheItem } from '../redemption/tranche-item';
import { TrancheLabel } from './tranche-label';
import { useTranches } from '../../lib/tranches/tranches-store';
import { VestingChart } from './vesting-chart';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { useEthereumConfig } from '@vegaprotocol/web3';
import type { Tranche } from '../../lib/tranches/tranches-store';
import { useTranches } from '../../lib/tranches/tranches-store';
const trancheMinimum = 10;
@@ -15,7 +18,7 @@ const shouldShowTranche = (t: Tranche) =>
export const Tranches = () => {
const tranches = useTranches((state) => state.tranches);
const [showAll, setShowAll] = useState<boolean>(false);
const [showAll, setShowAll] = React.useState<boolean>(false);
const { t } = useTranslation();
const { chainId } = useWeb3React();
const { config } = useEthereumConfig();
@@ -27,6 +30,10 @@ export const Tranches = () => {
return (
<section>
<SubHeading title={t('chartTitle')} />
<p>{t('chartAbove')}</p>
<VestingChart />
<p>{t('chartBelow')}</p>
{tranches?.length ? (
<ul role="list">
{(showAll ? tranches : filteredTranches).map((tranche) => {
@@ -48,12 +55,8 @@ export const Tranches = () => {
) : (
<p>{t('No tranches')}</p>
)}
<section className="text-center mt-4">
<ButtonLink
data-testid="show-all-tranches"
onClick={() => setShowAll(!showAll)}
>
<ButtonLink onClick={() => setShowAll(!showAll)}>
{showAll
? t(
'Showing tranches with <{{trancheMinimum}} VEGA, click to hide these tranches',
@@ -0,0 +1,149 @@
import { format, startOfMonth } from 'date-fns';
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Area,
AreaChart,
Label,
Legend,
ReferenceLine,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import colors from 'tailwindcss/colors';
import { DATE_FORMAT_LONG } from '../../lib/date-formats';
import data from './data.json';
import { theme } from '@vegaprotocol/tailwindcss-config';
const Colors = theme.colors;
const ORDER = ['community', 'publicSale', 'earlyInvestors', 'team'];
export const VestingChart = () => {
const { t } = useTranslation();
const currentDate = React.useMemo(
() => format(startOfMonth(new Date()), DATE_FORMAT_LONG),
[]
);
return (
<div style={{ marginBottom: 25 }}>
<ResponsiveContainer height={400} width="100%">
<AreaChart data={data}>
<defs>
{[
['pink', Colors.vega.pink.DEFAULT],
['green', Colors.vega.green.DEFAULT],
['orange', Colors.warning],
['yellow', Colors.vega.yellow.DEFAULT],
].map(([key, color]) => (
<linearGradient key={key} id={key} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity={0.85} />
<stop offset="100%" stopColor={color} stopOpacity={0} />
</linearGradient>
))}
</defs>
<Tooltip
contentStyle={{ backgroundColor: colors.black }}
separator=":"
// @ts-ignore formatter doesnt seem to allow returning JSX but nonetheless it works
formatter={(value: number) => {
return (
<div
style={{
display: 'flex',
textAlign: 'right',
}}
>
{Intl.NumberFormat().format(value)}
</div>
);
}}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
itemSorter={(label: any) => {
return ORDER.indexOf(label.dataKey) + 1;
}}
/>
<YAxis type="number" width={100}>
<Label
angle={270}
value={t('VEGA').toString()}
position="left"
offset={-5}
fill={colors.white}
/>
</YAxis>
<XAxis dataKey="date">
<Label
value={t('date').toString()}
position="bottom"
offset={5}
fill={colors.white}
/>
</XAxis>
<ReferenceLine
x={currentDate}
stroke={colors.white}
strokeWidth={2}
label={{
position: 'left',
value: currentDate,
fill: colors.white,
}}
/>
<Area
dot={false}
type="linear"
dataKey="team"
stroke={Colors.vega.pink.DEFAULT}
fill="url(#pink)"
yAxisId={0}
strokeWidth={2}
fillOpacity={0.85}
stackId="1"
name={t('Team')}
/>
<Area
dot={false}
type="monotone"
dataKey="earlyInvestors"
stroke={Colors.vega.green.DEFAULT}
fill="url(#green)"
yAxisId={0}
strokeWidth={2}
fillOpacity={0.85}
stackId="1"
name={t('Early Investors')}
/>
<Area
dot={false}
type="monotone"
dataKey="publicSale"
stroke={Colors.vega.yellow.DEFAULT}
fill="url(#yellow)"
yAxisId={0}
strokeWidth={2}
stackId="1"
fillOpacity={0.85}
name={t('Public Sale')}
/>
<Area
dot={false}
type="monotone"
dataKey="community"
stroke={Colors.warning}
fill="url(#orange)"
yAxisId={0}
strokeWidth={2}
fillOpacity={0.85}
stackId="1"
name={t('Community')}
/>
<Legend wrapperStyle={{ position: 'relative' }} />
</AreaChart>
</ResponsiveContainer>
</div>
);
};
@@ -17,14 +17,12 @@ import type { RouteChildProps } from '../index';
const Withdrawals = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
return (
<>
<Heading title={t('withdrawalsTitle')} />
{!pubKey && <p className="mb-8">{t('withdrawalsSubtitle')}</p>}
<VegaWalletContainer>
{() => <WithdrawPendingContainer />}
{(currVegaKey) => <WithdrawPendingContainer />}
</VegaWalletContainer>
</>
);
@@ -1,4 +1,5 @@
import { useParams } from 'react-router-dom';
import { useMemo } from 'react';
import { makeDerivedDataProvider } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
@@ -42,7 +43,7 @@ const useMarketDetails = (marketId: string | undefined) => {
const { data, loading, error } = useDataProvider({
dataProvider: lpDataProvider,
skipUpdates: true,
variables: { marketId: marketId || '' },
variables: useMemo(() => ({ marketId }), [marketId]),
});
const liquidityProviders = data?.liquidityProviders || [];
@@ -39,11 +39,14 @@ export const Last24hVolume = ({
[marketId, yTimestamp]
);
const variables24hAgo = {
marketId: marketId,
interval: Schema.Interval.INTERVAL_I1D,
since: yTimestamp,
};
const variables24hAgo = useMemo(
() => ({
marketId: marketId,
interval: Schema.Interval.INTERVAL_I1D,
since: yTimestamp,
}),
[marketId, yTimestamp]
);
const throttledSetCandles = useRef(
throttle((data: Candle[]) => {
@@ -61,7 +64,7 @@ export const Last24hVolume = ({
[throttledSetCandles]
);
const { data, error } = useDataProvider({
const { data, error } = useDataProvider<Candle[], Candle>({
dataProvider: marketCandlesProvider,
variables: variables,
update,
@@ -85,7 +88,7 @@ export const Last24hVolume = ({
[throttledSetVolumeChange]
);
useDataProvider({
useDataProvider<Candle[], Candle>({
dataProvider: marketCandlesProvider,
update: updateCandle24hAgo,
variables: variables24hAgo,
@@ -1,5 +0,0 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
NX_VEGA_URL=https://api.validators-testnet.vega.xyz/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=VALIDATOR_TESTNET
+2 -1
View File
@@ -10,7 +10,8 @@
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"lib": ["es5", "es6", "dom", "dom.iterable"]
},
"include": [],
"references": [
File diff suppressed because it is too large Load Diff
@@ -41,7 +41,6 @@ const completeWithdrawalBtn = 'complete-withdrawal';
const submitTransferBtn = '[type="submit"]';
const transferForm = 'transfer-form';
const depositSubmit = 'deposit-submit';
const approveSubmit = 'approve-submit';
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
describe('capsule - without MultiSign', { tags: '@slow' }, () => {
@@ -74,13 +73,13 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('approve-default').should(
cy.getByTestId('approve-warning').should(
'contain.text',
`Before you can make a deposit of your chosen asset, ${btcSymbol}, you need to approve its use in your Ethereum wallet`
`Deposits of ${btcSymbol} not approved`
);
cy.getByTestId(approveSubmit).click();
cy.getByTestId('approve-pending').should('exist');
cy.getByTestId('approve-confirmed').should('exist');
cy.getByTestId(depositSubmit).click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('10');
cy.getByTestId(depositSubmit).click();
cy.getByTestId(toastContent, txTimeout).should(
+7 -14
View File
@@ -31,13 +31,7 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
it('handles empty fields', () => {
cy.getByTestId('deposit-submit').click();
cy.getByTestId(formFieldError).should('contain.text', 'Required');
// once Ethereum wallet is connected and key selected the only field that will
// error is the asset select
cy.getByTestId(formFieldError).should('have.length', 1);
cy.get('[data-testid="input-error-text"][aria-describedby="asset"]').should(
'have.length',
1
);
cy.getByTestId(formFieldError).should('have.length', 2);
});
it('unable to select assets not enabled', () => {
@@ -47,13 +41,12 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
cy.get(assetSelectField + ' option:contains(Asset 4)').should('not.exist');
});
it('invalid public key when entering address manually', () => {
cy.getByTestId('enter-pubkey-manually').click();
cy.get(toAddressField).clear().type('INVALID_DEPOSIT_TO_ADDRESS');
cy.get(`[data-testid="${formFieldError}"][aria-describedby="to"]`).should(
'have.text',
'Invalid Vega key'
);
it('invalid public key', () => {
cy.get(toAddressField)
.clear()
.type('INVALID_DEPOSIT_TO_ADDRESS')
.next(`[data-testid="${formFieldError}"]`)
.should('have.text', 'Invalid Vega key');
});
it('invalid amount', () => {
@@ -26,20 +26,20 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
it('market price', () => {
cy.getByTestId(marketTitle).contains('Market price').click();
validateMarketDataRow(0, 'Mark Price', '46,126.90058');
validateMarketDataRow(1, 'Best Bid Price', '44,126.90058 ');
validateMarketDataRow(2, 'Best Offer Price', '48,126.90058 ');
validateMarketDataRow(0, 'Mark Price', '0.05749');
validateMarketDataRow(1, 'Best Bid Price', '6.81765 ');
validateMarketDataRow(2, 'Best Offer Price', '6.81769 ');
validateMarketDataRow(3, 'Quote Unit', 'BTC');
});
it('market volume displayed', () => {
cy.getByTestId(marketTitle).contains('Market volume').click();
validateMarketDataRow(0, '24 Hour Volume', '1');
validateMarketDataRow(0, '24 Hour Volume', '-');
validateMarketDataRow(1, 'Open Interest', '0');
validateMarketDataRow(2, 'Best Bid Volume', '1');
validateMarketDataRow(3, 'Best Offer Volume', '3');
validateMarketDataRow(4, 'Best Static Bid Volume', '2');
validateMarketDataRow(5, 'Best Static Offer Volume', '4');
validateMarketDataRow(2, 'Best Bid Volume', '5');
validateMarketDataRow(3, 'Best Offer Volume', '1');
validateMarketDataRow(4, 'Best Static Bid Volume', '5');
validateMarketDataRow(5, 'Best Static Offer Volume', '1');
});
it('insurance pool displayed', () => {
@@ -149,9 +149,9 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
.contains(/Liquidity(?! m)/)
.click();
validateMarketDataRow(0, 'Target Stake', '10.00 tBTC');
validateMarketDataRow(1, 'Supplied Stake', '0.01 tBTC');
validateMarketDataRow(2, 'Market Value Proxy', '20.00 tBTC');
validateMarketDataRow(0, 'Target Stake', '0.56789 tBTC');
validateMarketDataRow(1, 'Supplied Stake', '0.56767 tBTC');
validateMarketDataRow(2, 'Market Value Proxy', '6.77678 tBTC');
cy.getByTestId('view-liquidity-link').should(
'have.text',
@@ -163,8 +163,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
cy.getByTestId(marketTitle).contains('Liquidity price range').click();
validateMarketDataRow(0, 'Liquidity Price Range', '2.00% of mid price');
validateMarketDataRow(1, 'Lowest Price', '45,204.362 BTC');
validateMarketDataRow(2, 'Highest Price', '47,049.438 BTC');
validateMarketDataRow(1, 'Lowest Price', '0.05634 BTC');
validateMarketDataRow(2, 'Highest Price', '0.05864 BTC');
});
it('oracle displayed', () => {
@@ -38,7 +38,7 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId('tab-accounts')
.get(tradingAccountRowId)
.find('[col-id="deposited"]')
.should('have.text', '100,001.01');
.should('have.text', '1,001.00');
});
describe('sorting by ag-grid columns should work well', () => {
it('sorting by asset', () => {
@@ -58,24 +58,24 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = [
'1,000.00002',
'100,001.01',
'1,001.00',
'1,000.01',
'1,000.01',
'1,000.00',
'1,000.00001',
];
const marketsSortedAsc = [
'1,000.00',
'1,000.00001',
'1,000.00002',
'1,000.01',
'100,001.01',
'1,000.01',
'1,001.00',
];
const marketsSortedDesc = [
'100,001.01',
'1,001.00',
'1,000.01',
'1,000.01',
'1,000.00002',
'1,000.00001',
'1,000.00',
];
checkSorting(
'deposited',
@@ -87,9 +87,9 @@ describe('accounts', { tags: '@smoke' }, () => {
it('sorting by used', () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = ['0.00', '1.01', '0.01', '0.00', '0.00'];
const marketsSortedAsc = ['0.00', '0.00', '0.00', '0.01', '1.01'];
const marketsSortedDesc = ['1.01', '0.01', '0.00', '0.00', '0.00'];
const marketsSortedDefault = ['0.00', '1.00', '0.01', '0.01', '0.00'];
const marketsSortedAsc = ['0.00', '0.00', '0.01', '0.01', '1.00'];
const marketsSortedDesc = ['1.00', '0.01', '0.01', '0.00', '0.00'];
checkSorting(
'used',
marketsSortedDefault,
@@ -102,24 +102,24 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = [
'1,000.00002',
'100,000.00',
'1,000.00',
'1,000.00',
'1,000.00',
'1,000.00001',
];
const marketsSortedAsc = [
'1,000.00',
'1,000.00',
'1,000.00',
'1,000.00001',
'1,000.00002',
'100,000.00',
];
const marketsSortedDesc = [
'100,000.00',
'1,000.00002',
'1,000.00001',
'1,000.00',
'1,000.00',
'1,000.00',
];
checkSorting(
@@ -2,11 +2,7 @@ import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery, mockConnectWallet } from '@vegaprotocol/cypress';
import { testOrderSubmission } from '../support/order-validation';
import type { OrderSubmission } from '@vegaprotocol/wallet';
import {
accountsQuery,
estimateOrderQuery,
amendGeneralAccountBalance,
} from '@vegaprotocol/mock';
import { accountsQuery, estimateOrderQuery } from '@vegaprotocol/mock';
import { createOrder } from '../support/create-order';
const orderSizeField = 'order-size';
@@ -587,10 +583,6 @@ describe('suspended market validation', { tags: '@regression' }, () => {
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
);
const accounts = accountsQuery();
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Accounts', accounts);
});
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
@@ -640,10 +632,30 @@ describe('account validation', { tags: '@regression' }, () => {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
const accounts = accountsQuery();
amendGeneralAccountBalance(accounts, 'market-0', '0');
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Accounts', accounts);
aliasGQLQuery(
req,
'Accounts',
accountsQuery({
party: {
accountsConnection: {
edges: [
{
node: {
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '0',
market: null,
asset: {
__typename: 'Asset',
id: 'asset-0',
},
},
},
],
},
},
})
);
});
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -657,7 +669,7 @@ describe('account validation', { tags: '@regression' }, () => {
'have.text',
'You need ' +
'tDAI' +
' in your wallet to trade in this market.See all your collateral.Make a deposit'
' in your wallet to trade in this market. See all your collateral.Make a deposit'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist');
});
@@ -667,13 +679,19 @@ describe('account validation', { tags: '@regression' }, () => {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
const accounts = accountsQuery();
amendGeneralAccountBalance(accounts, 'market-0', '100000000');
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Accounts', accounts);
});
cy.mockGQL((req) => {
aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery());
aliasGQLQuery(
req,
'EstimateOrder',
estimateOrderQuery({
estimateOrder: {
marginLevels: {
__typename: 'MarginLevels',
initialLevel: '1000000000',
},
},
})
);
});
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -689,7 +707,7 @@ describe('account validation', { tags: '@regression' }, () => {
);
cy.getByTestId('dealticket-warning-margin').should(
'contain.text',
'You may not have enough margin available to open this position. 2,354.72283 tDAI is currently required. You have only 1,000.01 tDAI available.'
'9,999.99 tDAI is currently required. You have only 1,000.00 tDAI available.Deposit tDAI'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').click();
cy.getByTestId('dialog-content')
@@ -31,7 +31,7 @@ describe('orders list', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-0');
cy.getByTestId('Orders').click();
cy.wait('@Orders').then(() => {
expect(subscriptionMocks.OrdersUpdate).to.be.calledThrice;
expect(subscriptionMocks.OrdersUpdate).to.be.calledTwice;
});
cy.wait('@Markets');
});
@@ -136,7 +136,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-0');
cy.getByTestId('Orders').click();
cy.wait('@Orders').then(() => {
expect(subscriptionMocks.OrdersUpdate).to.be.calledThrice;
expect(subscriptionMocks.OrdersUpdate).to.be.calledTwice;
});
});
const orderId = '1234567890';
@@ -354,7 +354,7 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-0');
cy.getByTestId('Orders').click();
cy.wait('@Orders').then(() => {
expect(subscriptionMocks.OrdersUpdate).to.be.calledThrice;
expect(subscriptionMocks.OrdersUpdate).to.be.calledTwice;
});
cy.mockVegaWalletTransaction();
});
@@ -20,78 +20,46 @@ describe('positions', { tags: '@smoke' }, () => {
cy.getByTestId('Positions').click();
validatePositionsDisplayed();
});
describe('renders position among some graphql errors', () => {
it('rows should be displayed despite errors', () => {
const errors = [
{
message:
'no market data for market: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
path: ['marketsConnection', 'edges'],
extensions: {
code: 13,
type: 'Internal',
},
it('renders position among some graphql errors', () => {
const errors = [
{
message: 'no market data for market: market-2',
path: ['market', 'data'],
extensions: {
code: 13,
type: 'Internal',
},
];
const marketData = marketsDataQuery();
const edges = marketData.marketsConnection?.edges.map((market) => {
const replace =
market.node.data?.market.id === 'market-2' ? null : market.node.data;
return { ...market, node: { ...market.node, data: replace } };
});
const overrides = {
...marketData,
marketsConnection: { ...marketData.marketsConnection, edges },
};
cy.mockGQL((req) => {
aliasGQLQuery(req, 'MarketsData', overrides, errors);
});
cy.visit('/#/markets/market-0');
const emptyCells = [
'notional',
'markPrice',
'liquidationPrice',
'currentLeverage',
'averageEntryPrice',
];
cy.getByTestId('tab-positions').within(() => {
cy.get('[row-id="market-2"]').within(() => {
emptyCells.forEach((cell) => {
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
});
},
];
const marketData = marketsDataQuery();
const edges = marketData.marketsConnection?.edges.map((market) => {
const replace =
market.node.data?.market.id === 'market-2' ? null : market.node.data;
return { ...market, node: { ...market.node, data: replace } };
});
const overrides = {
...marketData,
marketsConnection: { ...marketData.marketsConnection, edges },
};
cy.mockGQL((req) => {
aliasGQLQuery(req, 'MarketsData', overrides, errors);
});
cy.visit('/#/markets/market-0');
const emptyCells = [
'notional',
'markPrice',
'liquidationPrice',
'currentLeverage',
'averageEntryPrice',
];
cy.getByTestId('tab-positions').within(() => {
cy.get('[row-id="market-2"]').within(() => {
emptyCells.forEach((cell) => {
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
});
});
});
it('error message should be displayed', () => {
const errors = [
{
message:
'no market data for asset: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
path: ['assets', 'edges'],
extensions: {
code: 13,
type: 'Internal',
},
},
];
const marketData = marketsDataQuery();
const edges = marketData.marketsConnection?.edges.map((market) => {
const replace =
market.node.data?.market.id === 'market-2' ? null : market.node.data;
return { ...market, node: { ...market.node, data: replace } };
});
const overrides = {
...marketData,
marketsConnection: { ...marketData.marketsConnection, edges },
};
cy.mockGQL((req) => {
aliasGQLQuery(req, 'MarketsData', overrides, errors);
});
cy.visit('/#/markets/market-0');
cy.get('.pointer-events-none.absolute.inset-0').contains(
'Something went wrong:'
);
});
});
describe('sorting by ag-grid columns should work well', () => {
@@ -164,10 +132,10 @@ describe('positions', { tags: '@smoke' }, () => {
cy.get('[col-id="liquidationPrice"]').should('contain.text', '0'); // liquidation price
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
cy.get('[col-id="currentLeverage"]').should('contain.text', '138.446.1');
cy.get('[col-id="marginAccountBalance"]') // margin allocated
.should('contain.text', '0.01');
.should('contain.text', '1,000');
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
+1 -43
View File
@@ -17,13 +17,6 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
});
it('can connect', () => {
// 0002-WCON-002
// 0002-WCON-003
// 0002-WCON-039
// 0002-WCON-017
// 0002-WCON-018
// 0002-WCON-019
// Mock authentication
cy.intercept('POST', 'https://wallet.testnet.vega.xyz/api/v1/auth/token', {
body: {
@@ -48,9 +41,6 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
},
});
cy.getByTestId(connectVegaBtn).click();
cy.contains(
'Choose wallet app to connect, or to change port or server URL enter a custom wallet location first'
);
cy.contains('Connect Vega wallet');
cy.contains('Hosted Fairground wallet');
@@ -61,13 +51,9 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
cy.getByTestId(form).find('#passphrase').click().type('pass');
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId(manageVegaBtn).should('exist');
cy.getByTestId('manage-vega-wallet').click();
cy.getByTestId('keypair-list').should('exist');
});
it('doesnt connect with invalid credentials', () => {
// 0002-WCON-020
// Mock incorrect username/password
cy.intercept('POST', 'https://wallet.testnet.vega.xyz/api/v1/auth/token', {
body: {
@@ -113,10 +99,6 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
});
it('can connect', () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
mockConnectWallet();
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
@@ -128,40 +110,16 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
});
it('can change selected public key and disconnect', () => {
// 0002-WCON-022
// 0002-WCON-023
// 0002-WCON-025
// 0002-WCON-026
// 0002-WCON-021
// 0002-WCON-027
// 0002-WCON-030
// 0002-WCON-029
// 0002-WCON-008
// 0002-WCON-035
// 0002-WCON-014
// 0002-WCON-010
mockConnectWallet();
const key2 = Cypress.env('VEGA_PUBLIC_KEY2');
const truncatedKey2 = Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY2');
cy.connectVegaWallet();
cy.getByTestId('manage-vega-wallet').click();
cy.getByTestId('keypair-list').should('exist');
cy.getByTestId(`key-${key2}`).should('contain.text', truncatedKey2);
cy.getByTestId(`key-${key2}`)
.find('[data-testid="copy-vega-public-key"]')
.should('be.visible');
cy.get(`[data-testid="key-${key2}"] > .mr-2`).click();
cy.getByTestId('keypair-list')
.find('[data-state="checked"]')
.should('be.visible');
cy.getByTestId('disconnect').click();
cy.getByTestId('connect-vega-wallet').should('exist');
cy.getByTestId('manage-vega-wallet').should('not.exist');
cy.getByTestId('connect-vega-wallet').click();
cy.contains(
'Choose wallet app to connect, or to change port or server URL enter a custom wallet location first'
);
});
});
@@ -193,7 +151,7 @@ describe('ethereum wallet', { tags: '@smoke' }, () => {
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
cy.get('#ethereum-address').should('have.value', ethWalletAddress);
cy.getByTestId('disconnect-ethereum-wallet')
.should('have.text', 'Disconnect')
.click();
@@ -76,9 +76,6 @@ describe(
cy.getByTestId(closeDialog).click();
cy.getByTestId('Trading').first().click();
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferDialog).should('not.exist');
cy.getByTestId('Portfolio').eq(0).click();
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferDialog).click();
cy.getByTestId(dialogTransferText).should(
'contain.text',
@@ -7,13 +7,13 @@ import type { onMessage } from '@vegaprotocol/cypress';
import type { PartialDeep } from 'type-fest';
import { orderUpdateSubscription } from '@vegaprotocol/mock';
const sendOrderUpdate: ((data: OrdersUpdateSubscription) => void)[] = [];
let sendOrderUpdate: (data: OrdersUpdateSubscription) => void;
const getOnOrderUpdate = () => {
const onOrderUpdate: onMessage<
OrdersUpdateSubscription,
OrdersUpdateSubscriptionVariables
> = (send) => {
sendOrderUpdate.push(send);
sendOrderUpdate = send;
};
return onOrderUpdate;
};
@@ -31,5 +31,5 @@ export function updateOrder(
if (!sendOrderUpdate) {
throw new Error('OrderSub not called');
}
sendOrderUpdate.forEach((send) => send(update));
sendOrderUpdate(update);
}
+18 -1
View File
@@ -28,6 +28,7 @@ import {
} from '@vegaprotocol/mock';
import type { PartialDeep } from 'type-fest';
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/market-list';
import type { MarketInfoQuery } from '@vegaprotocol/market-info';
type MarketPageMockData = {
state: Schema.MarketState;
@@ -68,6 +69,18 @@ const marketsDataOverride = (
},
});
const marketInfoOverride = (
data: MarketPageMockData
): PartialDeep<MarketInfoQuery> => ({
market: {
state: data.state,
tradingMode: data.tradingMode,
data: {
trigger: data.trigger,
},
},
});
const mockTradingPage = (
req: CyHttpMessages.IncomingHttpRequest,
state: Schema.MarketState = Schema.MarketState.STATE_ACTIVE,
@@ -96,7 +109,11 @@ const mockTradingPage = (
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
aliasGQLQuery(req, 'Asset', assetQuery());
aliasGQLQuery(req, 'MarketInfo', marketInfoQuery());
aliasGQLQuery(
req,
'MarketInfo',
marketInfoQuery(marketInfoOverride({ state, tradingMode, trigger }))
);
aliasGQLQuery(req, 'Trades', tradesQuery());
aliasGQLQuery(req, 'Chart', chartQuery());
aliasGQLQuery(req, 'Candles', candlesQuery());
+4 -3
View File
@@ -1,11 +1,12 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz
NX_VEGA_NETWORKS={\"STAGNET3\":\"https://stagnet3.console.vega.xyz\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_VEGA_TOKEN_URL=https://validator-testnet.governance.fairground.wtf
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
-1
View File
@@ -12,7 +12,6 @@ export const Home = () => {
// should be the oldest market that is currently trading in us mode(i.e. not in auction).
const { data, error, loading } = useDataProvider({
dataProvider: marketsWithDataProvider,
variables: undefined,
});
const update = useGlobalStore((store) => store.update);
const marketId = useGlobalStore((store) => store.marketId);
@@ -47,8 +47,7 @@ export const Liquidity = () => {
const useReloadLiquidityData = (marketId: string | undefined) => {
const { reload } = useDataProvider({
dataProvider: liquidityProvisionsDataProvider,
variables: { marketId: marketId || '' },
skip: !marketId,
variables: useMemo(() => ({ marketId }), [marketId]),
});
useEffect(() => {
const interval = setInterval(reload, 10000);
@@ -78,8 +77,7 @@ export const LiquidityContainer = ({
const { data, loading, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
variables: { marketId: marketId || '' },
skip: !marketId,
variables: useMemo(() => ({ marketId }), [marketId]),
});
const assetDecimalPlaces =
@@ -163,8 +161,7 @@ export const LiquidityViewContainer = ({
} = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
variables: { marketId: marketId || '' },
skip: !marketId,
variables: useMemo(() => ({ marketId }), [marketId]),
});
const targetStake = marketData?.targetStake;
+9 -2
View File
@@ -7,6 +7,10 @@ import {
useThrottledDataProvider,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketProvider, marketDataProvider } from '@vegaprotocol/market-list';
import { useGlobalStore, usePageTitleStore } from '../../stores';
@@ -31,10 +35,13 @@ const TitleUpdater = ({
}) => {
const pageTitle = usePageTitleStore((store) => store.pageTitle);
const updateTitle = usePageTitleStore((store) => store.updateTitle);
const { data: marketData } = useThrottledDataProvider(
const { data: marketData } = useThrottledDataProvider<
MarketData,
MarketDataUpdateFieldsFragment
>(
{
dataProvider: marketDataProvider,
variables: { marketId: marketId || '' },
variables: useMemo(() => ({ marketId }), [marketId]),
skip: !marketId,
},
1000
@@ -179,10 +179,7 @@ const MainGrid = ({
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral
pinnedAsset={pinnedAsset}
hideButtons
/>
<TradingViews.Collateral pinnedAsset={pinnedAsset} />
</VegaWalletContainer>
</Tab>
</Tabs>
@@ -111,11 +111,11 @@ export const TradeMarketHeader = ({
</div>
</HeaderStat>
) : null}
<MarketProposalNotification marketId={market?.id} />
<MarketLiquiditySupplied
marketId={market?.id}
assetDecimals={asset?.decimals || 0}
/>
<MarketProposalNotification marketId={market?.id} />
</Header>
);
};
@@ -2,16 +2,10 @@ import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import {
useDataProvider,
useBottomPlaceholder,
} from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useRef } from 'react';
import type { AgGridReact } from 'ag-grid-react';
export const DepositsContainer = () => {
const gridRef = useRef<AgGridReact | null>(null);
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error, reload } = useDataProvider({
dataProvider: depositsProvider,
@@ -19,15 +13,13 @@ export const DepositsContainer = () => {
skip: !pubKey,
});
const openDepositDialog = useDepositDialog((state) => state.open);
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
return (
<div className="h-full">
<div className="h-full grid grid-rows-[1fr,min-content]">
<div className="h-full relative">
<DepositsTable
rowData={data || []}
noRowsOverlayComponent={() => null}
ref={gridRef}
{...bottomPlaceholderProps}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -41,9 +33,8 @@ export const DepositsContainer = () => {
</div>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<Button
variant="primary"
size="sm"
onClick={() => openDepositDialog()}
data-testid="deposit-button"
@@ -49,10 +49,7 @@ export const Portfolio = () => {
</Tab>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<PositionsContainer
onMarketClick={onMarketClick}
noBottomPlaceholder
/>
<PositionsContainer onMarketClick={onMarketClick} />
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
@@ -20,35 +20,36 @@ export const WithdrawalsContainer = () => {
return (
<VegaWalletContainer>
<div className="h-full relative">
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
noRowsOverlayComponent={() => null}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No withdrawals')}
reload={reload}
<div className="h-full relative grid grid-rows-[1fr,min-content]">
<div className="h-full relative">
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
noRowsOverlayComponent={() => null}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No withdrawals')}
reload={reload}
/>
</div>
</div>
{!isReadOnly && (
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<Button
size="sm"
onClick={() => openWithdrawDialog()}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</Button>
</div>
)}
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
onClick={() => openWithdrawDialog()}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</Button>
</div>
)}
</VegaWalletContainer>
);
};
@@ -11,10 +11,8 @@ import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = ({
pinnedAsset,
hideButtons,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
@@ -38,30 +36,27 @@ export const AccountsContainer = ({
}
return (
<div className="h-full relative">
<AccountManager
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={openWithdrawalDialog}
onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
/>
{!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
<div className="h-full relative grid grid-rows-[1fr,min-content]">
<div>
<AccountManager
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={openWithdrawalDialog}
onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
/>
</div>
{!isReadOnly && (
<div className="flex gap-2 justify-end p-2 px-[11px]">
<Button
variant="primary"
size="sm"
data-testid="open-transfer-dialog"
onClick={() => openTransferDialog()}
>
{t('Transfer')}
</Button>
<Button
variant="primary"
size="sm"
onClick={() => openDepositDialog()}
>
<Button size="sm" onClick={() => openDepositDialog()}>
{t('Deposit')}
</Button>
</div>
@@ -24,7 +24,6 @@ export const Banner = () => {
<AnnouncementBanner>
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
<button
className="flex items-center"
onClick={() => update({ shouldDisplayAnnouncementBanner: false })}
>
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
+1 -1
View File
@@ -14,7 +14,7 @@ export const Footer = () => {
const { blockDiff, datanodeBlockHeight } = useNodeHealth();
return (
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300 lg:fixed bottom-0 left-0 border-r bg-white dark:bg-black">
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300">
{/* Pull left to align with top nav, due to button padding */}
<div className="-ml-2">
{VEGA_URL && (
@@ -1,4 +1,5 @@
import type { RefObject } from 'react';
import { useMemo } from 'react';
import { useInView } from 'react-intersection-observer';
import { isNumeric } from '@vegaprotocol/utils';
import {
@@ -8,6 +9,7 @@ import {
import { PriceChangeCell } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import type { CandleClose } from '@vegaprotocol/types';
import type { Candle } from '@vegaprotocol/market-list';
import { marketCandlesProvider } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
@@ -28,14 +30,19 @@ export const Last24hPriceChange = ({
}: Props) => {
const [ref, inView] = useInView({ root: inViewRoot?.current });
const yesterday = useYesterday();
const { data, error } = useThrottledDataProvider(
const variables = useMemo(
() => ({
marketId: marketId,
interval: Schema.Interval.INTERVAL_I1H,
since: new Date(yesterday).toISOString(),
}),
[marketId, yesterday]
);
const { data, error } = useThrottledDataProvider<Candle[], Candle>(
{
dataProvider: marketCandlesProvider,
variables: {
marketId: marketId || '',
interval: Schema.Interval.INTERVAL_I1H,
since: new Date(yesterday).toISOString(),
},
variables,
skip: !marketId || !inView,
},
THROTTLE_UPDATE_TIME
@@ -10,6 +10,8 @@ import {
useYesterday,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import { useMemo } from 'react';
import type { Candle } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
interface Props {
@@ -30,14 +32,19 @@ export const Last24hVolume = ({
const yesterday = useYesterday();
const [ref, inView] = useInView({ root: inViewRoot?.current });
const { data } = useThrottledDataProvider(
const variables = useMemo(
() => ({
marketId: marketId,
interval: Schema.Interval.INTERVAL_I1H,
since: new Date(yesterday).toISOString(),
}),
[marketId, yesterday]
);
const { data } = useThrottledDataProvider<Candle[], Candle>(
{
dataProvider: marketCandlesProvider,
variables: {
marketId: marketId || '',
interval: Schema.Interval.INTERVAL_I1H,
since: new Date(yesterday).toISOString(),
},
variables,
skip: !(inView && marketId),
},
THROTTLE_UPDATE_TIME
@@ -4,7 +4,10 @@ import {
useDataProvider,
useNetworkParams,
} from '@vegaprotocol/react-helpers';
import type { MarketData } from '@vegaprotocol/market-list';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import {
@@ -67,7 +70,7 @@ export const MarketLiquiditySupplied = ({
[noUpdate]
);
useDataProvider({
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update,
variables,
@@ -1,8 +1,13 @@
import type { RefObject } from 'react';
import { useMemo } from 'react';
import { useInView } from 'react-intersection-observer';
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
import { useThrottledDataProvider } from '@vegaprotocol/react-helpers';
import { PriceCell } from '@vegaprotocol/datagrid';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
@@ -22,10 +27,15 @@ export const MarketMarkPrice = ({
asPriceCell,
}: Props) => {
const [ref, inView] = useInView({ root: inViewRoot?.current });
const { data } = useThrottledDataProvider(
const variables = useMemo(() => ({ marketId }), [marketId]);
const { data } = useThrottledDataProvider<
MarketData,
MarketDataUpdateFieldsFragment
>(
{
dataProvider: marketDataProvider,
variables: { marketId: marketId || '' },
variables,
skip: !inView,
},
THROTTLE_UPDATE_TIME
@@ -1,11 +1,15 @@
import throttle from 'lodash/throttle';
import type { MarketData, Market } from '@vegaprotocol/market-list';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
Market,
} from '@vegaprotocol/market-list';
import { marketDataProvider } from '@vegaprotocol/market-list';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import { HeaderStat } from '../header';
import { useCallback, useRef, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import * as constants from '../constants';
export const MarketState = ({ market }: { market: Market | null }) => {
@@ -29,10 +33,14 @@ export const MarketState = ({ market }: { market: Market | null }) => {
[throttledSetMarketState]
);
useDataProvider({
const variables = useMemo(
() => ({ marketId: market?.id || '' }),
[market?.id]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update,
variables: { marketId: market?.id || '' },
variables,
skip: !market?.id,
});
@@ -1,16 +1,24 @@
import { useCallback, useRef, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import throttle from 'lodash/throttle';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import type { MarketData } from '@vegaprotocol/market-list';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import * as constants from '../constants';
export const MarketVolume = ({ marketId }: { marketId: string }) => {
const [marketVolume, setMarketVolume] = useState<string>('-');
const variables = { marketId };
const variables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const { data } = useDataProvider({
dataProvider: marketProvider,
variables,
@@ -38,7 +46,7 @@ export const MarketVolume = ({ marketId }: { marketId: string }) => {
[data?.positionDecimalPlaces, throttledSetMarketVolume]
);
useDataProvider({
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update,
variables,
@@ -106,13 +106,14 @@ export const SelectMarketPopover = ({
loading: marketsLoading,
reload: marketListReload,
} = useMarketList();
const variables = useMemo(() => ({ partyId: pubKey }), [pubKey]);
const {
data: positions,
loading: positionsLoading,
reload,
} = useDataProvider({
dataProvider: positionsDataProvider,
variables: { partyId: pubKey || '' },
variables,
skip: !pubKey,
});
const onSelectMarket = useCallback(
@@ -20,7 +20,6 @@ export const WelcomeDialog = () => {
const [riskAccepted] = useLocalStorage(constants.RISK_ACCEPTED_KEY);
const { data } = useDataProvider({
dataProvider: activeMarketsProvider,
variables: undefined,
});
const { update, shouldDisplayWelcomeDialog } = useGlobalStore((store) => ({
@@ -177,14 +177,22 @@ export const useEthereumTransactionToasts = () => {
store.remove,
]);
const dismissTx = useEthTransactionStore((state) => state.dismiss);
const [dismissTx, deleteTx] = useEthTransactionStore((state) => [
state.dismiss,
state.delete,
]);
const onClose = useCallback(
(tx: EthStoredTxState) => () => {
dismissTx(tx.id);
const safeToDelete = isFinal(tx);
if (safeToDelete) {
deleteTx(tx.id);
} else {
dismissTx(tx.id);
}
removeToast(`eth-${tx.id}`);
},
[dismissTx, removeToast]
[deleteTx, dismissTx, removeToast]
);
const fromEthTransaction = useCallback(
+1 -1
View File
@@ -72,7 +72,7 @@ function AppBody({ Component }: AppProps) {
const gridClasses = classNames(
'h-full relative z-0 grid',
'grid-rows-[repeat(3,min-content),1fr]'
'grid-rows-[repeat(3,min-content),1fr,min-content]'
);
return (
+58
View File
@@ -0,0 +1,58 @@
.pre-loader {
display: flex;
width: 100%;
min-height: 100vh;
justify-content: center;
align-items: center;
.loader-item {
width: 10px;
height: 10px;
background: black;
}
.pre-loader-center {
align-items: center;
display: flex;
flex-direction: column;
}
.pre-loader-wrapper {
width: 50px;
height: 50px;
display: flex;
flex-wrap: wrap;
}
@for $i from 0 through 16 {
.loader-item:nth-child(#{$i}) {
@if $i % 2 == 0 {
animation-delay: #{$i * 50 * random(5)}ms;
animation-direction: reverse;
} @else {
animation-delay: #{$i * -50 * random(5)}ms;
animation-direction: alternate;
}
}
}
.loader-item {
animation: flickering 0.4s linear alternate infinite;
}
@keyframes flickering {
0% {
opacity: 1;
}
25% {
opacity: 1;
}
26% {
opacity: 0;
}
100% {
opacity: 0;
}
}
}
html.dark {
.pre-loader {
.loader-item {
background: white;
}
}
}

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