Compare commits

...
Author SHA1 Message Date
Matthew Russell 1699e33cbd fix(trading): redirect to markets all page if no market found (#3888) 2023-05-22 22:02:55 -07:00
Matthew Russell 5280b79927 feat(trading): market selection changes (#3863) 2023-05-22 21:33:16 -07:00
Matthew Russell 34526d527e fix(trading): icon color when navbar is dark (#3887) 2023-05-22 19:07:18 -07:00
Matthew Russell 7b9aae700d chore(trading): dont show upgrade banner unless APP_VERSION is set (#3886) 2023-05-22 14:10:06 -07:00
Matthew Russell e9cb2ad217 chore(ui-toolkit): adjust nav styles and banner (#3865) 2023-05-22 13:20:17 -07:00
Edd 6dd4536fdd fix(explorer): change how internal filters are detected (#3852) 2023-05-22 21:08:57 +01:00
Bartłomiej Głownia 522eb521b9 feat(trading): show positions for all markets (#3868) 2023-05-22 12:55:15 -07:00
Edd 3aa0e974c2 feat(explorer): footer updates (#3884) 2023-05-22 12:54:37 -07:00
Art a0eb381ab1 feat(trading): upgrade banner (#3883) 2023-05-22 12:53:40 -07:00
Matthew Russell a51ab4d98d chore(governance): remove process tranches script (#3864) 2023-05-22 12:52:13 -07:00
Bartłomiej Głownia 67643afd74 fix(trading): positions and markets settlement asset column filter and sort (#3873) 2023-05-22 12:34:05 -07:00
dexturr 145539e0c0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-22 18:07:30 +00:00
Art 48109b584c fix(ui-toolkit): test toast configuration (#3877) 2023-05-22 18:34:02 +02:00
Maciek a4512e6064 feat(trading): move trade grid panel - add back priority for chart pane (#3880) 2023-05-22 15:55:02 +00:00
m.rayandEdd 45adac2932 chore(trading): update environments. add vega.trading to network switcher (#3869)
Co-authored-by: Edd <edd@vega.xyz>
2023-05-22 15:50:13 +00:00
91 changed files with 2289 additions and 153314 deletions
-32
View File
@@ -1,32 +0,0 @@
name: Generate tranches
on:
schedule:
- cron: '0 */6 * * *'
jobs:
master:
name: Generate Queries
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
with:
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
- name: Install root dependencies
run: yarn install
- name: Generate queries
run: node ./scripts/get-tranches.js
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: 'chore: update tranches'
commit_options: '--no-verify --signoff'
skip_fetch: true
skip_checkout: true
+3 -3
View File
@@ -3,15 +3,15 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_TOKEN_URL=https://stagnet1.governance.vega.xyz
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks
NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://stagnet1.governance.vega.xyz
NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
+1
View File
@@ -10,3 +10,4 @@ NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/
NX_VEGA_CONSOLE_URL=https://vega.trading
+1
View File
@@ -10,3 +10,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
+2 -1
View File
@@ -7,8 +7,9 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
NX_VEGA_GOVERNANCE_URL=https://governance.validators-testnet.vega.rocks
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks/
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks/
@@ -4,9 +4,11 @@ import {
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
import { Link, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react';
import { ENV } from '../../config/env';
import { Routes } from '../../routes/route-names';
import { Link as RouteLink } from 'react-router-dom';
export const Footer = () => {
const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL } = useEnvironment();
@@ -21,7 +23,7 @@ export const Footer = () => {
);
return (
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<footer className="grid grid-rows-2 lg:grid-cols-[1fr_auto] text-xs md:text-md lg:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<div className="flex justify-between gap-2 align-middle">
{GIT_COMMIT_HASH && (
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
@@ -56,11 +58,16 @@ export const Footer = () => {
</div>
) : null}
</div>
<div className="pl-2 align-center lg:align-right lg:flex lg:justify-end gap-2 align-middle lg:max-w-xs lg:ml-auto">
<RouteLink to={`/${Routes.DISCLAIMER}`} className="underline">
Disclaimer
</RouteLink>
</div>
</footer>
);
};
const NodeUrl = ({ url }: { url: string }) => {
export const NodeUrl = ({ url }: { url: string }) => {
// get base url from api url, api sub domain
const urlObj = new URL(url);
const nodeUrl = urlObj.origin.replace(/^[^.]+\./g, '');
@@ -84,15 +84,17 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
<AgGridColumn
colId="asset"
headerName={t('Settlement asset')}
field="tradableInstrument.instrument.product.settlementAsset"
field="tradableInstrument.instrument.product.settlementAsset.symbol"
hide={window.innerWidth <= BREAKPOINT_MD}
cellRenderer={({
value,
data,
}: VegaICellRendererParams<
MarketFieldsFragment,
'tradableInstrument.instrument.product.settlementAsset'
>) =>
value ? (
'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => {
const value =
data?.tradableInstrument.instrument.product.settlementAsset;
return value ? (
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value.id, e.target as HTMLElement);
@@ -102,8 +104,8 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
</ButtonLink>
) : (
''
)
}
);
}}
/>
<AgGridColumn
flex={2}
@@ -1,12 +1,13 @@
import { render } from '@testing-library/react';
import { OracleDetailsType } from './oracle-details-type';
import type { SourceTypeName } from './oracle-details-type';
import { OracleDetailsType, isInternalSourceType } from './oracle-details-type';
import type { SourceType } from './oracle';
import { PropertyKeyType } from '@vegaprotocol/types';
function renderComponent(type: SourceTypeName) {
return <OracleDetailsType type={type} />;
function renderComponent(type: SourceType) {
return <OracleDetailsType sourceType={type} />;
}
function renderWrappedComponent(type: SourceTypeName) {
function renderWrappedComponent(type: SourceType) {
return (
<table>
<tbody>{renderComponent(type)}</tbody>
@@ -14,19 +15,53 @@ function renderWrappedComponent(type: SourceTypeName) {
);
}
function mock(name: string): SourceType {
return {
sourceType: {
filters: [
{
__typename: 'Filter',
key: {
name,
type: PropertyKeyType.TYPE_STRING,
},
},
],
},
};
}
describe('Oracle type view', () => {
it('Renders nothing when type is null', () => {
const res = render(renderComponent(null as unknown as SourceTypeName));
const res = render(renderComponent(null as unknown as SourceType));
expect(res.container).toBeEmptyDOMElement();
});
it('Renders Internal time for internal sources', () => {
const res = render(renderWrappedComponent('DataSourceDefinitionInternal'));
expect(res.getByText('Internal time')).toBeInTheDocument();
it('Renders Internal time for internal sources - timestamp', () => {
const s = mock('vegaprotocol.builtin.timestamp');
expect(isInternalSourceType(s)).toEqual(true);
const res = render(renderWrappedComponent(s));
expect(res.getByText('Internal data')).toBeInTheDocument();
});
it('Renders Internal time for internal sources - potential future types', () => {
const s = mock('vegaprotocol.builtin.boolean');
expect(isInternalSourceType(s)).toEqual(true);
const res = render(renderWrappedComponent(s));
expect(res.getByText('Internal data')).toBeInTheDocument();
});
it('Renders External data otherwise - prices.external.whatever', () => {
const s = mock('prices.external.whatever');
expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s));
expect(res.getByText('External data')).toBeInTheDocument();
});
it('Renders External data otherwise', () => {
const res = render(renderWrappedComponent('DataSourceDefinitionExternal'));
const s = mock('prices.external.vegaprotocol.builtin.');
expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s));
expect(res.getByText('External data')).toBeInTheDocument();
});
});
@@ -1,27 +1,51 @@
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { SourceType } from './oracle';
export type SourceTypeName = SourceType['__typename'] | undefined;
/**
* Basic function to determine if a source is internal or external.
*
* This should be distinguishable using __typename, but the type is incorrectly
* reported at the moment, so instead we check the filters.
*
* @param s SourceType
* @returns boolean True if the source is internal
*/
export function isInternalSourceType(s: SourceType) {
if ('filters' in s.sourceType) {
const filters = s.sourceType.filters;
if (filters) {
return (
filters?.filter((f) => {
return f.key.name?.indexOf('vegaprotocol.builtin.') === 0;
}).length > 0
);
}
}
return false;
}
interface OracleDetailsTypeProps {
type: SourceTypeName;
sourceType: SourceType;
}
/**
* Renders a a single table row for the Oracle Details view that shows
* if the oracle is using the internal time oracle or external data
*/
export function OracleDetailsType({ type }: OracleDetailsTypeProps) {
if (!type) {
export function OracleDetailsType({ sourceType }: OracleDetailsTypeProps) {
if (!sourceType) {
return null;
}
const isInternal = isInternalSourceType(sourceType);
return (
<TableRow modifier="bordered">
<TableHeader scope="row">Type</TableHeader>
<TableCell modifier="bordered">
{type === 'DataSourceDefinitionInternal'
? 'Internal time'
: 'External data'}
{isInternal ? 'Internal data' : 'External data'}
</TableCell>
</TableRow>
);
@@ -53,7 +53,7 @@ export const OracleDetails = ({
<OracleLink id={id} />
</TableCell>
</TableRow>
<OracleDetailsType type={sourceType.__typename} />
<OracleDetailsType sourceType={sourceType} />
<OracleSigners sourceType={sourceType} />
<OracleMarkets id={id} />
<TableRow modifier="bordered">
@@ -0,0 +1,40 @@
import { t } from '@vegaprotocol/i18n';
import { RouteTitle } from '../../components/route-title';
export const Disclaimer = () => {
return (
<section>
<div className="px-40 max-sm:px-0 max-md:px-10 mb-4 max-w-5xl">
<RouteTitle data-testid="disclaimer-header">
{t('Disclaimer')}
</RouteTitle>
<p className="mt-3">
The Vega Block Explorer is an application that allows users to, among
other things, browse through blocks, view wallet addresses, network
hashrate, transaction data and other key information on the Vega
blockchain. It is free, public and open source software. Software
upgrades may contain bugs or security vulnerabilities that might
result in loss of functionality.
</p>
<p className="mt-3">
The Vega Block Explorer uses data from nodes on the Vega Blockchain.
The developers of the Vega Block Explorer do not operate or run the
Vega Blockchain or any other blockchain.
</p>
<p className="mt-3 font-semibold">
The Vega Block Explorer is provided as is. The developers of the
Vega Block Explorer make no representations or warranties of any kind,
whether express or implied, statutory or otherwise regarding the Vega
Block Explorer. They disclaim all warranties of merchantability,
quality, fitness for purpose. They disclaim all warranties that the
Vega Block Explorer is free of harmful components or errors.
</p>
<p className="mt-3 font-bold">
No developer of the Vega Block Explorer accepts any responsibility
for, or liability to users in connection with their use of the Vega
Block Explorer.
</p>
</div>
</section>
);
};
@@ -10,4 +10,5 @@ export const Routes = {
MARKETS: 'markets',
ORACLES: 'oracles',
NETWORK_PARAMETERS: 'network-parameters',
DISCLAIMER: 'disclaimer',
};
@@ -29,6 +29,7 @@ import { AssetLink, MarketLink } from '../components/links';
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
import { Disclaimer } from './pages/disclaimer';
export type Navigable = {
path: string;
@@ -335,6 +336,17 @@ export const routerConfig: Route[] = [
},
],
},
{
path: Routes.DISCLAIMER,
element: <Disclaimer />,
handle: {
name: t('Disclaimer'),
text: t('Disclaimer'),
breadcrumb: () => (
<Link to={Routes.DISCLAIMER}>{t('Disclaimer')}</Link>
),
},
},
...partiesRoutes,
...assetsRoutes,
...genesisRoutes,
+3 -2
View File
@@ -5,10 +5,11 @@ NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_FAIRGROUND=false
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https://stagnet1.governance.vega.xyz","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz"}'
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https://governance.stagnet1.vega.rocks","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz"}'
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
+2 -2
View File
@@ -1,9 +1,9 @@
# App configuration variables
NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
NX_VEGA_ENV=STAGNET1
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https://stagnet1.governance.vega.xyz","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz"}'
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https://governance.stagnet1.vega.rocks","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz"}'
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
+1 -1
View File
@@ -13,4 +13,4 @@ NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
+1 -1
View File
@@ -10,4 +10,4 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
@@ -5,6 +5,9 @@ import { ProtocolUpgradeProposalDetailInfo } from '../components/protocol-upgrad
import { getNormalisedVotingPower } from '../../staking/shared';
import type { NodesFragmentFragment } from '../../staking/home/__generated__/Nodes';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useVegaRelease } from '@vegaprotocol/environment';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
export interface ProtocolUpgradeProposalProps {
proposal: ProtocolUpgradeProposalFieldsFragment;
@@ -42,6 +45,9 @@ export const ProtocolUpgradeProposal = ({
lastBlockHeight,
consensusValidators,
}: ProtocolUpgradeProposalProps) => {
const { t } = useTranslation();
const releaseInfo = useVegaRelease(proposal.vegaReleaseTag);
const consensusApprovals = useMemo(
() => getConsensusApprovals(consensusValidators || [], proposal),
[consensusValidators, proposal]
@@ -78,6 +84,14 @@ export const ProtocolUpgradeProposal = ({
totalConsensusValidators={consensusValidators.length}
/>
)}
{releaseInfo && releaseInfo.htmlUrl && (
<div className="mb-10">
<ExternalLink href={releaseInfo.htmlUrl}>
{t('Explore release on GitHub')}
</ExternalLink>
</div>
)}
</section>
);
};
+2 -1
View File
@@ -23,5 +23,6 @@ NX_VEGA_URL="https://api.n07.testnet.vega.xyz/graphql"
NX_VEGA_WALLET_URL=http://localhost:1789
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
@@ -2,7 +2,7 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_VEGA_ENV=DEVNET
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
@@ -2,7 +2,8 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\"}
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
NX_VEGA_CONSOLE_URL=https://vega.trading
@@ -4,5 +4,6 @@ NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
NX_VEGA_ENV=STAGNET1
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
@@ -2,7 +2,8 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\"}
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.tom
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_NETWORKS={'{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=VALIDATOR_TESTNET
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -6,7 +6,8 @@ NX_VEGA_CONFIG_URL=''
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_ENV=CUSTOM
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_URL=http://localhost:3008/graphql
NX_VEGA_WALLET_URL=http://localhost:1789
@@ -19,6 +20,7 @@ CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_CONSOLE_URL=https://console.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_ORACLE_PUBKEY=6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
+3 -1
View File
@@ -5,7 +5,8 @@ NX_VEGA_CONFIG_URL=''
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_ENV=CUSTOM
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_URL=http://localhost:3008/graphql
NX_VEGA_WALLET_URL=http://localhost:1789
@@ -17,6 +18,7 @@ CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_CONSOLE_URL=https://console.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
@@ -1,20 +1,16 @@
import { closeWelcomeDialog } from '../support/helpers';
const dialogContent = 'dialog-content';
const nodeHealth = 'node-health';
describe('home', { tags: '@regression' }, () => {
describe.skip('home', { tags: '@regression' }, () => {
before(() => {
cy.clearAllLocalStorage();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
closeWelcomeDialog();
});
describe('footer', () => {
it.skip('shows current block height', () => {
closeWelcomeDialog();
it('shows current block height', () => {
// 0006-NETW-004
// 0006-NETW-005
// 0006-NETW-008
+3 -97
View File
@@ -1,9 +1,7 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { marketsDataQuery } from '@vegaprotocol/mock';
import * as Schema from '@vegaprotocol/types';
const selectMarketOverlay = 'select-market-list';
const dialogContent = 'dialog-content';
const generateProposal = (code: string): ProposalListFieldsFragment => ({
@@ -110,77 +108,12 @@ describe('home', { tags: '@regression' }, () => {
cy.get('main[data-testid^="/markets/"]');
// Overlay should be shown
cy.getByTestId(selectMarketOverlay).should('exist');
cy.contains('Select a market to get started').should('be.visible');
// I expect the market overlay table to contain at least 3 rows (one header row)
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.then((row) => {
expect(row.length >= 3).to.be.true;
});
// each market shown in overlay table contains content under the last price and change fields
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.each(($element, index) => {
if (index > 0) {
// skip header row
cy.root().within(() => {
cy.getByTestId('price').should('not.be.empty');
});
}
});
cy.getByTestId('welcome-notice-proposed-markets')
.children('div.pt-1.flex.justify-between')
.should('have.length', 3)
.each((item) => {
cy.wrap(item).getByTestId('external-link').should('exist');
});
cy.getByTestId('dialog-close').click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
// the choose market overlay is no longer showing
cy.contains('Select a market to get started').should('not.exist');
cy.contains('Loading...').should('not.exist');
cy.url().should('eq', Cypress.config().baseUrl + '/#/markets/market-0');
});
});
describe('market table should be properly rendered', () => {
it('redirects to a default market with the landing dialog open', () => {
const override = {
marketsConnection: {
edges: [
{
node: {
data: {
markPrice: '46126900581221212121212121212121212121212121212',
},
},
},
],
},
};
// @ts-ignore partial deep check failing
const data = marketsDataQuery(override);
cy.mockGQL((req) => {
aliasGQLQuery(req, 'MarketsData', data);
});
cy.visit('/');
cy.wait('@Markets');
cy.getByTestId(selectMarketOverlay)
.get('table')
.invoke('outerWidth')
.then((value) => {
expect(value).to.be.closeTo(554, 10);
});
});
});
describe('no markets found', () => {
beforeEach(() => {
cy.mockGQL((req) => {
@@ -206,40 +139,13 @@ describe('home', { tags: '@regression' }, () => {
cy.wait('@Markets');
cy.wait('@MarketsData');
});
it('redirects to a the empty market page and displays welcome notice', () => {
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets`);
it('redirects to market/all and displays welcome notice', () => {
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
cy.getByTestId('welcome-notice-title').should(
'contain.text',
'Welcome to Console'
);
cy.getByTestId('welcome-notice-proposed-markets').should(
'contain.text',
'AAAZZZ'
);
});
});
describe('no proposal found', () => {
it('there is a link to propose market', () => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ProposalsList', {
proposalsConnection: {
__typename: 'ProposalsConnection',
edges: null,
},
});
});
cy.visit('/');
cy.wait('@Markets');
cy.wait('@MarketsData');
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.then((row) => {
expect(row.length >= 3).to.be.true;
});
cy.getByTestId('external-link')
.contains('Propose a market')
.should('exist');
});
});
@@ -1,4 +1,3 @@
const selectMarketOverlay = 'select-market-list';
const marketInfoBtn = 'Info';
const marketInfoSubtitle = 'accordion-title';
const marketSummaryBlock = 'header-summary';
@@ -14,45 +13,6 @@ const itemHeader = 'item-header';
const itemValue = 'item-value';
const marketListContent = 'popover-content';
describe(
'Console - market list - live env',
{ tags: '@live', testIsolation: true },
() => {
beforeEach(() => {
cy.visit('/');
});
it('shows the market list page', () => {
cy.get('main', { timeout: 20000 });
// Overlay should be shown
cy.getByTestId(selectMarketOverlay).should('exist');
cy.contains('Select a market to get started').should('be.visible');
// I expect the market overlay table to contain at least one row
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.should('have.length.greaterThan', 1);
// each market shown in overlay table contains content under the last price and change fields
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.getByTestId('price')
.should('not.be.empty');
});
it('redirects to a default market', () => {
cy.getByTestId('dialog-close').click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
// the choose market overlay is no longer showing
cy.contains('Select a market to get started').should('not.exist');
cy.contains('Loading...').should('not.exist');
cy.getByTestId('popover-trigger').should('not.be.empty');
});
}
);
describe(
'Console - market info - live env',
{ tags: '@live', testIsolation: true },
@@ -38,28 +38,28 @@ describe('markets selector', { tags: '@smoke' }, () => {
// TODO: load data from mocks in. Using alias and wrap intermittently fails
const data = [
{
code: 'AAPL.MF21',
name: 'Apple Monthly (30 Jun 2022)',
markPrice: '46,126.90058',
change: '+200.00%',
},
{
code: 'BTCUSD.MF21',
name: 'ACTIVE MARKET',
markPrice: '46,126.90058',
code: 'SOLUSD',
markPrice: '84.41XYZalpha',
change: '+200.00%',
vol: '324h vol',
},
{
code: 'ETHBTC.QM21',
name: 'ETHBTC Quarterly (30 Jun 2022)',
markPrice: '46,126.90058',
markPrice: '46,126.90058tBTC',
change: '+200.00%',
vol: '324h vol',
},
{
code: 'SOLUSD',
name: 'SUSPENDED MARKET',
markPrice: '84.41',
code: 'BTCUSD.MF21',
markPrice: '46,126.90058tDAI',
change: '+200.00%',
vol: '324h vol',
},
{
code: 'AAPL.MF21',
markPrice: '46,126.90058tUSDC',
change: '+200.00%',
vol: '324h vol',
},
];
cy.getByTestId(list)
@@ -68,12 +68,13 @@ describe('markets selector', { tags: '@smoke' }, () => {
const market = data[i];
// 6001-MARK-021
expect(item.find('h3').text()).equals(market.code);
// 6001-MARK-022
expect(item.find('h4').text()).equals(market.name);
expect(
item.find('[data-testid="market-selector-data-row"]').eq(0).text()
).contains(market.vol);
// 6001-MARK-024
expect(item.find('[data-testid="market-item-price"]').text()).equals(
market.markPrice
);
expect(
item.find('[data-testid="market-selector-data-row"]').eq(1).text()
).contains(market.markPrice);
// 6001-MARK-023
expect(item.find('[data-testid="market-item-change"]').text()).equals(
market.change
@@ -96,8 +97,8 @@ describe('markets selector', { tags: '@smoke' }, () => {
// 6001-MARK-29
cy.getByTestId(searchInput).clear().type('btc');
cy.getByTestId(list).find('a').should('have.length', 2);
cy.getByTestId(list).find('a').eq(0).contains('BTCUSD.MF21');
cy.getByTestId(list).find('a').eq(1).contains('ETHBTC.QM21');
cy.getByTestId(list).find('a').eq(1).contains('BTCUSD.MF21');
cy.getByTestId(list).find('a').eq(0).contains('ETHBTC.QM21');
cy.getByTestId(searchInput).clear();
cy.getByTestId(list).find('a').should('have.length', 4);
+5 -17
View File
@@ -3,8 +3,6 @@ import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
import { marketsQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/utils';
const dialogCloseBtn = 'dialog-close';
describe('markets table', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.clearLocalStorage().then(() => {
@@ -14,20 +12,18 @@ describe('markets table', { tags: '@smoke' }, () => {
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.visit('/');
cy.wait('@Markets');
cy.wait('@MarketsData');
cy.wait('@MarketsCandles');
cy.visit('/#/markets/all');
});
});
it('renders markets correctly', () => {
cy.wait('@Markets');
cy.wait('@MarketsData');
cy.get('[data-testid^="market-link-"]').should('not.be.empty');
cy.getByTestId('price').invoke('text').should('not.be.empty');
cy.getByTestId('settlement-asset').should('not.be.empty');
cy.getByTestId('price-change-percentage').should('not.be.empty');
cy.getByTestId('price-change').should('not.be.empty');
cy.getByTestId('sparkline-svg').should('be.visible');
});
it('able to open and sort full market list - market page', () => {
@@ -37,9 +33,6 @@ describe('markets table', { tags: '@smoke' }, () => {
'ETHBTC.QM21',
'SOLUSD',
];
cy.getByTestId('view-market-list-link')
.should('have.attr', 'href', '#/markets/all')
.click();
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
cy.contains('AAPL.MF21').should('be.visible');
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
@@ -51,10 +44,6 @@ describe('markets table', { tags: '@smoke' }, () => {
});
it('proposed markets tab should be rendered properly', () => {
cy.getByTestId('view-market-list-link')
.should('have.attr', 'href', '#/markets/all')
.click();
cy.get('[data-testid="All markets"]').should(
'have.attr',
'data-state',
@@ -87,8 +76,8 @@ describe('markets table', { tags: '@smoke' }, () => {
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
);
});
it('proposed markets tab should be sorted properly', () => {
cy.getByTestId('view-market-list-link').click();
cy.get('[data-testid="Proposed markets"]').click();
const marketColDefault = [
'ETHUSD',
@@ -167,7 +156,7 @@ describe('markets table', { tags: '@smoke' }, () => {
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
});
it('opening auction subsets should be properly displayed', () => {
it.skip('opening auction subsets should be properly displayed', () => {
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
@@ -200,7 +189,6 @@ describe('markets table', { tags: '@smoke' }, () => {
});
cy.visit('#/markets/market-0');
cy.url().should('contain', 'market-0');
cy.getByTestId(dialogCloseBtn).click();
cy.getByTestId('item-value').contains('Opening auction').realHover();
cy.getByTestId('opening-auction-sub-status').should(
'contain.text',
@@ -8,9 +8,6 @@ describe('Navbar', { tags: '@smoke' }, () => {
cy.visit('/');
cy.wait('@Markets');
cy.wait('@MarketsData');
cy.wait('@MarketsCandles');
// close welcome dialog
cy.getByTestId('dialog-close').click();
});
const pages = [
@@ -1,12 +1,9 @@
import { closeWelcomeDialog } from '../support/helpers';
describe('Settings page', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.clearLocalStorage().then(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
closeWelcomeDialog();
cy.get('[aria-label="cog icon"]').click();
});
});
-5
View File
@@ -9,8 +9,3 @@ export const selectAsset = (assetIndex: number) => {
// eslint-disable-next-line
cy.wait(100);
};
export const closeWelcomeDialog = () => {
cy.getByTestId('select-market-list').should('exist');
cy.getByTestId('dialog-close').click();
};
+1 -1
View File
@@ -85,7 +85,7 @@ const mockTradingPage = (
trigger?: Schema.AuctionTrigger
) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'NodeCheck', statisticsQuery());
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
aliasGQLQuery(
req,
+3 -3
View File
@@ -5,11 +5,11 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_NETWORKS={\"MAINNET\":\"https://vega.trading\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.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
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
+2 -2
View File
@@ -4,8 +4,8 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=''
NX_VEGA_ENV=CUSTOM
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_URL=http://localhost:3008/graphql
NX_VEGA_WALLET_URL=http://localhost:1789
+1 -1
View File
@@ -6,7 +6,7 @@ NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.se
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
+4 -1
View File
@@ -5,10 +5,13 @@ NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.se
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_ENV=MAINNET
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_NETWORKS={\"MAINNET\":\"https://vega.trading\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://vega.trading
# TAG name of the current app version - TODO: bump to the latest upon release
NX_APP_VERSION=v0.20.8-core-0.71.4
+3 -3
View File
@@ -5,9 +5,9 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_TOKEN_URL=https://stagnet1.governance.vega.xyz
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks
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
+2 -1
View File
@@ -6,10 +6,11 @@ NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.se
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_ENV=TESTNET
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\"}
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.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
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
+5 -4
View File
@@ -5,11 +5,12 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz
NX_VEGA_NETWORKS={\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_VEGA_TOKEN_URL=https://validator-testnet.governance.vega.xyz
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks
NX_VEGA_NETWORKS={\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_VEGA_TOKEN_URL=https://governance.validators-testnet.vega.rocks
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
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
+2 -2
View File
@@ -9,7 +9,7 @@ import { useGlobalStore } from '../../stores';
export const Home = () => {
const navigate = useNavigate();
// The default market selected in the platform behind the overlay
// should be the oldest market that is currently trading in us mode(i.e. not in auction).
// should be the oldest market that is currently trading in continuous mode(i.e. not in auction).
const { data, error, loading } = useDataProvider({
dataProvider: marketsWithDataProvider,
variables: undefined,
@@ -30,7 +30,7 @@ export const Home = () => {
replace: true,
});
} else {
navigate(Links[Routes.MARKET]());
navigate(Links[Routes.MARKETS]());
}
}
}, [marketId, data, navigate, update]);
@@ -22,7 +22,19 @@ describe('MarketSelectorItem', () => {
id: 'market-0',
decimalPlaces: 2,
// @ts-ignore fragment doesn't contain candles
candles: [{ close: '5' }, { close: '10' }],
candles: [
{ close: '5', volume: '50' },
{ close: '10', volume: '50' },
],
tradableInstrument: {
instrument: {
product: {
settlementAsset: {
symbol: 'SYM',
},
},
},
},
});
const marketData: MarketDataUpdateFieldsFragment = {
__typename: 'ObservableMarketData',
@@ -83,6 +95,9 @@ describe('MarketSelectorItem', () => {
};
it('renders market information', async () => {
const symbol =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
renderJsx();
const link = screen.getByRole('link');
@@ -91,7 +106,8 @@ describe('MarketSelectorItem', () => {
expect(link).toHaveClass('ring-1');
expect(screen.getByTestId('market-item-price')).toHaveTextContent('-');
expect(screen.getByTitle('24h vol')).toHaveTextContent('100');
expect(screen.getByTitle(symbol)).toHaveTextContent('-');
// candles are loaded immediately
expect(screen.getByTestId('market-item-change')).toHaveTextContent(
@@ -99,7 +115,7 @@ describe('MarketSelectorItem', () => {
);
await waitFor(() => {
expect(screen.getByTestId('market-item-price')).toHaveTextContent(
expect(screen.getByTitle(symbol)).toHaveTextContent(
addDecimalsFormatNumber(marketData.markPrice, market.decimalPlaces)
);
});
@@ -7,8 +7,14 @@ import {
priceChangePercentage,
} from '@vegaprotocol/utils';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
import { calcCandleVolume } from '@vegaprotocol/markets';
import { useMarketDataUpdateSubscription } from '@vegaprotocol/markets';
import { Sparkline } from '@vegaprotocol/ui-toolkit';
import {
MarketTradingMode,
MarketTradingModeMapping,
} from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
export const MarketSelectorItem = ({
market,
@@ -38,13 +44,6 @@ export const MarketSelectorItem = ({
onSelect && onSelect(market.id);
}}
>
<h3>{market.tradableInstrument.instrument.code}</h3>
<h4
title={market.tradableInstrument.instrument.name}
className="text-sm text-vega-light-300 dark:text-vega-dark-300 text-ellipsis whitespace-nowrap overflow-hidden"
>
{market.tradableInstrument.instrument.name}
</h4>
<MarketData market={market} />
</Link>
</div>
@@ -68,30 +67,84 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
? addDecimalsFormatNumber(market.data.markPrice, market.decimalPlaces)
: '-';
const marketTradingMode = marketData
? marketData.marketTradingMode
: market.tradingMode;
const mode = [
MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
].includes(marketTradingMode)
? MarketTradingModeMapping[marketTradingMode]
: '';
const instrument = market.tradableInstrument.instrument;
const vol = market.candles ? calcCandleVolume(market.candles) : '0';
const volume =
vol && vol !== '0'
? addDecimalsFormatNumber(vol, market.positionDecimalPlaces)
: '0.00';
return (
<div className="flex flex-nowrap justify-between items-center mt-1">
<div className="w-1/2">
<div
className="text-ellipsis whitespace-nowrap overflow-hidden"
data-testid="market-item-price"
<>
<div className="flex items-end gap-1 mb-1">
<h3
className={classNames(
'overflow-hidden text-ellipsis whitespace-nowrap',
{
'w-1/2': mode, // make space for showing the trading mode
}
)}
>
{price}
</div>
{market.tradableInstrument.instrument.code}
</h3>
{mode && (
<p className="w-1/2 text-xs text-right text-vega-orange-500 dark:text-vega-orange-550">
{mode}
</p>
)}
</div>
<DataRow value={volume} label={t('24h vol')} />
<DataRow
value={price}
label={instrument.product.settlementAsset.symbol}
/>
<div className="relative">
{market.candles && (
<PriceChange candles={market.candles.map((c) => c.close)} />
)}
<div
// absolute so height is not larger than price change value
className="absolute right-0 bottom-0 w-[120px]"
>
{market.candles && (
<Sparkline
width={120}
height={20}
data={market.candles.filter(Boolean).map((c) => Number(c.close))}
/>
)}
</div>
</div>
<div className="w-1/2 max-w-[120px]">
{market.candles ? (
<Sparkline
width={120}
height={20}
data={market.candles.filter(Boolean).map((c) => Number(c.close))}
/>
) : (
'-'
)}
</div>
</>
);
};
const DataRow = ({ value, label }: { value: string; label: string }) => {
return (
<div
className="text-ellipsis whitespace-nowrap overflow-hidden leading-tight"
data-testid="market-selector-data-row"
>
<span title={label} className="text-sm mr-1">
{value}
</span>
<span className="text-xs text-vega-light-300 dark:text-vega-light-300">
{label}
</span>
</div>
);
};
@@ -2,8 +2,8 @@ import classNames from 'classnames';
// Make sure these match the available __typename properties on product
export const Product = {
Spot: 'Spot',
Future: 'Future',
Spot: 'Spot',
Perpetual: 'Perpetual',
} as const;
@@ -12,8 +12,8 @@ export type ProductType = keyof typeof Product;
const ProductTypeMapping: {
[key in ProductType]: string;
} = {
[Product.Spot]: 'Spot',
[Product.Future]: 'Futures',
[Product.Spot]: 'Spot',
[Product.Perpetual]: 'Perpetuals',
};
+18 -10
View File
@@ -277,6 +277,7 @@ const MainGrid = memo(
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.High}
minSize={200}
preferredSize={sizesMiddle[1] || '50%'}
>
@@ -338,24 +339,31 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
return (
<div className={wrapperClasses}>
<div className="border-b border-r border-default">
<div className="flex gap-2 justify-between items-center px-4 py-2">
<div className="h-full flex gap-2 justify-between items-end px-4 pt-1 pb-3">
<HeaderTitle
primaryContent={market?.tradableInstrument.instrument.code}
secondaryContent={market?.tradableInstrument.instrument.name}
/>
<button
onClick={() => setSidebarOpen((x) => !x)}
className="p-2"
className="flex flex-col items-center text-xs w-12"
data-testid="sidebar-toggle"
>
<span
className={classNames('block', {
'rotate-90 translate-x-1': !sidebarOpen,
'-rotate-90 -translate-x-1': sidebarOpen,
})}
>
<VegaIcon name={VegaIconNames.CHEVRON_UP} />
</span>
{sidebarOpen ? (
<>
<VegaIcon name={VegaIconNames.CHEVRON_UP} />
<span className="text-vega-light-300 dark:text-vega-dark-300">
{t('Close')}
</span>
</>
) : (
<>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
<span className="text-vega-light-300 dark:text-vega-dark-300">
{t('Markets')}
</span>
</>
)}
</button>
</div>
</div>
@@ -315,39 +315,47 @@ describe('useMarketSelectorList', () => {
]);
});
it('sorts albphabetically', () => {
it('sorts by state and volume by default', () => {
const markets = [
createMarketFragment({
id: 'market-0',
tradableInstrument: {
instrument: {
code: 'd',
state: MarketState.STATE_PENDING,
// @ts-ignore candles not on fragment
candles: [
{
volume: '200',
},
},
],
}),
createMarketFragment({
id: 'market-1',
tradableInstrument: {
instrument: {
code: 'b',
state: MarketState.STATE_ACTIVE,
// @ts-ignore candles not on fragment
candles: [
{
volume: '200',
},
},
],
}),
createMarketFragment({
id: 'market-2',
tradableInstrument: {
instrument: {
code: 'a',
state: MarketState.STATE_ACTIVE,
// @ts-ignore candles not on fragment
candles: [
{
volume: '100',
},
},
],
}),
createMarketFragment({
state: MarketState.STATE_PENDING,
id: 'market-3',
tradableInstrument: {
instrument: {
code: 'c',
// @ts-ignore candles not on fragment
candles: [
{
volume: '100',
},
},
],
}),
];
@@ -364,10 +372,10 @@ describe('useMarketSelectorList', () => {
assets: [],
});
expect(result.current.markets).toEqual([
markets[2],
markets[1],
markets[3],
markets[2],
markets[0],
markets[3],
]);
});
@@ -1,11 +1,18 @@
import { useMemo } from 'react';
import orderBy from 'lodash/orderBy';
import { MarketState } from '@vegaprotocol/types';
import { useMarketList } from '@vegaprotocol/markets';
import { calcCandleVolume, useMarketList } from '@vegaprotocol/markets';
import { priceChangePercentage } from '@vegaprotocol/utils';
import type { Filter } from './market-selector';
import { Sort } from './sort-dropdown';
// Used for sort order and filter
const MARKET_TEMPLATE = [
MarketState.STATE_ACTIVE,
MarketState.STATE_SUSPENDED,
MarketState.STATE_PENDING,
];
export const useMarketSelectorList = ({
product,
assets,
@@ -46,7 +53,19 @@ export const useMarketSelectorList = ({
});
if (sort === Sort.None) {
return orderBy(markets, ['tradableInstrument.instrument.code'], ['asc']);
// Sort by market state primarilly and AtoZ secondarilly
return orderBy(
markets,
[
(m) => MARKET_TEMPLATE.indexOf(m.state),
(m) => {
if (!m.candles?.length) return 0;
const vol = calcCandleVolume(m.candles);
return Number(vol || 0);
},
],
['asc', 'desc']
);
}
if (sort === Sort.Gained || sort === Sort.Lost) {
@@ -78,9 +97,5 @@ export const useMarketSelectorList = ({
};
export const isMarketActive = (state: MarketState) => {
return [
MarketState.STATE_ACTIVE,
MarketState.STATE_SUSPENDED,
MarketState.STATE_PENDING,
].includes(state);
return MARKET_TEMPLATE.includes(state);
};
+1
View File
@@ -1 +1,2 @@
export * from './announcement-banner';
export * from './upgrade-banner';
@@ -0,0 +1,79 @@
import { useMemo, useState } from 'react';
import {
ReleasesFeed,
useEnvironment,
useReleases,
Networks,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import {
CopyWithTooltip,
ExternalLink,
Intent,
NotificationBanner,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
const CANONICAL_URL = 'https://vega.trading';
type UpgradeBannerProps = {
showVersionChange: boolean;
};
export const UpgradeBanner = ({ showVersionChange }: UpgradeBannerProps) => {
const [visible, setVisible] = useState(true);
const { data } = useReleases(ReleasesFeed.FrontEnd);
const { APP_VERSION, VEGA_ENV } = useEnvironment();
/**
* Filtering out the release candidates.
*/
const latest = useMemo(() => {
const valid =
VEGA_ENV === Networks.MAINNET
? data?.filter((r) => !/-rc$/i.test(r.tagName))
: data;
return valid && valid.length > 0 ? valid[0] : undefined;
}, [VEGA_ENV, data]);
if (!APP_VERSION) return null;
if (!visible || !latest || latest.tagName === APP_VERSION) return null;
const versionChange = (
<span>
<span className="line-through text-vega-light-300 dark:text-vega-dark-300">
{APP_VERSION}
</span>{' '}
<VegaIcon size={14} name={VegaIconNames.ARROW_RIGHT} />{' '}
<span className="text-vega-orange-500 dark:text-vega-yellow-500">
<ExternalLink href={latest.htmlUrl}>{latest.tagName}</ExternalLink>
</span>
</span>
);
return (
<NotificationBanner
intent={Intent.Warning}
onClose={() => {
setVisible(false);
}}
>
<div className="uppercase ">
{t('Upgrade to the latest version of Console')}{' '}
{showVersionChange && versionChange}
</div>
<div data-testid="bookmark-message">
{t('Bookmark')}{' '}
<ExternalLink href={CANONICAL_URL}>{t('vega.trading')}</ExternalLink>
<CopyWithTooltip text={CANONICAL_URL}>
<button title={t('Copy %s', CANONICAL_URL)}>
<span className="sr-only">{t('Copy %s', CANONICAL_URL)}</span>
<VegaIcon size={14} name={VegaIconNames.COPY} />
</button>
</CopyWithTooltip>{' '}
{'to always see the latest version.'}
</div>
</NotificationBanner>
);
};
+1
View File
@@ -1,4 +1,5 @@
import { t } from '@vegaprotocol/i18n';
export const THROTTLE_UPDATE_TIME = 500;
export const RISK_ACCEPTED_KEY = 'vega_risk_accepted';
export const MAINNET_WELCOME_HEADER = t(
+2 -2
View File
@@ -73,10 +73,10 @@ export const HeaderTitle = ({
}) => {
return (
<div className="text-left" data-testid="header-title">
<div className="text-sm md:text-md lg:text-lg whitespace-nowrap leading-4">
<div className="text-sm md:text-md lg:text-lg whitespace-nowrap !leading-[1]">
{primaryContent}
</div>
<div className="text-xs whitespace-nowrap text-neutral-500 dark:text-neutral-400">
<div className="text-xs whitespace-nowrap text-vega-light-300 dark:text-vega-dark-300">
{secondaryContent}
</div>
</div>
+1 -6
View File
@@ -4,15 +4,10 @@ export const WalletIcon = ({ className }: { className?: string }) => {
width="26"
height="18"
viewBox="0 0 26 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
data-testid="wallet-icon"
>
<path
d="M4.77437 17.7499H4.74987C3.6504 17.7368 2.77439 16.8489 2.77439 15.7772V12.8495V12.6343L2.5615 12.6023C1.59672 12.4575 0.849609 11.6116 0.849609 10.6266V7.40064C0.849609 6.39018 1.59509 5.56985 2.56147 5.4249L2.77439 5.39297V5.17767V2.24998C2.77439 1.14102 3.66537 0.25 4.77437 0.25H23.7501C24.8591 0.25 25.7501 1.14098 25.7501 2.24998V15.7499C25.7501 16.8588 24.8591 17.7499 23.7501 17.7499H4.77437ZM4.44917 12.5992H4.19917L4.77441 16.075V16.325H4.77466H23.7502C24.0778 16.325 24.3254 16.0777 24.3254 15.7497V2.24984C24.3254 1.9222 24.0782 1.6746 23.7502 1.6746H4.77441C4.44677 1.6746 4.19917 1.92182 4.19917 2.24984V5.12306V5.37306H4.44917H7.0244C8.51139 5.37306 9.67508 6.56094 9.67508 8.02374V9.94852C9.67508 11.4355 8.4872 12.5992 7.0244 12.5992H4.44917ZM2.84963 6.8253C2.52199 6.8253 2.27439 7.07253 2.27439 7.40054V10.6264C2.27439 10.9541 2.52161 11.2017 2.84962 11.2017L7.02419 11.2019C7.73619 11.2019 8.25009 10.6515 8.25009 9.97598V8.0512C8.25009 7.3392 7.69976 6.8253 7.0242 6.8253H2.84963Z"
stroke="none"
/>
<path d="M4.77437 17.7499H4.74987C3.6504 17.7368 2.77439 16.8489 2.77439 15.7772V12.8495V12.6343L2.5615 12.6023C1.59672 12.4575 0.849609 11.6116 0.849609 10.6266V7.40064C0.849609 6.39018 1.59509 5.56985 2.56147 5.4249L2.77439 5.39297V5.17767V2.24998C2.77439 1.14102 3.66537 0.25 4.77437 0.25H23.7501C24.8591 0.25 25.7501 1.14098 25.7501 2.24998V15.7499C25.7501 16.8588 24.8591 17.7499 23.7501 17.7499H4.77437ZM4.44917 12.5992H4.19917L4.77441 16.075V16.325H4.77466H23.7502C24.0778 16.325 24.3254 16.0777 24.3254 15.7497V2.24984C24.3254 1.9222 24.0782 1.6746 23.7502 1.6746H4.77441C4.44677 1.6746 4.19917 1.92182 4.19917 2.24984V5.12306V5.37306H4.44917H7.0244C8.51139 5.37306 9.67508 6.56094 9.67508 8.02374V9.94852C9.67508 11.4355 8.4872 12.5992 7.0244 12.5992H4.44917ZM2.84963 6.8253C2.52199 6.8253 2.27439 7.07253 2.27439 7.40054V10.6264C2.27439 10.9541 2.52161 11.2017 2.84962 11.2017L7.02419 11.2019C7.73619 11.2019 8.25009 10.6515 8.25009 9.97598V8.0512C8.25009 7.3392 7.69976 6.8253 7.0242 6.8253H2.84963Z" />
</svg>
);
};
+3 -2
View File
@@ -16,10 +16,11 @@ import {
NavigationItem,
NavigationLink,
ExternalLink,
Icon,
NavigationBreakpoint,
NavigationTrigger,
NavigationContent,
VegaIconNames,
VegaIcon,
} from '@vegaprotocol/ui-toolkit';
import { Links, Routes } from '../../pages/client-router';
@@ -146,7 +147,7 @@ const NavExternalLink = ({
<ExternalLink href={href}>
<span className="flex items-center gap-2">
<span>{children}</span>
<Icon name="arrow-top-right" size={3} />
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</span>
</ExternalLink>
);
@@ -1,2 +0,0 @@
export * from './select-market-columns';
export * from './select-market-table';
@@ -1,344 +0,0 @@
import type { RefObject, MouseEvent } from 'react';
import {
FeesCell,
calcCandleHigh,
calcCandleLow,
calcCandleVolume,
Last24hPriceChange,
Last24hVolume,
} from '@vegaprotocol/markets';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { PriceCell } from '@vegaprotocol/datagrid';
import { Link as UILink, Sparkline, Tooltip } from '@vegaprotocol/ui-toolkit';
import isNil from 'lodash/isNil';
import type { CandleClose } from '@vegaprotocol/types';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
import { Link } from 'react-router-dom';
import { MarketMarkPrice } from '../market-mark-price';
import { MarketTradingMode } from '../market-trading-mode';
import { Links, Routes } from '../../pages/client-router';
const ellipsisClasses = 'whitespace-nowrap overflow-hidden text-ellipsis';
export const cellClassNames = `py-1 first:text-left text-right ${ellipsisClasses}`;
const FeesInfo = () => {
return (
<Tooltip
description={
<span>
{t(
'Fees are paid by market takers on aggressive orders only. The fee displayed is made up of:'
)}
<ul className="list-disc ml-4">
<li>{t('An infrastructure fee')}</li>
<li>{t('A liquidity provision fee')}</li>
<li>{t('A maker fee')}</li>
</ul>
</span>
}
>
<span>{t('Taker fee')}</span>
</Tooltip>
);
};
export enum ColumnKind {
Market,
LastPrice,
Change24,
Asset,
ProductType,
Sparkline,
High24,
Low24,
TradingMode,
Volume,
Fee,
Position,
FullName,
}
export interface Column {
kind: ColumnKind;
value: string | React.ReactNode;
className: string;
onlyOnDetailed: boolean;
dataTestId?: string;
}
const headers: Column[] = [
{
kind: ColumnKind.Market,
value: t('Market'),
className: cellClassNames,
onlyOnDetailed: false,
},
{
kind: ColumnKind.ProductType,
value: t('Type'),
className: 'py-2 text-left hidden sm:table-cell',
onlyOnDetailed: false,
},
{
kind: ColumnKind.LastPrice,
value: t('Last price'),
className: cellClassNames,
onlyOnDetailed: false,
},
{
kind: ColumnKind.Change24,
value: t('Change (24h)'),
className: cellClassNames,
onlyOnDetailed: false,
},
{
kind: ColumnKind.Sparkline,
value: t(''),
className: `${cellClassNames} hidden lg:table-cell`,
onlyOnDetailed: false,
},
{
kind: ColumnKind.Asset,
value: t('Settlement asset'),
className: `${cellClassNames} hidden sm:table-cell`,
onlyOnDetailed: false,
},
{
kind: ColumnKind.High24,
value: t('24h High'),
className: `${cellClassNames} hidden xl:table-cell`,
onlyOnDetailed: true,
},
{
kind: ColumnKind.Low24,
value: t('24h Low'),
className: `${cellClassNames} hidden xl:table-cell`,
onlyOnDetailed: true,
},
{
kind: ColumnKind.Volume,
value: t('24h Volume'),
className: `${cellClassNames} hidden lg:table-cell`,
onlyOnDetailed: true,
},
{
kind: ColumnKind.TradingMode,
value: t('Trading mode'),
className: `${cellClassNames} hidden lg:table-cell`,
onlyOnDetailed: true,
},
{
kind: ColumnKind.Fee,
value: <FeesInfo />,
className: `${cellClassNames} hidden xl:table-cell`,
onlyOnDetailed: true,
},
];
export const columnHeadersPositionMarkets: Column[] = [
...headers,
{
kind: ColumnKind.Position,
value: t('Position'),
className: `${cellClassNames} hidden xxl:table-cell`,
onlyOnDetailed: true,
},
];
export const columnHeaders: Column[] = [
...headers,
{
kind: ColumnKind.FullName,
value: t('Full name'),
className: `${cellClassNames} hidden xxl:block`,
onlyOnDetailed: true,
},
];
export type OnCellClickHandler = (
e: MouseEvent,
kind: ColumnKind,
value: string
) => void;
export const columns = (
market: MarketMaybeWithDataAndCandles,
onSelect: (id: string, metaKey?: boolean) => void,
onCellClick: OnCellClickHandler,
inViewRoot?: RefObject<HTMLElement>
) => {
const candlesClose = market.candles
?.map((candle) => candle?.close)
.filter((c: string | undefined): c is CandleClose => !isNil(c));
const candleLow = market.candles && calcCandleLow(market.candles);
const candleHigh = market.candles && calcCandleHigh(market.candles);
const candleVolume = market.candles && calcCandleVolume(market.candles);
const selectMarketColumns: Column[] = [
{
kind: ColumnKind.Market,
value: (
<Link
to={Links[Routes.MARKET](market.id)}
data-testid={`market-link-${market.id}`}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onSelect(market.id, e.metaKey || e.ctrlKey);
}}
>
<UILink>{market.tradableInstrument.instrument.code}</UILink>
</Link>
),
className: `${cellClassNames} max-w-[110px]`,
onlyOnDetailed: false,
},
{
kind: ColumnKind.ProductType,
value: market.tradableInstrument.instrument.product.__typename,
className: `py-2 text-left hidden sm:table-cell max-w-[50px] ${ellipsisClasses}`,
onlyOnDetailed: false,
},
{
kind: ColumnKind.LastPrice,
value: (
<MarketMarkPrice
marketId={market.id}
decimalPlaces={market?.decimalPlaces}
initialValue={market.data?.markPrice}
inViewRoot={inViewRoot}
asPriceCell
/>
),
className: `${cellClassNames} max-w-[100px]`,
onlyOnDetailed: false,
},
{
kind: ColumnKind.Change24,
value: (
<Last24hPriceChange
marketId={market.id}
decimalPlaces={market?.decimalPlaces}
inViewRoot={inViewRoot}
initialValue={candlesClose}
/>
),
className: `${cellClassNames} max-w-[150px]`,
onlyOnDetailed: false,
},
{
kind: ColumnKind.Sparkline,
value: market.candles && (
<Sparkline
width={100}
height={20}
muted={false}
data={candlesClose?.map((c: string) => Number(c)) || []}
/>
),
className: `${cellClassNames} hidden lg:table-cell max-w-[80px]`,
onlyOnDetailed: false && candlesClose,
},
{
kind: ColumnKind.Asset,
value: (
<button
data-dialog-trigger
className="inline underline"
onClick={(e) => {
e.stopPropagation();
onCellClick(
e,
ColumnKind.Asset,
market.tradableInstrument.instrument.product.settlementAsset.id
);
}}
>
{market.tradableInstrument.instrument.product.settlementAsset.symbol}
</button>
),
dataTestId: 'settlement-asset',
className: `${cellClassNames} hidden sm:table-cell max-w-[100px]`,
onlyOnDetailed: false,
},
{
kind: ColumnKind.High24,
value: candleHigh ? (
<PriceCell
value={Number(candleHigh)}
valueFormatted={addDecimalsFormatNumber(
candleHigh.toString(),
market.decimalPlaces,
2
)}
/>
) : (
'-'
),
className: `${cellClassNames} hidden xl:table-cell font-mono`,
onlyOnDetailed: true,
},
{
kind: ColumnKind.Low24,
value: candleLow ? (
<PriceCell
value={Number(candleLow)}
valueFormatted={addDecimalsFormatNumber(
candleLow.toString(),
market.decimalPlaces,
2
)}
/>
) : (
'-'
),
className: `${cellClassNames} hidden xl:table-cell font-mono`,
onlyOnDetailed: true,
},
{
kind: ColumnKind.Volume,
value: (
<Last24hVolume
marketId={market.id}
positionDecimalPlaces={market.positionDecimalPlaces}
initialValue={candleVolume}
inViewRoot={inViewRoot}
formatDecimals={2}
/>
),
className: `${cellClassNames} hidden lg:table-cell font-mono`,
onlyOnDetailed: true,
dataTestId: 'market-volume',
},
{
kind: ColumnKind.TradingMode,
value: (
<MarketTradingMode
marketId={market?.id}
inViewRoot={inViewRoot}
initialTradingMode={market.tradingMode}
initialTrigger={market.data?.trigger}
/>
),
className: `${cellClassNames} hidden lg:table-cell`,
onlyOnDetailed: true,
dataTestId: 'trading-mode-col',
},
{
kind: ColumnKind.Fee,
value: <FeesCell feeFactors={market.fees.factors} />,
className: `${cellClassNames} hidden xl:table-cell font-mono`,
onlyOnDetailed: true,
dataTestId: 'taker-fee',
},
{
kind: ColumnKind.FullName,
value: market.tradableInstrument.instrument.name,
className: `${cellClassNames} hidden xxl:block`,
onlyOnDetailed: true,
dataTestId: 'market-name',
},
];
return selectMarketColumns;
};
@@ -1,63 +0,0 @@
import { columnHeaders } from './select-market-columns';
import classNames from 'classnames';
import type { Column } from './select-market-columns';
export const SelectMarketTableHeader = ({
detailed = false,
headers = columnHeaders,
}) => {
return (
<tr className="sticky top-0 z-10 border-b border-default bg-inherit">
{headers.map(({ kind, value, className, onlyOnDetailed }) => {
const thClass = classNames(
'font-normal text-neutral-500 dark:text-neutral-400',
className
);
if (!onlyOnDetailed || detailed === onlyOnDetailed) {
return (
<th key={kind} className={thClass}>
{value}
</th>
);
}
return null;
})}
</tr>
);
};
export const SelectMarketTableRow = ({
detailed = false,
columns,
onSelect,
marketId,
}: {
detailed?: boolean;
columns: Column[];
onSelect: (id: string, metaKey?: boolean) => void;
marketId: string;
}) => {
return (
<tr
className={`hover:bg-neutral-200 dark:hover:bg-neutral-700 cursor-pointer relative h-[34px]`}
onClick={(ev) => {
onSelect(marketId, ev.metaKey || ev.ctrlKey);
}}
data-testid={`market-link-${marketId}`}
>
{columns.map(({ kind, value, className, dataTestId, onlyOnDetailed }) => {
if (!onlyOnDetailed || detailed === onlyOnDetailed) {
const tdClass = classNames(className);
return (
<td key={kind} data-testid={dataTestId} className={tdClass}>
{value}
</td>
);
}
return null;
})}
</tr>
);
};
@@ -53,12 +53,13 @@ const MobileWalletButton = ({
setDrawerOpen(!drawerOpen);
}
}, [drawerOpen, fetchPubKeys, isConnected, openVegaWalletDialog]);
console.log(drawerOpen, isYellow);
const iconClass = drawerOpen
? 'hidden'
: isYellow
? 'fill-black'
: 'fill-black dark:fill-white';
: 'fill-white';
const [container, setContainer] = useState<HTMLElement | null>(null);
const walletButton = (
@@ -1,11 +0,0 @@
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import * as constants from '../constants';
export const WelcomeDialogHeader = () => {
const { VEGA_ENV } = useEnvironment();
const header =
VEGA_ENV === Networks.MAINNET
? constants.MAINNET_WELCOME_HEADER
: constants.TESTNET_WELCOME_HEADER;
return <h1 className="mb-6 p-4 text-center text-2xl">{header}</h1>;
};
@@ -8,7 +8,6 @@ import { activeMarketsProvider } from '@vegaprotocol/markets';
import * as constants from '../constants';
import { RiskNoticeDialog } from './risk-notice-dialog';
import { WelcomeNoticeDialog } from './welcome-notice-dialog';
import { WelcomeLandingDialog } from './welcome-landing-dialog';
import { useGlobalStore } from '../../stores';
import { useEnvironment } from '@vegaprotocol/environment';
import { Networks } from '@vegaprotocol/environment';
@@ -43,6 +42,7 @@ export const WelcomeDialog = () => {
shouldDisplayWelcomeDialog: isRiskDialogNeeded,
});
}, [update, isRiskDialogNeeded]);
if (isRiskDialogNeeded) {
dialogContent = (
<RiskNoticeDialog onClose={onCloseDialog} network={VEGA_ENV} />
@@ -52,9 +52,6 @@ export const WelcomeDialog = () => {
} else if (isWelcomeDialogNeeded && data?.length === 0) {
dialogContent = <WelcomeNoticeDialog />;
onClose = onCloseDialog;
} else if (isWelcomeDialogNeeded && (data?.length || 0) > 0) {
dialogContent = <WelcomeLandingDialog onClose={onCloseDialog} />;
onClose = onCloseDialog;
} else {
dialogContent = null as React.ReactNode;
}
@@ -1,258 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import * as Schema from '@vegaprotocol/types';
import type {
MarketMaybeWithCandles,
MarketMaybeWithData,
MarketData,
} from '@vegaprotocol/markets';
import { SelectMarketLandingTable } from './welcome-landing-dialog';
const mockMarketClickHandler = jest.fn();
jest.mock('../../lib/hooks/use-market-click-handler', () => ({
useMarketClickHandler: () => mockMarketClickHandler,
}));
type Market = MarketMaybeWithCandles & MarketMaybeWithData;
type PartialMarket = Partial<
Omit<Market, 'data'> & { data: Partial<MarketData> }
>;
const MARKET_A: PartialMarket = {
__typename: 'Market',
id: '1',
decimalPlaces: 2,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
id: '1',
code: 'ABCDEF',
name: 'ABCDEF 1-Day',
product: {
__typename: 'Future',
quoteName: 'ABCDEF',
settlementAsset: {
__typename: 'Asset',
id: 'asset-ABC',
name: 'asset-ABC',
decimals: 2,
symbol: 'ABC',
},
dataSourceSpecForTradingTermination: {
__typename: 'DataSourceSpec',
id: 'oracleId',
data: {
__typename: 'DataSourceDefinition',
sourceType: {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
},
},
},
},
dataSourceSpecForSettlementData: {
__typename: 'DataSourceSpec',
id: 'oracleId',
data: {
__typename: 'DataSourceDefinition',
sourceType: {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
},
},
},
},
dataSourceSpecBinding: {
__typename: 'DataSourceSpecToFutureBinding',
tradingTerminationProperty: 'trading-termination-property',
settlementDataProperty: 'settlement-data-property',
},
},
metadata: {
__typename: 'InstrumentMetadata',
tags: ['ABC', 'DEF'],
},
},
},
fees: {
__typename: 'Fees',
factors: {
__typename: 'FeeFactors',
infrastructureFee: '0.01',
liquidityFee: '0.01',
makerFee: '0.01',
},
},
data: {
__typename: 'MarketData',
market: {
__typename: 'Market',
id: '1',
},
markPrice: '90',
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_OPENING,
marketState: Schema.MarketState.STATE_PENDING,
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
indicativeVolume: '1000',
},
candles: [
{
__typename: 'Candle',
high: '100',
low: '10',
open: '10',
close: '80',
volume: '1000',
periodStart: '2022-11-01T15:49:00Z',
},
{
__typename: 'Candle',
high: '10',
low: '1',
open: '1',
close: '100',
volume: '1000',
periodStart: '2022-11-01T15:50:00Z',
},
],
};
const MARKET_B: PartialMarket = {
__typename: 'Market',
id: '2',
decimalPlaces: 2,
positionDecimalPlaces: 0,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
id: '2',
code: 'XYZ',
name: 'XYZ 1-Day',
product: {
__typename: 'Future',
quoteName: 'XYZ',
settlementAsset: {
__typename: 'Asset',
id: 'asset-XYZ',
name: 'asset-XYZ',
decimals: 2,
symbol: 'XYZ',
},
dataSourceSpecForTradingTermination: {
__typename: 'DataSourceSpec',
id: 'oracleId',
data: {
__typename: 'DataSourceDefinition',
sourceType: {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
},
},
},
},
dataSourceSpecForSettlementData: {
__typename: 'DataSourceSpec',
id: 'oracleId',
data: {
__typename: 'DataSourceDefinition',
sourceType: {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
},
},
},
},
dataSourceSpecBinding: {
__typename: 'DataSourceSpecToFutureBinding',
tradingTerminationProperty: 'trading-termination-property',
settlementDataProperty: 'settlement-data-property',
},
},
metadata: {
__typename: 'InstrumentMetadata',
tags: ['XYZ'],
},
},
},
fees: {
__typename: 'Fees',
factors: {
__typename: 'FeeFactors',
infrastructureFee: '0.01',
liquidityFee: '0.01',
makerFee: '0.01',
},
},
data: {
__typename: 'MarketData',
market: {
__typename: 'Market',
id: '2',
},
markPrice: '123.123',
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_OPENING,
marketState: Schema.MarketState.STATE_PENDING,
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
indicativeVolume: '2000',
},
candles: [
{
__typename: 'Candle',
high: '100',
low: '10',
open: '10',
close: '80',
volume: '1000',
periodStart: '2022-11-01T15:49:00Z',
},
],
};
describe('WelcomeLandingDialog', () => {
it('should call onSelect callback on SelectMarketLandingTable', () => {
const onClose = jest.fn();
render(
<MemoryRouter>
<SelectMarketLandingTable
markets={[MARKET_A as Market, MARKET_B as Market]}
onClose={onClose}
/>
</MemoryRouter>,
{ wrapper: MockedProvider }
);
fireEvent.click(screen.getAllByTestId(`market-link-1`)[0]);
expect(onClose).toHaveBeenCalled();
fireEvent.click(screen.getAllByTestId(`market-link-2`)[0]);
expect(onClose).toHaveBeenCalled();
});
it('should not call onClose when metaKey is held', () => {
const onClose = jest.fn();
render(
<MemoryRouter>
<SelectMarketLandingTable
markets={[MARKET_A as Market, MARKET_B as Market]}
onClose={onClose}
/>
</MemoryRouter>,
{ wrapper: MockedProvider }
);
fireEvent.click(screen.getAllByTestId(`market-link-1`)[0], {
metaKey: true,
});
expect(mockMarketClickHandler).toHaveBeenCalled();
expect(onClose).not.toHaveBeenCalled();
fireEvent.click(screen.getAllByTestId(`market-link-1`)[0]);
expect(onClose).toHaveBeenCalled();
});
});
@@ -1,130 +0,0 @@
import React, { useCallback } from 'react';
import { useMarketList } from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { Link as UILink, TinyScroll } from '@vegaprotocol/ui-toolkit';
import type { OnCellClickHandler } from '../select-market';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
import {
ColumnKind,
columns,
SelectMarketTableHeader,
SelectMarketTableRow,
} from '../select-market';
import { WelcomeDialogHeader } from './welcome-dialog-header';
import { Link } from 'react-router-dom';
import { ProposedMarkets } from './proposed-markets';
import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { TelemetryApproval } from './telemetry-approval';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
export const SelectMarketLandingTable = ({
markets,
onClose,
}: {
markets: MarketMaybeWithDataAndCandles[] | null;
onClose: () => void;
}) => {
const onSelect = useMarketClickHandler();
const onSelectMarket = useCallback(
(id: string, metaKey?: boolean) => {
onSelect(id, metaKey);
if (!metaKey) {
onClose();
}
},
[onSelect, onClose]
);
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const onCellClick = useCallback<OnCellClickHandler>(
(e, kind, value) => {
if (value && kind === ColumnKind.Asset) {
openAssetDetailsDialog(value, e.target as HTMLElement);
}
},
[openAssetDetailsDialog]
);
const showProposed = (markets?.length || 0) <= 5;
return (
<>
<TinyScroll
className="max-h-[60vh] overflow-x-auto -mr-4 pr-4"
data-testid="select-market-list"
>
<p className="text-neutral-500 dark:text-neutral-400 mb-4">
{t('Select a market to get started...')}
</p>
<table className="text-sm relative h-full min-w-full whitespace-nowrap">
<thead className="sticky top-0 z-10 bg-white dark:bg-black">
<SelectMarketTableHeader />
</thead>
<tbody>
{markets?.map((market, i) => (
<SelectMarketTableRow
marketId={market.id}
key={i}
detailed={false}
onSelect={onSelectMarket}
columns={columns(market, onSelectMarket, onCellClick)}
/>
))}
</tbody>
</table>
</TinyScroll>
<div className="mt-4 text-md">
<Link
to={Links[Routes.MARKETS]()}
data-testid="view-market-list-link"
onClick={() => onClose()}
>
<UILink className="text-sm underline">
{'Or view full market list'}
</UILink>
</Link>
</div>
{showProposed && <ProposedMarkets />}
</>
);
};
interface LandingDialogContainerProps {
onClose: () => void;
}
export const WelcomeLandingDialog = ({
onClose,
}: LandingDialogContainerProps) => {
const { data, loading, error } = useMarketList();
const { VEGA_ENV } = useEnvironment();
const isMainnet = VEGA_ENV === Networks.MAINNET;
if (error) {
return (
<div className="flex justify-center items-center">
<p className="my-8">{t('Failed to load markets')}</p>
</div>
);
}
if (loading) {
return (
<div className="flex justify-center items-center">
<p className="my-8">{t('Loading...')}</p>
</div>
);
}
return (
<>
<WelcomeDialogHeader />
<SelectMarketLandingTable markets={data} onClose={onClose} />
{isMainnet && (
<TelemetryApproval
helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
)}
/>
)}
</>
);
};
+2 -1
View File
@@ -31,7 +31,7 @@ import ToastsManager from './toasts-manager';
import { HashRouter, useLocation, useSearchParams } from 'react-router-dom';
import { Connectors } from '../lib/vega-connectors';
import { ViewingBanner } from '../components/viewing-banner';
import { AnnouncementBanner } from '../components/banner';
import { AnnouncementBanner, UpgradeBanner } from '../components/banner';
import { AppLoader, DynamicLoader } from '../components/app-loader';
import { Navbar } from '../components/navbar';
import { ENV } from '../lib/config';
@@ -99,6 +99,7 @@ function AppBody({ Component }: AppProps) {
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
/>
<ViewingBanner />
<UpgradeBanner showVersionChange={true} />
</div>
<main data-testid={location.pathname}>
<Component />
+11 -10
View File
@@ -2,9 +2,10 @@ import { useEffect, useState } from 'react';
import type { AppNameType, Announcement } from './schema';
import { useAnnouncement } from './hooks/use-announcement';
import {
Icon,
AnnouncementBanner as Banner,
ExternalLink,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
export type AnnouncementBannerProps = {
@@ -75,22 +76,22 @@ export const AnnouncementBanner = ({
return (
<Banner className="relative px-10">
<button
className="absolute left-0 top-0 w-10 h-full flex items-center justify-center"
data-testid="app-announcement-close"
onClick={() => setVisible(false)}
>
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
</button>
<div
data-testid="app-announcement"
className="relative font-alpha flex gap-2 justify-center calt uppercase text-center text-lg text-white"
className="relative font-alpha flex gap-2 justify-center text-center text-lg text-white"
>
<span className="pr-4">{data.text}</span>
<span>{data.text}</span>{' '}
{data.urlText && data.url && (
<ExternalLink href={data.url}>{data.urlText}</ExternalLink>
)}
</div>
<button
className="absolute right-0 top-0 p-4 w-10 h-full flex items-center justify-center text-white"
data-testid="app-announcement-close"
onClick={() => setVisible(false)}
>
<VegaIcon name={VegaIconNames.CROSS} size={24} />
</button>
</Banner>
);
};
@@ -7,6 +7,8 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
ExternalLink,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '../../hooks/use-environment';
import { Networks } from '../../types';
@@ -114,7 +116,10 @@ export const NetworkSwitcher = ({
className
)}
>
<span className="mr-2"> {envTriggerMapping[current]}</span>
<span className="flex justify-between items-center gap-2">
<span>{envTriggerMapping[current]}</span>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
</span>
</DropdownMenuTrigger>
}
>
+1
View File
@@ -4,3 +4,4 @@ export * from './use-node-health';
export * from './use-node-switcher-store';
export * from './use-vega-releases';
export * from './use-vega-release';
export * from './use-releases';
File diff suppressed because one or more lines are too long
@@ -286,6 +286,7 @@ function compileEnvVars() {
ETH_WALLET_MNEMONIC: process.env['NX_ETH_WALLET_MNEMONIC'],
ORACLE_PROOFS_URL: process.env['NX_ORACLE_PROOFS_URL'],
VEGA_DOCS_URL: process.env['NX_VEGA_DOCS_URL'],
VEGA_CONSOLE_URL: process.env['NX_VEGA_CONSOLE_URL'],
VEGA_EXPLORER_URL: process.env['NX_VEGA_EXPLORER_URL'],
VEGA_TOKEN_URL: process.env['NX_VEGA_TOKEN_URL'],
GITHUB_FEEDBACK_URL: process.env['NX_GITHUB_FEEDBACK_URL'],
@@ -295,6 +296,7 @@ function compileEnvVars() {
GIT_ORIGIN_URL: process.env['GIT_ORIGIN_URL'],
ANNOUNCEMENTS_CONFIG_URL: process.env['NX_ANNOUNCEMENTS_CONFIG_URL'],
VEGA_INCIDENT_URL: process.env['NX_VEGA_INCIDENT_URL'],
APP_VERSION: process.env['NX_APP_VERSION'],
};
return env;
+9 -4
View File
@@ -28,21 +28,24 @@ const EmptyLinks: DAppLinks = {
const ExplorerLinks = {
...EmptyLinks,
[Networks.TESTNET]: 'https://explorer.fairground.wtf',
[Networks.VALIDATOR_TESTNET]: 'https://validator-testnet.explorer.vega.xyz',
[Networks.VALIDATOR_TESTNET]:
'https://explorer.validators-testnet.vega.rocks',
[Networks.MAINNET]: 'https://explorer.vega.xyz',
};
const ConsoleLinks = {
...EmptyLinks,
[Networks.STAGNET1]: 'https://stagnet1.console.vega.xyz',
[Networks.STAGNET1]: 'https://trading.stagnet1.vega.rocks',
[Networks.TESTNET]: 'https://console.fairground.wtf',
[Networks.MAINNET]: 'https://vega.trading',
};
const TokenLinks = {
...EmptyLinks,
[Networks.DEVNET]: 'https://dev.governance.vega.xyz',
[Networks.TESTNET]: 'https://governance.fairground.wtf',
[Networks.VALIDATOR_TESTNET]: 'https://validator-testnet.governance.vega.xyz',
[Networks.VALIDATOR_TESTNET]:
'https://governance.validators-testnet.vega.rocks',
[Networks.MAINNET]: 'https://governance.vega.xyz',
};
@@ -72,10 +75,12 @@ export const DocsLinks = VEGA_DOCS_URL
: undefined;
export const useLinks = (dapp: DApp, network?: Net) => {
const { VEGA_ENV, VEGA_EXPLORER_URL, VEGA_TOKEN_URL } = useEnvironment();
const { VEGA_ENV, VEGA_EXPLORER_URL, VEGA_TOKEN_URL, VEGA_CONSOLE_URL } =
useEnvironment();
const fallback = {
[DApp.Explorer]: VEGA_EXPLORER_URL,
[DApp.Token]: VEGA_TOKEN_URL,
[DApp.Console]: VEGA_CONSOLE_URL,
};
let net = network || VEGA_ENV;
@@ -0,0 +1,48 @@
import { renderHook, waitFor } from '@testing-library/react';
import fetchMock from 'fetch-mock';
import { GITHUB_VEGA_FRONTEND_RELEASES_DATA } from './mocks/github-releases';
import {
GITHUB_VEGA_FRONTEND_RELEASES,
ReleasesFeed,
useReleases,
} from './use-releases';
describe('useReleases', () => {
afterEach(() => {
fetchMock.reset();
});
it('should return an empty list when request is unsuccessful', async () => {
fetchMock.get(GITHUB_VEGA_FRONTEND_RELEASES, 404);
const { result } = renderHook(() => {
return useReleases(ReleasesFeed.FrontEnd);
});
expect(result.current.loading).toBeTruthy();
await waitFor(() => {
expect(result.current.loading).toBeFalsy();
expect(result.current.data).toEqual([]);
});
});
it('should return a list of releases', async () => {
fetchMock.get(
GITHUB_VEGA_FRONTEND_RELEASES,
GITHUB_VEGA_FRONTEND_RELEASES_DATA
);
const { result } = renderHook(() => useReleases(ReleasesFeed.FrontEnd));
expect(result.current.loading).toBeTruthy();
await waitFor(() => {
expect(result.current.loading).toBeFalsy();
expect(result.current).not.toEqual([]);
const data = result.current.data;
expect(data?.[0].tagName).toEqual('v0.20.8-core-0.71.4');
expect(data?.[0].htmlUrl).toEqual(
'https://github.com/vegaprotocol/frontend-monorepo/releases/tag/v0.20.8-core-0.71.4'
);
expect(data?.[1].tagName).toEqual('v0.20.6-core-0.71.4-3');
expect(data?.[2].tagName).toEqual('v0.20.6-core-0.71.4');
});
});
});
+121
View File
@@ -0,0 +1,121 @@
import { localLoggerFactory } from '@vegaprotocol/utils';
import { useCallback, useEffect, useState } from 'react';
import z from 'zod';
export const GITHUB_VEGA_RELEASES =
'https://api.github.com/repos/vegaprotocol/vega/releases';
export const GITHUB_VEGA_DEV_RELEASES =
'https://api.github.com/repos/vegaprotocol/vega-dev-releases/releases';
export const GITHUB_VEGA_FRONTEND_RELEASES =
'https://api.github.com/repos/vegaprotocol/frontend-monorepo/releases';
export enum ReleasesFeed {
Vega = GITHUB_VEGA_RELEASES,
VegaDev = GITHUB_VEGA_DEV_RELEASES,
FrontEnd = GITHUB_VEGA_FRONTEND_RELEASES,
}
const GithubReleaseSchema = z.object({
url: z.string(),
html_url: z.string(),
id: z.number(),
tag_name: z.string(),
name: z.string().nullable(),
draft: z.boolean(),
created_at: z.string().datetime(),
});
const GithubReleasesSchema = z.array(GithubReleaseSchema);
type ReleaseInfo = {
id: number;
name: string;
tagName: string;
htmlUrl: string;
url: string;
isDraft: boolean;
createdAt: Date;
isDevRelease: boolean;
};
const toReleaseInfo = (
releaseData: z.infer<typeof GithubReleaseSchema>,
isDevRelease: boolean
): ReleaseInfo => ({
id: releaseData.id,
name: releaseData.name || '',
tagName: releaseData.tag_name,
htmlUrl: releaseData.html_url,
url: releaseData.url,
isDraft: releaseData.draft,
createdAt: new Date(releaseData.created_at),
isDevRelease,
});
export type ReleasesState = {
loading: boolean;
data: ReleaseInfo[] | null;
error: Error | null | undefined;
};
export const fetchReleases = async (
feed: ReleasesFeed,
cache: RequestCache = 'default'
) => {
const response = await fetch(String(feed), {
cache,
});
if (response.ok) {
const json = await response.json();
const data = GithubReleasesSchema.parse(json);
return data.map((d) => toReleaseInfo(d, feed === ReleasesFeed.VegaDev));
}
return [];
};
export const useReleases = (feed: ReleasesFeed, cache?: RequestCache) => {
const [state, setState] = useState<ReleasesState>({
loading: true,
data: null,
error: null,
});
const logger = localLoggerFactory({ application: 'github' });
const fetchData = useCallback(() => {
let mounted = true;
fetchReleases(feed, cache)
.then((releases) => {
if (mounted) {
setState({
loading: false,
data: releases.sort(
(a, b) => b.createdAt.valueOf() - a.createdAt.valueOf()
),
error: null,
});
}
})
.catch((err) => {
if (mounted) {
setState({
loading: false,
data: null,
error: err,
});
}
logger.error('get releases from GitHub API', err);
});
return () => {
mounted = false;
};
}, [cache, feed, logger]);
useEffect(() => {
fetchData();
}, [fetchData]);
return state;
};
@@ -1,9 +1,6 @@
import { renderHook, waitFor } from '@testing-library/react';
import fetchMock from 'fetch-mock';
import {
GITHUB_VEGA_DEV_RELEASES,
GITHUB_VEGA_RELEASES,
} from './use-vega-releases';
import { GITHUB_VEGA_DEV_RELEASES, GITHUB_VEGA_RELEASES } from './use-releases';
import {
GITHUB_VEGA_RELEASES_DATA,
GITHUB_VEGA_DEV_RELEASES_DATA,
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { useVegaReleases } from './use-vega-releases';
export const useVegaRelease = (tag: string, includeDevReleases = false) => {
const { data } = useVegaReleases();
const { data } = useVegaReleases(includeDevReleases, 'force-cache');
return useMemo(() => {
return data?.find((r) => r.tagName === tag);
}, [data, tag]);
@@ -1,14 +1,11 @@
import { renderHook, waitFor } from '@testing-library/react';
import fetchMock from 'fetch-mock';
import {
GITHUB_VEGA_DEV_RELEASES,
GITHUB_VEGA_RELEASES,
useVegaReleases,
} from './use-vega-releases';
import { useVegaReleases } from './use-vega-releases';
import {
GITHUB_VEGA_RELEASES_DATA,
GITHUB_VEGA_DEV_RELEASES_DATA,
} from './mocks/github-releases';
import { GITHUB_VEGA_DEV_RELEASES, GITHUB_VEGA_RELEASES } from './use-releases';
describe('useVegaReleases', () => {
afterEach(() => {
+14 -75
View File
@@ -1,82 +1,18 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { useCallback, useEffect, useState } from 'react';
import z from 'zod';
export const GITHUB_VEGA_RELEASES =
'https://api.github.com/repos/vegaprotocol/vega/releases';
export const GITHUB_VEGA_DEV_RELEASES =
'https://api.github.com/repos/vegaprotocol/vega-dev-releases/releases';
const GithubReleaseSchema = z.object({
url: z.string(),
html_url: z.string(),
id: z.number(),
tag_name: z.string(),
name: z.string().nullable(),
draft: z.boolean(),
created_at: z.string().datetime(),
});
const GithubReleasesSchema = z.array(GithubReleaseSchema);
type ReleaseInfo = {
id: number;
name: string;
tagName: string;
htmlUrl: string;
url: string;
isDraft: boolean;
createdAt: Date;
isDevRelease: boolean;
};
const toReleaseInfo = (
releaseData: z.infer<typeof GithubReleaseSchema>,
isDevRelease: boolean
): ReleaseInfo => ({
id: releaseData.id,
name: releaseData.name || '',
tagName: releaseData.tag_name,
htmlUrl: releaseData.html_url,
url: releaseData.url,
isDraft: releaseData.draft,
createdAt: new Date(releaseData.created_at),
isDevRelease,
});
enum ReleaseFeed {
// @ts-ignore TS18033 - allowed in 5.0
Vega = GITHUB_VEGA_RELEASES,
// @ts-ignore TS18033
VegaDev = GITHUB_VEGA_DEV_RELEASES,
}
const fetchReleases = async (feed: ReleaseFeed) => {
const response = await fetch(String(feed), {
cache: 'force-cache',
});
if (response.ok) {
const json = await response.json();
const data = GithubReleasesSchema.parse(json);
return data.map((d) => toReleaseInfo(d, feed === ReleaseFeed.VegaDev));
}
return [];
};
type State = {
loading: boolean;
data: ReleaseInfo[] | null;
error: Error | null | undefined;
};
import type { ReleasesState } from './use-releases';
import { ReleasesFeed } from './use-releases';
import { fetchReleases } from './use-releases';
/**
* Retrieves a list of vega releases from github.
* First element is the newest.
*/
export const useVegaReleases = (includeDevReleases = false) => {
const [state, setState] = useState<State>({
export const useVegaReleases = (
includeDevReleases = false,
cache?: RequestCache
) => {
const [state, setState] = useState<ReleasesState>({
loading: true,
data: null,
error: null,
@@ -87,8 +23,11 @@ export const useVegaReleases = (includeDevReleases = false) => {
Promise.all(
includeDevReleases
? [fetchReleases(ReleaseFeed.Vega), fetchReleases(ReleaseFeed.VegaDev)]
: [fetchReleases(ReleaseFeed.Vega)]
? [
fetchReleases(ReleasesFeed.Vega, cache),
fetchReleases(ReleasesFeed.VegaDev, cache),
]
: [fetchReleases(ReleasesFeed.Vega, cache)]
)
.then(([vega, vegaDev]) => {
if (mounted) {
@@ -114,7 +53,7 @@ export const useVegaReleases = (includeDevReleases = false) => {
return () => {
mounted = false;
};
}, [includeDevReleases, setState]);
}, [cache, includeDevReleases]);
useEffect(() => {
fetchData();
@@ -19,6 +19,7 @@ const schemaObject = {
GITHUB_FEEDBACK_URL: z.optional(z.string()),
ORACLE_PROOFS_URL: z.optional(z.string().url()),
VEGA_ENV: z.nativeEnum(Networks),
VEGA_CONSOLE_URL: z.optional(z.string()),
VEGA_EXPLORER_URL: z.optional(z.string()),
VEGA_TOKEN_URL: z.optional(z.string()),
VEGA_DOCS_URL: z.optional(z.string()),
@@ -50,6 +51,7 @@ const schemaObject = {
ETH_WALLET_MNEMONIC: z.optional(z.string()),
ANNOUNCEMENTS_CONFIG_URL: z.optional(z.string()),
VEGA_INCIDENT_URL: z.optional(z.string()),
APP_VERSION: z.optional(z.string()),
};
// combine schema above with custom rule to ensure either
@@ -203,13 +203,15 @@ export const MarketListTable = forwardRef<
/>
<AgGridColumn
headerName={t('Settlement asset')}
field="tradableInstrument.instrument.product.settlementAsset"
field="tradableInstrument.instrument.product.settlementAsset.symbol"
cellRenderer={({
value,
data,
}: VegaICellRendererParams<
MarketMaybeWithData,
'tradableInstrument.instrument.product.settlementAsset'
'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => {
const value =
data?.tradableInstrument.instrument.product.settlementAsset;
return value ? (
<ButtonLink
onClick={(e) => {
+7
View File
@@ -130,6 +130,13 @@ export const closedMarketsWithDataProvider = makeDerivedDataProvider<
addData(parts[0] as Market[], parts[1] as MarketData[])
);
export const allMarketsWithDataProvider = makeDerivedDataProvider<
MarketMaybeWithData[],
never
>([marketsProvider, marketsDataProvider], (parts) =>
addData(parts[0] as Market[], parts[1] as MarketData[])
);
export type MarketMaybeWithDataAndCandles = MarketMaybeWithData &
MarketMaybeWithCandles;
@@ -14,7 +14,7 @@ import type {
MarketMaybeWithData,
MarketDataQueryVariables,
} from '@vegaprotocol/markets';
import { marketsWithDataProvider } from '@vegaprotocol/markets';
import { allMarketsWithDataProvider } from '@vegaprotocol/markets';
import type {
PositionsQuery,
PositionFieldsFragment,
@@ -308,7 +308,8 @@ export const positionsMetricsProvider = makeDerivedDataProvider<
[
positionsDataProvider,
accountsDataProvider,
(callback, client) => marketsWithDataProvider(callback, client, undefined),
(callback, client) =>
allMarketsWithDataProvider(callback, client, undefined),
marginsDataProvider,
],
([positions, accounts, marketsData, margins], variables) => {
@@ -216,6 +216,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
/>
<AgGridColumn
headerName={t('Settlement asset')}
field="assetSymbol"
colId="asset"
minWidth={100}
cellRenderer={({ data }: VegaICellRendererParams<Position>) => {
@@ -0,0 +1,7 @@
export const IconArrowDown = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 16 16">
<path d="M8.53 12.3705L13.6252 7.27524L14.3748 8.02477L8 14.3995L1.62523 8.02477L2.37477 7.27524L7.47 12.3705V1.64999H8.53V12.3705Z" />
</svg>
);
};
@@ -0,0 +1,7 @@
export const IconChevronDown = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 16 16">
<path d="M1.62 5.62L2.38 4.87L8 10.5L13.62 4.87L14.38 5.62L8 12L1.62 5.62Z" />
</svg>
);
};
@@ -16,6 +16,8 @@ import { IconChevronUp } from './svg-icons/icon-chevron-up';
import { IconTrendUp } from './svg-icons/icon-trend-up';
import { IconCross } from './svg-icons/icon-cross';
import { IconKebab } from './svg-icons/icon-kebab';
import { IconArrowDown } from './svg-icons/icon-arrow-down';
import { IconChevronDown } from './svg-icons/icon-chevron-down';
export enum VegaIconNames {
BREAKDOWN = 'breakdown',
@@ -32,7 +34,9 @@ export enum VegaIconNames {
OPEN_EXTERNAL = 'open-external',
QUESTION_MARK = 'question-mark',
ARROW_RIGHT = 'arrow-right',
ARROW_DOWN = 'arrow-down',
CHEVRON_UP = 'chevron-up',
CHEVRON_DOWN = 'chevron-down',
TREND_UP = 'trend-up',
CROSS = 'cross',
KEBAB = 'kebab',
@@ -56,7 +60,9 @@ export const VegaIconNameMap: Record<
forum: IconForum,
'open-external': IconOpenExternal,
'arrow-right': IconArrowRight,
'arrow-down': IconArrowDown,
'chevron-up': IconChevronUp,
'chevron-down': IconChevronDown,
'trend-up': IconTrendUp,
cross: IconCross,
kebab: IconKebab,
+1 -1
View File
@@ -44,7 +44,7 @@ export const ExternalLink = ({ children, className, ...props }: LinkProps) => (
<Link
className={classNames(
className,
'inline-flex items-baseline underline-offset-4'
'inline-flex gap-1 items-baseline underline-offset-4'
)}
target="_blank"
data-testid="external-link"
@@ -5,7 +5,7 @@ import { useContext } from 'react';
import { useRef } from 'react';
import { VegaLogo } from '../vega-logo';
import * as NavigationMenu from '@radix-ui/react-navigation-menu';
import { Icon } from '../icon';
import { VegaIcon, VegaIconNames } from '../icon';
import { Drawer } from '../drawer';
import { NavLink } from 'react-router-dom';
import type {
@@ -152,8 +152,8 @@ export const NavigationTrigger = ({
{...props}
>
<span>{children}</span>
<span className="rotate-90 group-data-open/drawer:hidden">
<Icon name="arrow-right" size={3} />
<span className="flex items-center group-data-open/drawer:hidden">
<VegaIcon name={VegaIconNames.ARROW_DOWN} />
</span>
<div
aria-hidden="true"
@@ -189,15 +189,15 @@ export const NavigationContent = ({
className={classNames(
'navigation-content',
'absolute z-20 top-12 w-max',
'p-2 mt-1',
'p-6 mt-1 min-w-[290px]',
'text-vega-light-300 dark:text-vega-dark-300',
'border rounded border-vega-light-200 dark:border-vega-dark-200',
'shadow-[8px_8px_16px_0_rgba(0,0,0,0.4)]',
'text-vega-light-300 dark:text-vega-light-300',
{
'bg-white dark:bg-black': theme === 'system' || theme === 'yellow',
'bg-white': theme === 'light',
'bg-black': theme === 'dark',
'bg-vega-light-100 dark:bg-vega-dark-100':
theme === 'system' || theme === 'yellow',
'bg-vega-light-100': theme === 'light',
'bg-vega-dark-100': theme === 'dark',
}
)}
>
@@ -348,7 +348,7 @@ export const Navigation = ({
<div
className={classNames(
'navbar',
'flex gap-4 h-12 items-center font-alpha text-lg calt',
'flex gap-4 h-12 items-center font-alpha text-lg',
{
'text-vega-light-300 dark:text-vega-dark-300': theme === 'system',
'text-vega-light-300': theme === 'light',
@@ -92,10 +92,7 @@ export const SparklineView = ({
return (
<svg
data-testid="sparkline-svg"
className={classNames(
'pt-px pr-0 w-full overflow-visible p-2',
className
)}
className={classNames('w-full', className)}
width={width}
height={height}
viewBox="0 0 100 100"
@@ -7,10 +7,11 @@ import { ToastPosition, useToastsConfiguration, useToasts } from './use-toasts';
import { useCallback } from 'react';
import { Intent } from '../../utils/intent';
const testToast = {
const TEST_TOAST = {
id: 'test-toast',
intent: Intent.Primary,
content: <>{t('This is an example of a toast notification')}</>,
onClose: () => useToasts.getState().remove('test-toast'),
};
export const ToastPositionSetter = () => {
@@ -20,7 +21,7 @@ export const ToastPositionSetter = () => {
const handleChange = useCallback(
(position: ToastPosition) => {
setPostion(position);
setToast(testToast);
setToast(TEST_TOAST);
},
[setToast, setPostion]
);
@@ -109,12 +109,12 @@ export enum ToastPosition {
BottomCenter,
}
type ToastConfiguration = {
type ToastsConfiguration = {
position: ToastPosition;
setPosition: (position: ToastPosition) => void;
};
export const useToastsConfiguration = create<ToastConfiguration>()(
export const useToastsConfiguration = create<ToastsConfiguration>()(
persist(
immer((set) => ({
position: ToastPosition.BottomRight,
-968
View File
@@ -1,968 +0,0 @@
const ethers = require('ethers');
const BigNumber = require('bignumber.js');
const uniq = require('lodash/uniq');
const fs = require('fs');
const MAX_ATTEMPTS = 5;
const CONFIG = [
{
env: 'mainnet',
contract: '0x23d1bfe8fa50a167816fbd79d7932577c06011f4',
provider: 'https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
startBlock: 12834524,
},
];
const vestingAbi = [
{
inputs: [
{
internalType: 'address',
name: 'token_v1_address',
type: 'address',
},
{
internalType: 'address',
name: 'token_v2_address',
type: 'address',
},
{
internalType: 'address[]',
name: 'old_addresses',
type: 'address[]',
},
{
internalType: 'address[]',
name: 'new_addresses',
type: 'address[]',
},
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'new_controller',
type: 'address',
},
],
name: 'Controller_Set',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'issuer',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'Issuer_Permitted',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'issuer',
type: 'address',
},
],
name: 'Issuer_Revoked',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'user',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
indexed: true,
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'Stake_Deposited',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'user',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
indexed: true,
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'Stake_Removed',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'from',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
indexed: true,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: true,
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'Stake_Transferred',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'user',
type: 'address',
},
{
indexed: true,
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'Tranche_Balance_Added',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'user',
type: 'address',
},
{
indexed: true,
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'Tranche_Balance_Removed',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
indexed: false,
internalType: 'uint256',
name: 'cliff_start',
type: 'uint256',
},
{
indexed: false,
internalType: 'uint256',
name: 'duration',
type: 'uint256',
},
],
name: 'Tranche_Created',
type: 'event',
},
{
inputs: [],
name: 'accuracy_scale',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'address_migration',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
internalType: 'address',
name: 'target',
type: 'address',
},
],
name: 'assisted_withdraw_from_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'controller',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'cliff_start',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'duration',
type: 'uint256',
},
],
name: 'create_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'default_tranche_id',
outputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
],
name: 'get_tranche_balance',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
],
name: 'get_vested_for_tranche',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'issue_into_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'move_into_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'issuer',
type: 'address',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'permit_issuer',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'permitted_issuance',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'remove_stake',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'issuer',
type: 'address',
},
],
name: 'revoke_issuer',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'new_controller',
type: 'address',
},
],
name: 'set_controller',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'target',
type: 'address',
},
{
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'stake_balance',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'stake_tokens',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'staking_token',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'total_locked',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'total_staked',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'tranche_count',
outputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
name: 'tranches',
outputs: [
{
internalType: 'uint256',
name: 'cliff_start',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'duration',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'user_stats',
outputs: [
{
internalType: 'uint256',
name: 'total_in_all_tranches',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lien',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
],
name: 'user_total_all_tranches',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'v1_address',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'v1_migrated',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'v2_address',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
],
name: 'withdraw_from_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
];
function addDecimal(value, decimals) {
return value.dividedBy(Math.pow(10, decimals)).decimalPlaces(decimals);
}
function createUserTransactions(events, decimals) {
return events.map((event) => {
return {
amount: addDecimal(
new BigNumber(event.args?.amount.toString()),
decimals
),
user: event.args?.user,
tranche_id: event.args?.tranche_id,
tx: event.transactionHash,
};
});
}
function getUsersInTranche(
balanceAddedEvents,
balanceRemovedEvents,
addresses,
decimals
) {
return addresses.map((address) => {
const userDeposits = balanceAddedEvents.filter(
(event) => event.args?.user === address
);
const userWithdraws = balanceRemovedEvents.filter(
(event) => event.args?.user === address
);
const deposits = createUserTransactions(userDeposits, decimals);
const withdrawals = createUserTransactions(userWithdraws, decimals);
const total_tokens = deposits.reduce(
(pre, cur) => pre.plus(cur.amount),
new BigNumber(0)
);
const withdrawn_tokens = withdrawals.reduce(
(pre, cur) => pre.plus(cur.amount),
new BigNumber(0)
);
const remaining_tokens = total_tokens.minus(withdrawn_tokens);
return {
address,
deposits,
withdrawals,
total_tokens,
withdrawn_tokens,
remaining_tokens,
};
});
}
function sumFromEvents(events, decimals) {
const amounts = events.map((e) =>
addDecimal(new BigNumber(e.args?.amount.toString()), decimals)
);
// Start with a 0 so if there are none there is no NaN
return BigNumber.sum.apply(null, [new BigNumber(0), ...amounts]);
}
function getLockedAmount(totalAdded, cliffStart, trancheDuration) {
let amount = new BigNumber(0);
const ts = Math.round(new Date().getTime() / 1000);
const tranche_progress = (ts - cliffStart) / trancheDuration;
if (tranche_progress < 0) {
amount = totalAdded;
} else if (tranche_progress < 1) {
amount = totalAdded.times(1 - tranche_progress);
}
return amount;
}
function createTransactions(events, decimals) {
return events.map((event) => {
return {
amount: addDecimal(
new BigNumber(event.args?.amount.toString()),
decimals
),
user: event.args?.user,
tx: event.transactionHash,
};
});
}
function getTranchesFromHistory(
createEvents,
addEvents,
removeEvents,
decimals
) {
return createEvents.map((event) => {
const tranche_id = event.args?.tranche_id;
const balanceAddedEvents = addEvents.filter(
(e) =>
e.event === 'Tranche_Balance_Added' && e.args?.tranche_id === tranche_id
);
const balanceRemovedEvents = removeEvents.filter(
(e) =>
e.event === 'Tranche_Balance_Removed' &&
e.args?.tranche_id === tranche_id
);
//get tranche start and end dates
const tranche_duration = event.args?.duration;
const cliff_start = event.args?.cliff_start;
const tranche_start = new Date(cliff_start.mul(1000).toNumber());
const tranche_end = new Date(
cliff_start.add(tranche_duration).mul(1000).toNumber()
);
// get added and removed values
const total_added = sumFromEvents(balanceAddedEvents, decimals);
const total_removed = sumFromEvents(balanceRemovedEvents, decimals);
// get locked amount
const locked_amount = getLockedAmount(
total_added,
cliff_start,
tranche_duration
);
// get all deposits and withdrawals
const deposits = createTransactions(balanceAddedEvents, decimals);
const withdrawals = createTransactions(balanceRemovedEvents, decimals);
// get all users
const uniqueAddresses = uniq(
balanceAddedEvents.map((event) => event.args?.user)
);
const users = getUsersInTranche(
balanceAddedEvents,
balanceRemovedEvents,
uniqueAddresses,
decimals
);
return {
tranche_id: parseInt(tranche_id),
tranche_start,
tranche_end,
total_added,
total_removed,
locked_amount,
deposits,
withdrawals,
users,
};
});
}
const getBatched = async (filter, contract, provider, startblock) => {
const batchSize = 100000;
const currentBlockNumber = await provider.getBlockNumber();
const batches = Math.ceil(currentBlockNumber / batchSize);
const startIndex = Math.floor(startblock / batchSize);
console.log(
`Processing batches of size ${batches} starting at batch ${startIndex}`
);
let res = [];
for (let index = startIndex; index < batches; index++) {
let events = [];
let attempts = 1;
// Sometimes the queries fail for timeout even on small batch sizes. Some basic retry logic.
while (attempts < MAX_ATTEMPTS) {
try {
events = await contract.queryFilter(
filter,
index * batchSize,
(index + 1) * batchSize - 1
);
break;
} catch {
console.log('retry batch', index);
++attempts;
if (attempts >= MAX_ATTEMPTS) {
throw new Error('Could not get in 4 attempts');
}
}
}
res = [...events, ...res];
console.log(
`Processed blocks ${index * batchSize} to ${
(index + 1) * batchSize - 1
} as part of batch ${index}`
);
}
return res;
};
const run = async (c) => {
const provider = new ethers.providers.JsonRpcProvider({
url: c.provider,
timeout: 1000 * 60 * 1000,
});
const contract = new ethers.Contract(c.contract, vestingAbi, provider);
const [created, added, removed] = await Promise.all([
getBatched(
contract.filters.Tranche_Created(),
contract,
provider,
c.startBlock
),
getBatched(
contract.filters.Tranche_Balance_Added(),
contract,
provider,
c.startBlock
),
getBatched(
contract.filters.Tranche_Balance_Removed(),
contract,
provider,
c.startBlock
),
]);
fs.writeFileSync(
`./apps/static/src/assets/${c.env}-tranches.json`,
JSON.stringify(
getTranchesFromHistory(created, added, removed, 18),
null,
2
),
{ encoding: 'UTF-8' }
);
};
CONFIG.forEach((c) => run(c));