Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18f8d5e648 | ||
|
|
261f32aa5b | ||
|
|
053775bef6 | ||
|
|
0660eda334 | ||
|
|
f22a3bc2d2 | ||
|
|
fde77ebccb | ||
|
|
e309669736 | ||
|
|
b1621d1191 | ||
|
|
39907f07db | ||
|
|
3dab5f3d9b | ||
|
|
3cf9ae7582 | ||
|
|
557894e2ef | ||
|
|
baf9875c69 | ||
|
|
51199b02ce | ||
|
|
bb826c88f0 | ||
|
|
0da20b750f | ||
|
|
e16c447564 | ||
|
|
a2170d27d6 | ||
|
|
00d840b7b3 | ||
|
|
2a4a05630f | ||
|
|
97f1f40f2c | ||
|
|
a8e6963521 | ||
|
|
a2bffa1dfd | ||
|
|
9253e8067a | ||
|
|
58972f0a11 | ||
|
|
a3c55fd7c3 | ||
|
|
c63cba1071 | ||
|
|
bc9d87fe30 | ||
|
|
6a21862378 | ||
|
|
0fb4348e9c | ||
|
|
b751bcf17d | ||
|
|
d32f27fcb1 | ||
|
|
d05cd6a2ed | ||
|
|
c003e5fa30 | ||
|
|
f62d3289ab | ||
|
|
a3d3d18c5c | ||
|
|
77ca101781 | ||
|
|
d238c37d0c | ||
|
|
79feb485f6 | ||
|
|
6aa5c3b6e3 | ||
|
|
82abc13fda | ||
|
|
933f07cddf | ||
|
|
c8067669c2 | ||
|
|
78f5a9c520 |
@@ -19,7 +19,7 @@ jobs:
|
|||||||
create-docker-image:
|
create-docker-image:
|
||||||
name: Create docker image for console-test
|
name: Create docker image for console-test
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
timeout-minutes: 20
|
timeout-minutes: 45
|
||||||
steps:
|
steps:
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
# check-out frontend-monorepo
|
# check-out frontend-monorepo
|
||||||
@@ -138,7 +138,7 @@ jobs:
|
|||||||
name: run-tests
|
name: run-tests
|
||||||
runs-on: 8-cores
|
runs-on: 8-cores
|
||||||
needs: [create-docker-image, console-test-branch]
|
needs: [create-docker-image, console-test-branch]
|
||||||
timeout-minutes: 20
|
timeout-minutes: 45
|
||||||
steps:
|
steps:
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
# load docker image
|
# load docker image
|
||||||
@@ -205,7 +205,7 @@ jobs:
|
|||||||
# run tests
|
# run tests
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
|
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 1 --dist loadfile --durations=45
|
||||||
working-directory: apps/trading/e2e
|
working-directory: apps/trading/e2e
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
# upload traces
|
# upload traces
|
||||||
@@ -227,3 +227,46 @@ jobs:
|
|||||||
name: worker-logs
|
name: worker-logs
|
||||||
path: ./logs/
|
path: ./logs/
|
||||||
retention-days: 15
|
retention-days: 15
|
||||||
|
#----------------------------------------------
|
||||||
|
# ----- upload market-sim logs -----
|
||||||
|
#----------------------------------------------
|
||||||
|
- name: Prepare and Zip market-sim-logs
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
parent_dir="/tmp/market-sim-logs"
|
||||||
|
echo "Creating parent directory at $parent_dir"
|
||||||
|
mkdir -p "$parent_dir"
|
||||||
|
|
||||||
|
echo "Waiting for vega-sim-* folders to be created..."
|
||||||
|
sleep 10 # Waits 10 seconds to ensure all folders are created
|
||||||
|
|
||||||
|
echo "Before searching for vega-sim-* folders in /tmp..."
|
||||||
|
folders=$(find /tmp -mindepth 1 -type d -name 'vega-sim-*' -print) || echo "Find command failed with exit code $?"
|
||||||
|
|
||||||
|
echo "After searching for vega-sim-* folders in /tmp..."
|
||||||
|
|
||||||
|
if [ -z "$folders" ]; then
|
||||||
|
echo "No vega-sim-* folders found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Moving vega-sim-* folders to $parent_dir"
|
||||||
|
echo "$folders" | xargs -I {} mv {} "$parent_dir/"
|
||||||
|
|
||||||
|
echo "Checking if $parent_dir is not empty..."
|
||||||
|
if [ "$(ls -A $parent_dir)" ]; then
|
||||||
|
echo "Zipping the parent directory..."
|
||||||
|
zip -r market-sim-logs.zip "$parent_dir" && echo "Zip file created successfully."
|
||||||
|
else
|
||||||
|
echo "$parent_dir is empty. No zip file created."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
shell: /usr/bin/bash -e {0}
|
||||||
|
|
||||||
|
- name: Upload market-sim-logs
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: market-sim-logs
|
||||||
|
path: market-sim-logs.zip
|
||||||
|
retention-days: 15
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
|||||||
cy.getByTestId('icon-cross').click();
|
cy.getByTestId('icon-cross').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('Proposal page displayed on mobile', function () {
|
it('Proposal page displayed on mobile', function () {
|
||||||
const proposalTitle = 'Add Lorem Ipsum market';
|
const proposalTitle = 'Add Lorem Ipsum market';
|
||||||
|
|
||||||
cy.common_switch_to_mobile_and_click_toggle();
|
cy.common_switch_to_mobile_and_click_toggle();
|
||||||
@@ -55,7 +55,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Able to view new asset proposal', function () {
|
it.skip('Able to view new asset proposal', function () {
|
||||||
const proposalTitle = 'Test new asset proposal';
|
const proposalTitle = 'Test new asset proposal';
|
||||||
const newAssetProposalBody = getNewAssetTxBody();
|
const newAssetProposalBody = getNewAssetTxBody();
|
||||||
cy.VegaWalletSubmitProposal(newAssetProposalBody);
|
cy.VegaWalletSubmitProposal(newAssetProposalBody);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="whitespace-nowrap">
|
<span>
|
||||||
{useName && <Icon size={4} name="cube" className="mr-2" />}
|
{useName && <Icon size={4} name="cube" className="mr-2" />}
|
||||||
<Link
|
<Link
|
||||||
className="underline font-mono"
|
className="underline font-mono"
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { render, waitFor } from '@testing-library/react';
|
import { render, waitFor } from '@testing-library/react';
|
||||||
import {
|
import { NestedDataList, sortNestedDataByChildren } from './nested-data-list';
|
||||||
BORDER_COLOURS,
|
|
||||||
NestedDataList,
|
|
||||||
sortNestedDataByChildren,
|
|
||||||
} from './nested-data-list';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
const mockData = {
|
const mockData = {
|
||||||
@@ -61,38 +57,6 @@ describe('NestedDataList', () => {
|
|||||||
expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2');
|
expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should repeat the border colours in the correct order', () => {
|
|
||||||
const colourMockData = {
|
|
||||||
t0: {
|
|
||||||
t1: {
|
|
||||||
t2: {
|
|
||||||
t3: {
|
|
||||||
t4: {
|
|
||||||
t5: {
|
|
||||||
t6: {
|
|
||||||
t7: {
|
|
||||||
t8: {
|
|
||||||
hello: 'world',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const tree = render(<NestedDataList data={colourMockData} />);
|
|
||||||
const { getByTestId } = tree;
|
|
||||||
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
const item = getByTestId(`T${i}`);
|
|
||||||
const expected = BORDER_COLOURS.light[i % 5];
|
|
||||||
expect(item.style.borderColor.toUpperCase()).toBe(expected);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sort the data by values with children', () => {
|
it('should sort the data by values with children', () => {
|
||||||
const mockData = {
|
const mockData = {
|
||||||
nonce: '5980890939790185837',
|
nonce: '5980890939790185837',
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const DeterministicOrderDetails = ({
|
|||||||
const o = data.orderByID;
|
const o = data.orderByID;
|
||||||
return (
|
return (
|
||||||
<div className={wrapperClasses}>
|
<div className={wrapperClasses}>
|
||||||
<div className="mb-12 lg:mb-0">
|
<div className="mb-0">
|
||||||
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
|
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
|
||||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||||
<abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2">
|
<abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2">
|
||||||
@@ -89,9 +89,9 @@ const DeterministicOrderDetails = ({
|
|||||||
<span>{t('Reference')}</span>: {o.reference}
|
<span>{t('Reference')}</span>: {o.reference}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="grid md:grid-cols-5 gap-x-6 mt-4">
|
<div className="grid grid-cols-2 md:grid-cols-5 gap-x-6 mt-4">
|
||||||
<div className="mb-12 md:mb-0">
|
<div className="mb-6 md:mb-0">
|
||||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||||
{t('Status')}
|
{t('Status')}
|
||||||
</h2>
|
</h2>
|
||||||
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
|
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
|
||||||
@@ -99,15 +99,17 @@ const DeterministicOrderDetails = ({
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-12 md:mb-0">
|
<div className="mb-6 md:mb-0">
|
||||||
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
|
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||||
|
{t('Size')}
|
||||||
|
</h2>
|
||||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||||
<SizeInMarket size={o.size} marketId={o.market.id} />
|
<SizeInMarket size={o.size} marketId={o.market.id} />
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="">
|
<div className="mb-6 md:mb-0">
|
||||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||||
{t('Version')}
|
{t('Version')}
|
||||||
</h2>
|
</h2>
|
||||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||||
@@ -115,8 +117,8 @@ const DeterministicOrderDetails = ({
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
{o.type ? (
|
{o.type ? (
|
||||||
<div className="">
|
<div className="mb-6 md:mb-0">
|
||||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||||
{t('Type')}
|
{t('Type')}
|
||||||
</h2>
|
</h2>
|
||||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ export const Signature = ({ signature }: SignatureProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inline-flex border rounded signature-component relative pr-[20px]">
|
<div className="inline-flex border rounded signature-component relative pr-[20px]">
|
||||||
<span
|
<div
|
||||||
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
|
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
|
||||||
title={`Version ${signature.version}`}
|
title={`${signature.algo}`}
|
||||||
>
|
>
|
||||||
{signature.algo}
|
<span>v{signature.version}</span>
|
||||||
</span>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
isOpen
|
isOpen
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type ExplorerReferralCodeOwnerQueryVariables = Types.Exact<{
|
||||||
|
id: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ExplorerReferralCodeOwnerQuery = { __typename?: 'Query', referralSets: { __typename?: 'ReferralSetConnection', edges: Array<{ __typename?: 'ReferralSetEdge', node: { __typename?: 'ReferralSet', createdAt: any, updatedAt: any, referrer: string } } | null> } };
|
||||||
|
|
||||||
|
|
||||||
|
export const ExplorerReferralCodeOwnerDocument = gql`
|
||||||
|
query ExplorerReferralCodeOwner($id: ID!) {
|
||||||
|
referralSets(id: $id) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
referrer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useExplorerReferralCodeOwnerQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useExplorerReferralCodeOwnerQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useExplorerReferralCodeOwnerQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useExplorerReferralCodeOwnerQuery({
|
||||||
|
* variables: {
|
||||||
|
* id: // value for 'id'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useExplorerReferralCodeOwnerQuery(baseOptions: Apollo.QueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
|
||||||
|
}
|
||||||
|
export function useExplorerReferralCodeOwnerLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
|
||||||
|
}
|
||||||
|
export type ExplorerReferralCodeOwnerQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerQuery>;
|
||||||
|
export type ExplorerReferralCodeOwnerLazyQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerLazyQuery>;
|
||||||
|
export type ExplorerReferralCodeOwnerQueryResult = Apollo.QueryResult<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
query ExplorerReferralCodeOwner($id: ID!) {
|
||||||
|
referralSets(id: $id) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
referrer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { render, waitFor } from '@testing-library/react';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { ReferralCodeOwner } from './referral-code-owner';
|
||||||
|
import type { ReferralCodeOwnerProps } from './referral-code-owner';
|
||||||
|
import { ExplorerReferralCodeOwnerDocument } from './__generated__/code-owner';
|
||||||
|
const renderComponent = (
|
||||||
|
props: ReferralCodeOwnerProps,
|
||||||
|
mocks: MockedResponse[]
|
||||||
|
) => {
|
||||||
|
return render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<MemoryRouter>
|
||||||
|
<ReferralCodeOwner {...props} />
|
||||||
|
</MemoryRouter>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ReferralCodeOwner', () => {
|
||||||
|
it('should render loading state', () => {
|
||||||
|
const mocks = [
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
query: ExplorerReferralCodeOwnerDocument,
|
||||||
|
variables: {
|
||||||
|
id: 'ABC123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||||
|
|
||||||
|
expect(getByText('Loading...')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render error state', async () => {
|
||||||
|
const errorMessage = 'Error fetching referrer: ABC123';
|
||||||
|
const mocks = [
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
query: ExplorerReferralCodeOwnerDocument,
|
||||||
|
variables: {
|
||||||
|
id: 'ABC123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
error: new Error('nope'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(getByText(errorMessage)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render link to referring party', async () => {
|
||||||
|
const referrerId = 'DEF789';
|
||||||
|
|
||||||
|
const mocks = [
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
query: ExplorerReferralCodeOwnerDocument,
|
||||||
|
variables: {
|
||||||
|
id: 'ABC123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
referralSets: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
__typename: 'ReferralSet',
|
||||||
|
referrer: referrerId,
|
||||||
|
createdAt: '123',
|
||||||
|
updatedAt: '456',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(getByText(referrerId)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { TableCell } from '../../../table';
|
||||||
|
import { useExplorerReferralCodeOwnerQuery } from './__generated__/code-owner';
|
||||||
|
import { PartyLink } from '../../../links';
|
||||||
|
|
||||||
|
export interface ReferralCodeOwnerProps {
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the owner of a referral code
|
||||||
|
*/
|
||||||
|
export const ReferralCodeOwner = ({ code }: ReferralCodeOwnerProps) => {
|
||||||
|
const { data, error, loading } = useExplorerReferralCodeOwnerQuery({
|
||||||
|
variables: {
|
||||||
|
id: code,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const referrer = data?.referralSets.edges[0]?.node.referrer || '';
|
||||||
|
return (
|
||||||
|
<TableCell>
|
||||||
|
{loading && 'Loading...'}
|
||||||
|
{error && `Error fetching referrer: ${code}`}
|
||||||
|
{referrer.length > 0 && <PartyLink id={referrer} />}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { ReferralTeam } from './team';
|
||||||
|
import type { CreateReferralSet } from './team';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
|
||||||
|
describe('ReferralTeam', () => {
|
||||||
|
const team = {
|
||||||
|
name: 'Test Team',
|
||||||
|
teamUrl: 'https://example.com/team',
|
||||||
|
avatarUrl: 'https://example.com/avatar',
|
||||||
|
closed: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTx: CreateReferralSet = {
|
||||||
|
team,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockId = '123456';
|
||||||
|
const mockCreator = 'JohnDoe';
|
||||||
|
|
||||||
|
it('should render the team name', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(getByText('Test Team')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the team ID', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(getByText('Id')).toBeInTheDocument();
|
||||||
|
expect(getByText(mockId)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the creator', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(getByText('Creator')).toBeInTheDocument();
|
||||||
|
expect(getByText(mockCreator)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the team URL', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(getByText('Team URL')).toBeInTheDocument();
|
||||||
|
expect(getByText(team.teamUrl)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the avatar URL', () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(getByText('Avatar')).toBeInTheDocument();
|
||||||
|
expect(getByText(team.avatarUrl)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the open status as a tick if closed is falsy', () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(getByTestId('open-yes')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the open status as a cross if it is truthy', () => {
|
||||||
|
const m = {
|
||||||
|
team: {
|
||||||
|
closed: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<ReferralTeam tx={m} id={mockId} creator={mockCreator} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(getByTestId('open-no')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
VegaIcon,
|
||||||
|
Icon,
|
||||||
|
KeyValueTable,
|
||||||
|
KeyValueTableRow,
|
||||||
|
VegaIconNames,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { components } from '../../../../../types/explorer';
|
||||||
|
import Hash from '../../../links/hash';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { PartyLink } from '../../../links';
|
||||||
|
|
||||||
|
export type CreateReferralSet = components['schemas']['v1CreateReferralSet'];
|
||||||
|
export type ReferralTeam = CreateReferralSet['team'];
|
||||||
|
|
||||||
|
export interface ReferralTeamProps {
|
||||||
|
tx: CreateReferralSet;
|
||||||
|
id: string;
|
||||||
|
creator: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the details for a team in a CreateReferralSet or UpdateReferralSet transaction.
|
||||||
|
*
|
||||||
|
* Intentionally does not render the avatar image or link to the team url.
|
||||||
|
*/
|
||||||
|
export const ReferralTeam = ({ tx, id, creator }: ReferralTeamProps) => {
|
||||||
|
if (!tx.team) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<div className="inline-block mr-2 leading-none">
|
||||||
|
<VegaIcon name={VegaIconNames.TEAM} />
|
||||||
|
</div>
|
||||||
|
{tx.team.name && (
|
||||||
|
<h3 className="inline-block leading-loose">{tx.team.name}</h3>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="min-w-fit max-w-2xl block">
|
||||||
|
<KeyValueTable>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('Id')}
|
||||||
|
<Hash text={id} truncate={false} />
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('Creator')}
|
||||||
|
<PartyLink id={creator} truncate={false} />
|
||||||
|
</KeyValueTableRow>
|
||||||
|
{tx.team.teamUrl && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('Team URL')}
|
||||||
|
<Hash text={tx.team.teamUrl} truncate={false} />
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{tx.team.avatarUrl && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('Avatar')}
|
||||||
|
<Hash text={tx.team.avatarUrl} truncate={false} />
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('Open')}
|
||||||
|
<span data-testid={!tx.team.closed ? 'open-yes' : 'open-no'}>
|
||||||
|
{!tx.team.closed ? <Icon name="tick" /> : <Icon name="cross" />}
|
||||||
|
</span>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -27,7 +27,8 @@ export const sharedHeaderProps = {
|
|||||||
className: 'align-top',
|
className: 'align-top',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Labels: Record<BlockExplorerTransactionResult['type'], string> = {
|
// The incoming type field is usually the right thing to show. Exceptions are listed here
|
||||||
|
const LabelOverrides: Record<BlockExplorerTransactionResult['type'], string> = {
|
||||||
'Stop Orders Submission': 'Stop Order',
|
'Stop Orders Submission': 'Stop Order',
|
||||||
'Stop Orders Cancellation': 'Cancel Stop Order',
|
'Stop Orders Cancellation': 'Cancel Stop Order',
|
||||||
};
|
};
|
||||||
@@ -50,7 +51,7 @@ export const TxDetailsShared = ({
|
|||||||
const time: string = blockData?.result.block.header.time || '';
|
const time: string = blockData?.result.block.header.time || '';
|
||||||
const height: string = blockData?.result.block.header.height || txData.block;
|
const height: string = blockData?.result.block.header.height || txData.block;
|
||||||
|
|
||||||
const type = Labels[txData.type] || txData.type;
|
const type = LabelOverrides[txData.type] || txData.type;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
|
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||||
|
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||||
|
import { ReferralCodeOwner } from './referrals/referral-code-owner';
|
||||||
|
|
||||||
|
interface TxDetailsApplyReferralCodeProps {
|
||||||
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
|
pubKey: string | undefined;
|
||||||
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||||
|
*/
|
||||||
|
export const TxDetailsApplyReferralCode = ({
|
||||||
|
txData,
|
||||||
|
pubKey,
|
||||||
|
blockData,
|
||||||
|
}: TxDetailsApplyReferralCodeProps) => {
|
||||||
|
if (!txData || !txData.command.applyReferralCode || !txData.signature) {
|
||||||
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const referralCode = txData.command.applyReferralCode.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||||
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
/>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Applied Code')}</TableCell>
|
||||||
|
<TableCell>{referralCode}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Referrer')}</TableCell>
|
||||||
|
<ReferralCodeOwner code={referralCode} />
|
||||||
|
</TableRow>
|
||||||
|
</TableWithTbody>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
|
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||||
|
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||||
|
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||||
|
import { ReferralTeam } from './referrals/team';
|
||||||
|
|
||||||
|
interface TxDetailsCreateReferralProps {
|
||||||
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
|
pubKey: string | undefined;
|
||||||
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||||
|
*/
|
||||||
|
export const TxDetailsCreateReferralSet = ({
|
||||||
|
txData,
|
||||||
|
pubKey,
|
||||||
|
blockData,
|
||||||
|
}: TxDetailsCreateReferralProps) => {
|
||||||
|
if (!txData || !txData.command.createReferralSet || !txData.signature) {
|
||||||
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = txSignatureToDeterministicId(txData.signature.value);
|
||||||
|
|
||||||
|
const isTeam = txData.command.createReferralSet.isTeam || false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||||
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
/>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
|
||||||
|
<TableCell>{id}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableWithTbody>
|
||||||
|
|
||||||
|
<ReferralTeam
|
||||||
|
tx={txData.command.createReferralSet}
|
||||||
|
id={id}
|
||||||
|
creator={txData.submitter}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -28,6 +28,10 @@ import { TxProposal } from './tx-proposal';
|
|||||||
import { TxDetailsTransfer } from './tx-transfer';
|
import { TxDetailsTransfer } from './tx-transfer';
|
||||||
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
|
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
|
||||||
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
|
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
|
||||||
|
import { TxDetailsCreateReferralSet } from './tx-create-referral-set';
|
||||||
|
import { TxDetailsApplyReferralCode } from './tx-apply-referral-code';
|
||||||
|
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
|
||||||
|
import { TxDetailsJoinTeam } from './tx-join-team';
|
||||||
|
|
||||||
interface TxDetailsWrapperProps {
|
interface TxDetailsWrapperProps {
|
||||||
txData: BlockExplorerTransactionResult | undefined;
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
@@ -121,6 +125,14 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
|||||||
return TxDetailsStopOrderSubmission;
|
return TxDetailsStopOrderSubmission;
|
||||||
case 'Transfer Funds':
|
case 'Transfer Funds':
|
||||||
return TxDetailsTransfer;
|
return TxDetailsTransfer;
|
||||||
|
case 'Create Referral Set':
|
||||||
|
return TxDetailsCreateReferralSet;
|
||||||
|
case 'Update Referral Set':
|
||||||
|
return TxDetailsUpdateReferralSet;
|
||||||
|
case 'Apply Referral Code':
|
||||||
|
return TxDetailsApplyReferralCode;
|
||||||
|
case 'Join Team':
|
||||||
|
return TxDetailsJoinTeam;
|
||||||
default:
|
default:
|
||||||
return TxDetailsGeneric;
|
return TxDetailsGeneric;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
|
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||||
|
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||||
|
import { ReferralCodeOwner } from './referrals/referral-code-owner';
|
||||||
|
|
||||||
|
interface TxDetailsJoinTeamProps {
|
||||||
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
|
pubKey: string | undefined;
|
||||||
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||||
|
*/
|
||||||
|
export const TxDetailsJoinTeam = ({
|
||||||
|
txData,
|
||||||
|
pubKey,
|
||||||
|
blockData,
|
||||||
|
}: TxDetailsJoinTeamProps) => {
|
||||||
|
if (!txData || !txData.command.joinTeam || !txData.signature) {
|
||||||
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const team = txData.command.joinTeam.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||||
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
/>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Team')}</TableCell>
|
||||||
|
<TableCell>{team}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Referrer')}</TableCell>
|
||||||
|
<ReferralCodeOwner code={team} />
|
||||||
|
</TableRow>
|
||||||
|
</TableWithTbody>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
|
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||||
|
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||||
|
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||||
|
import { ReferralTeam } from './referrals/team';
|
||||||
|
|
||||||
|
interface TxDetailsUpdateReferralProps {
|
||||||
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
|
pubKey: string | undefined;
|
||||||
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A copy of create referral set, effectively.
|
||||||
|
* Updating a referral set without a team doesn't make sense,
|
||||||
|
* but is valid, so this component ignores sense.
|
||||||
|
*/
|
||||||
|
export const TxDetailsUpdateReferralSet = ({
|
||||||
|
txData,
|
||||||
|
pubKey,
|
||||||
|
blockData,
|
||||||
|
}: TxDetailsUpdateReferralProps) => {
|
||||||
|
if (!txData || !txData.command.updateReferralSet || !txData.signature) {
|
||||||
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = txSignatureToDeterministicId(txData.signature.value);
|
||||||
|
|
||||||
|
const isTeam = txData.command.updateReferralSet.isTeam || false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||||
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
/>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
|
||||||
|
<TableCell>{id}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableWithTbody>
|
||||||
|
|
||||||
|
<ReferralTeam
|
||||||
|
tx={txData.command.updateReferralSet}
|
||||||
|
id={id}
|
||||||
|
creator={txData.submitter}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -28,6 +28,7 @@ export type FilterOption =
|
|||||||
| 'Delegate'
|
| 'Delegate'
|
||||||
| 'Ethereum Key Rotate Submission'
|
| 'Ethereum Key Rotate Submission'
|
||||||
| 'Issue Signatures'
|
| 'Issue Signatures'
|
||||||
|
| 'Join Team'
|
||||||
| 'Key Rotate Submission'
|
| 'Key Rotate Submission'
|
||||||
| 'Liquidity Provision Order'
|
| 'Liquidity Provision Order'
|
||||||
| 'Node Signature'
|
| 'Node Signature'
|
||||||
@@ -47,45 +48,46 @@ export type FilterOption =
|
|||||||
| 'Vote on Proposal'
|
| 'Vote on Proposal'
|
||||||
| 'Withdraw';
|
| 'Withdraw';
|
||||||
|
|
||||||
// Alphabetised list of transaction types to appear at the top level
|
export const filterOptions: Record<string, FilterOption[]> = {
|
||||||
export const PrimaryFilterOptions: FilterOption[] = [
|
'Market Instructions': [
|
||||||
'Amend LiquidityProvision Order',
|
'Amend LiquidityProvision Order',
|
||||||
'Amend Order',
|
'Amend Order',
|
||||||
'Batch Market Instructions',
|
'Batch Market Instructions',
|
||||||
'Cancel LiquidityProvision Order',
|
'Cancel LiquidityProvision Order',
|
||||||
'Cancel Order',
|
'Cancel Order',
|
||||||
'Cancel Transfer Funds',
|
'Liquidity Provision Order',
|
||||||
'Delegate',
|
'Stop Orders Submission',
|
||||||
'Liquidity Provision Order',
|
'Stop Orders Cancellation',
|
||||||
'Proposal',
|
'Submit Order',
|
||||||
'Stop Orders Submission',
|
],
|
||||||
'Stop Orders Cancellation',
|
'Transfers and Withdrawals': [
|
||||||
'Submit Oracle Data',
|
'Transfer Funds',
|
||||||
'Submit Order',
|
'Cancel Transfer Funds',
|
||||||
'Transfer Funds',
|
'Withdraw',
|
||||||
'Undelegate',
|
],
|
||||||
'Vote on Proposal',
|
Governance: ['Delegate', 'Undelegate', 'Vote on Proposal', 'Proposal'],
|
||||||
'Withdraw',
|
Referrals: [
|
||||||
];
|
'Apply Referral Code',
|
||||||
|
'Create Referral Set',
|
||||||
|
'Join Team',
|
||||||
|
'Update Referral Set',
|
||||||
|
],
|
||||||
|
'External Data': ['Chain Event', 'Submit Oracle Data'],
|
||||||
|
Validators: [
|
||||||
|
'Ethereum Key Rotate Submission',
|
||||||
|
'Issue Signatures',
|
||||||
|
'Key Rotate Submission',
|
||||||
|
'Node Signature',
|
||||||
|
'Node Vote',
|
||||||
|
'Protocol Upgrade',
|
||||||
|
'Register new Node',
|
||||||
|
'State Variable Proposal',
|
||||||
|
'Validator Heartbeat',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
// Alphabetised list of transaction types to nest under a 'More...' submenu
|
export const AllFilterOptions: FilterOption[] =
|
||||||
export const SecondaryFilterOptions: FilterOption[] = [
|
Object.values(filterOptions).flat();
|
||||||
'Chain Event',
|
|
||||||
'Ethereum Key Rotate Submission',
|
|
||||||
'Issue Signatures',
|
|
||||||
'Key Rotate Submission',
|
|
||||||
'Node Signature',
|
|
||||||
'Node Vote',
|
|
||||||
'Protocol Upgrade',
|
|
||||||
'Register new Node',
|
|
||||||
'State Variable Proposal',
|
|
||||||
'Validator Heartbeat',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const AllFilterOptions: FilterOption[] = [
|
|
||||||
...PrimaryFilterOptions,
|
|
||||||
...SecondaryFilterOptions,
|
|
||||||
];
|
|
||||||
|
|
||||||
export interface TxFilterProps {
|
export interface TxFilterProps {
|
||||||
filters: Set<FilterOption>;
|
filters: Set<FilterOption>;
|
||||||
@@ -122,46 +124,33 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{PrimaryFilterOptions.map((f) => (
|
|
||||||
<DropdownMenuCheckboxItem
|
{Object.entries(filterOptions).map(([key, value]) => (
|
||||||
key={f}
|
<DropdownMenuSub key={key}>
|
||||||
checked={filters.has(f)}
|
<DropdownMenuSubTrigger>
|
||||||
onCheckedChange={() => {
|
{t(key)}
|
||||||
// NOTE: These act like radio buttons until the API supports multiple filters
|
<Icon name="chevron-right" />
|
||||||
setFilters(new Set([f]));
|
</DropdownMenuSubTrigger>
|
||||||
}}
|
<DropdownMenuSubContent>
|
||||||
id={`radio-${f}`}
|
{value.map((f) => (
|
||||||
>
|
<DropdownMenuCheckboxItem
|
||||||
{f}
|
key={f}
|
||||||
<DropdownMenuItemIndicator>
|
checked={filters.has(f)}
|
||||||
<Icon name="tick-circle" />
|
onCheckedChange={(checked) => {
|
||||||
</DropdownMenuItemIndicator>
|
// NOTE: These act like radio buttons until the API supports multiple filters
|
||||||
</DropdownMenuCheckboxItem>
|
setFilters(new Set([f]));
|
||||||
|
}}
|
||||||
|
id={`radio-${f}`}
|
||||||
|
>
|
||||||
|
{f}
|
||||||
|
<DropdownMenuItemIndicator>
|
||||||
|
<Icon name="tick-circle" className="inline" />
|
||||||
|
</DropdownMenuItemIndicator>
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuSubContent>
|
||||||
|
</DropdownMenuSub>
|
||||||
))}
|
))}
|
||||||
<DropdownMenuSub>
|
|
||||||
<DropdownMenuSubTrigger>
|
|
||||||
{t('More Types')}
|
|
||||||
<Icon name="chevron-right" />
|
|
||||||
</DropdownMenuSubTrigger>
|
|
||||||
<DropdownMenuSubContent>
|
|
||||||
{SecondaryFilterOptions.map((f) => (
|
|
||||||
<DropdownMenuCheckboxItem
|
|
||||||
key={f}
|
|
||||||
checked={filters.has(f)}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
// NOTE: These act like radio buttons until the API supports multiple filters
|
|
||||||
setFilters(new Set([f]));
|
|
||||||
}}
|
|
||||||
id={`radio-${f}`}
|
|
||||||
>
|
|
||||||
{f}
|
|
||||||
<DropdownMenuItemIndicator>
|
|
||||||
<Icon name="tick-circle" className="inline" />
|
|
||||||
</DropdownMenuItemIndicator>
|
|
||||||
</DropdownMenuCheckboxItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuSubContent>
|
|
||||||
</DropdownMenuSub>
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ const displayString: StringMap = {
|
|||||||
StopOrdersSubmission: 'Stop',
|
StopOrdersSubmission: 'Stop',
|
||||||
StopOrdersCancellation: 'Cancel stop',
|
StopOrdersCancellation: 'Cancel stop',
|
||||||
'Stop Orders Cancellation': 'Cancel stop',
|
'Stop Orders Cancellation': 'Cancel stop',
|
||||||
|
'Apply Referral Code': 'Referral',
|
||||||
|
'Create Referral Set': 'Create referral',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getLabelForStopOrderType(
|
export function getLabelForStopOrderType(
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const Layout = () => {
|
|||||||
<ProtocolUpgradeInProgressNotification />
|
<ProtocolUpgradeInProgressNotification />
|
||||||
</div>
|
</div>
|
||||||
<div className={fixedWidthClasses}>
|
<div className={fixedWidthClasses}>
|
||||||
<main className="p-4">
|
<main className="md:p-4">
|
||||||
{!isHome && <BreadcrumbsContainer className="mb-4" />}
|
{!isHome && <BreadcrumbsContainer className="mb-4" />}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ const Tx = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data || !data?.transaction) {
|
||||||
|
errorMessage = 'Transaction not found';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
@@ -49,7 +53,7 @@ const Tx = () => {
|
|||||||
<TxDetails
|
<TxDetails
|
||||||
className="mb-28"
|
className="mb-28"
|
||||||
txData={data?.transaction}
|
txData={data?.transaction}
|
||||||
pubKey={data?.transaction.submitter}
|
pubKey={data?.transaction?.submitter}
|
||||||
/>
|
/>
|
||||||
</RenderFetched>
|
</RenderFetched>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ interface TxDetailsProps {
|
|||||||
export const txDetailsTruncateLength = 30;
|
export const txDetailsTruncateLength = 30;
|
||||||
|
|
||||||
export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
|
export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
|
||||||
if (!txData) {
|
if (!txData || !pubKey) {
|
||||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
return <>{t('Transaction could not be found')}</>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<section className="mb-10" key={txData.hash}>
|
<section className="mb-10" key={txData.hash}>
|
||||||
|
|||||||
+2
-2
@@ -8,13 +8,13 @@ type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
|
|||||||
type XOR<T, U> = T | U extends object
|
type XOR<T, U> = T | U extends object
|
||||||
? (Without<T, U> & U) | (Without<U, T> & T)
|
? (Without<T, U> & U) | (Without<U, T> & T)
|
||||||
: T | U;
|
: T | U;
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
type OneOf<T extends any[]> = T extends [infer Only]
|
type OneOf<T extends any[]> = T extends [infer Only]
|
||||||
? Only
|
? Only
|
||||||
: T extends [infer A, infer B, ...infer Rest]
|
: T extends [infer A, infer B, ...infer Rest]
|
||||||
? OneOf<[XOR<A, B>, ...Rest]>
|
? OneOf<[XOR<A, B>, ...Rest]>
|
||||||
: never;
|
: never;
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
export interface paths {
|
export interface paths {
|
||||||
'/info': {
|
'/info': {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
|||||||
.and('contain.text', 'USDC (fake)');
|
.and('contain.text', 'USDC (fake)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Unable to submit proposal with public key', function () {
|
it.skip('Unable to submit proposal with public key', function () {
|
||||||
const expectedErrorTxt = `You are connected in a view only state for public key: ${vegaWalletPubKey}. In order to send transactions you must connect to a real wallet.`;
|
const expectedErrorTxt = `You are connected in a view only state for public key: ${vegaWalletPubKey}. In order to send transactions you must connect to a real wallet.`;
|
||||||
|
|
||||||
goToMakeNewProposal(governanceProposalType.RAW);
|
goToMakeNewProposal(governanceProposalType.RAW);
|
||||||
@@ -55,7 +55,10 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
|||||||
cy.getByTestId('dialog-content')
|
cy.getByTestId('dialog-content')
|
||||||
.first()
|
.first()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get('h1').should('have.text', 'Transaction failed');
|
cy.getByTestId('dialog-title').should(
|
||||||
|
'have.text',
|
||||||
|
'Transaction failed'
|
||||||
|
);
|
||||||
cy.getByTestId('Error').should('have.text', expectedErrorTxt);
|
cy.getByTestId('Error').should('have.text', expectedErrorTxt);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
|
|||||||
performanceHysteresisEpochs: 2,
|
performanceHysteresisEpochs: 2,
|
||||||
slaCompetitionFactor: '0.1',
|
slaCompetitionFactor: '0.1',
|
||||||
},
|
},
|
||||||
|
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||||
quadraticSlippageFactor: '0',
|
quadraticSlippageFactor: '0',
|
||||||
instrument: {
|
instrument: {
|
||||||
name: 'Token test market',
|
name: 'Token test market',
|
||||||
@@ -196,6 +197,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
|
|||||||
timeWindow: '3600',
|
timeWindow: '3600',
|
||||||
scalingFactor: 10,
|
scalingFactor: 10,
|
||||||
},
|
},
|
||||||
|
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||||
triggeringRatio: '0.7',
|
triggeringRatio: '0.7',
|
||||||
auctionExtension: '1',
|
auctionExtension: '1',
|
||||||
},
|
},
|
||||||
@@ -240,6 +242,7 @@ export function createSuccessorMarketProposalTxBody(
|
|||||||
decimalPlaces: '5',
|
decimalPlaces: '5',
|
||||||
positionDecimalPlaces: '5',
|
positionDecimalPlaces: '5',
|
||||||
linearSlippageFactor: '0.001',
|
linearSlippageFactor: '0.001',
|
||||||
|
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||||
quadraticSlippageFactor: '0',
|
quadraticSlippageFactor: '0',
|
||||||
liquiditySlaParameters: {
|
liquiditySlaParameters: {
|
||||||
priceRange: '0.5',
|
priceRange: '0.5',
|
||||||
@@ -334,6 +337,7 @@ export function createSuccessorMarketProposalTxBody(
|
|||||||
timeWindow: '3600',
|
timeWindow: '3600',
|
||||||
scalingFactor: 10,
|
scalingFactor: 10,
|
||||||
},
|
},
|
||||||
|
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||||
triggeringRatio: '0.7',
|
triggeringRatio: '0.7',
|
||||||
auctionExtension: '1',
|
auctionExtension: '1',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
import { Web3Provider } from '@vegaprotocol/web3';
|
import { Web3Provider } from '@vegaprotocol/web3';
|
||||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
|
||||||
import {
|
import {
|
||||||
useVegaTransactionManager,
|
useVegaTransactionManager,
|
||||||
useVegaTransactionUpdater,
|
useVegaTransactionUpdater,
|
||||||
@@ -96,7 +96,9 @@ const cache: InMemoryCacheConfig = {
|
|||||||
const Web3Container = ({
|
const Web3Container = ({
|
||||||
chainId,
|
chainId,
|
||||||
}: {
|
}: {
|
||||||
|
/** Ethereum chain id */
|
||||||
chainId: number;
|
chainId: number;
|
||||||
|
/** Ethereum provider url */
|
||||||
providerUrl: string;
|
providerUrl: string;
|
||||||
}) => {
|
}) => {
|
||||||
const InitializeHandlers = () => {
|
const InitializeHandlers = () => {
|
||||||
@@ -123,6 +125,9 @@ const Web3Container = ({
|
|||||||
MOZILLA_EXTENSION_URL,
|
MOZILLA_EXTENSION_URL,
|
||||||
VEGA_WALLET_URL,
|
VEGA_WALLET_URL,
|
||||||
} = useEnvironment();
|
} = useEnvironment();
|
||||||
|
|
||||||
|
const vegaChainId = useChainId(VEGA_URL);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chainId) {
|
if (chainId) {
|
||||||
return initializeConnectors(
|
return initializeConnectors(
|
||||||
@@ -157,7 +162,8 @@ const Web3Container = ({
|
|||||||
!VEGA_EXPLORER_URL ||
|
!VEGA_EXPLORER_URL ||
|
||||||
!DocsLinks ||
|
!DocsLinks ||
|
||||||
!CHROME_EXTENSION_URL ||
|
!CHROME_EXTENSION_URL ||
|
||||||
!MOZILLA_EXTENSION_URL
|
!MOZILLA_EXTENSION_URL ||
|
||||||
|
!vegaChainId
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -169,6 +175,7 @@ const Web3Container = ({
|
|||||||
config={{
|
config={{
|
||||||
network: VEGA_ENV,
|
network: VEGA_ENV,
|
||||||
vegaUrl: VEGA_URL,
|
vegaUrl: VEGA_URL,
|
||||||
|
chainId: vegaChainId,
|
||||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||||
links: {
|
links: {
|
||||||
explorer: VEGA_EXPLORER_URL,
|
explorer: VEGA_EXPLORER_URL,
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
import {
|
||||||
|
PARTY_NOT_FOUND,
|
||||||
|
filterAcceptableGraphqlErrors,
|
||||||
|
isPartyNotFoundError,
|
||||||
|
} from './party';
|
||||||
|
import type { GraphQLError } from 'graphql';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @returns GraphQLError
|
||||||
|
*/
|
||||||
|
function createMockApolloErrors(message: string): GraphQLError {
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
extensions: {
|
||||||
|
code: message.toUpperCase().replace(/ /g, '_'),
|
||||||
|
},
|
||||||
|
locations: [],
|
||||||
|
originalError: new Error(message),
|
||||||
|
path: [],
|
||||||
|
nodes: [],
|
||||||
|
positions: [1],
|
||||||
|
name: message,
|
||||||
|
source: {
|
||||||
|
body: message,
|
||||||
|
name: message,
|
||||||
|
locationOffset: {
|
||||||
|
line: 1,
|
||||||
|
column: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('filterAcceptableGraphqlErrors', () => {
|
||||||
|
it('should return undefined if the error is a party not found error', () => {
|
||||||
|
const error: Partial<ApolloError> = {
|
||||||
|
graphQLErrors: [createMockApolloErrors('failed to get party for ID')],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the error if it is not a party not found error', () => {
|
||||||
|
const error: Partial<ApolloError> = {
|
||||||
|
graphQLErrors: [createMockApolloErrors('Some other error')],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||||
|
|
||||||
|
expect(result).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the error if there are multiple errors', () => {
|
||||||
|
const error: Partial<ApolloError> = {
|
||||||
|
graphQLErrors: [
|
||||||
|
createMockApolloErrors('failed to get party for ID'),
|
||||||
|
createMockApolloErrors('Some other error'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||||
|
|
||||||
|
expect(result).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the error if there are no errors', () => {
|
||||||
|
const error: Partial<ApolloError> = {
|
||||||
|
graphQLErrors: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||||
|
|
||||||
|
expect(result).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if the error is undefined', () => {
|
||||||
|
const result = filterAcceptableGraphqlErrors(undefined);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isPartyNotFoundError', () => {
|
||||||
|
it('should return true if the error message includes PARTY_NOT_FOUND', () => {
|
||||||
|
const error = { message: 'failed to get party for ID' };
|
||||||
|
|
||||||
|
const result = isPartyNotFoundError(error);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the error message does not include PARTY_NOT_FOUND', () => {
|
||||||
|
const error = { message: 'Some other error' };
|
||||||
|
|
||||||
|
const result = isPartyNotFoundError(error);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Will trip if the error message changes, which should not be a problem, but there
|
||||||
|
// might be logic that depends on it
|
||||||
|
it('expects party not found error to remain consistent', () => {
|
||||||
|
const error = 'failed to get party for ID';
|
||||||
|
|
||||||
|
expect(PARTY_NOT_FOUND).toStrictEqual(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
|
||||||
export const PARTY_NOT_FOUND = 'failed to get party for ID';
|
export const PARTY_NOT_FOUND = 'failed to get party for ID';
|
||||||
|
|
||||||
export const isPartyNotFoundError = (error: { message: string }) => {
|
export const isPartyNotFoundError = (error: { message: string }) => {
|
||||||
@@ -6,3 +8,23 @@ export const isPartyNotFoundError = (error: { message: string }) => {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a party has no accounts or data, then this GraphQL query believes it does not exist
|
||||||
|
* Not having any rewards is a valid state, so in some cases we can filter this error out.
|
||||||
|
*
|
||||||
|
* @param error ApolloError | undefined
|
||||||
|
* @returns ApolloError | undefined
|
||||||
|
*/
|
||||||
|
export function filterAcceptableGraphqlErrors(
|
||||||
|
error?: ApolloError
|
||||||
|
): ApolloError | undefined {
|
||||||
|
// Currently the only error we expect is when a party has no accounts
|
||||||
|
if (error && error.graphQLErrors.length === 1) {
|
||||||
|
if (isPartyNotFoundError(error.graphQLErrors[0])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -124,7 +124,7 @@ describe('Proposal header', () => {
|
|||||||
screen.queryByTestId('proposal-description')
|
screen.queryByTestId('proposal-description')
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||||
'Market change: MarketId'
|
'Update to market ID: MarketId'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+42
-4
@@ -1,5 +1,11 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Lozenge, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
CopyWithTooltip,
|
||||||
|
Lozenge,
|
||||||
|
Tooltip,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { shorten } from '@vegaprotocol/utils';
|
import { shorten } from '@vegaprotocol/utils';
|
||||||
import { Heading, SubHeading } from '../../../../components/heading';
|
import { Heading, SubHeading } from '../../../../components/heading';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
@@ -12,7 +18,12 @@ import {
|
|||||||
useNewTransferProposalDetails,
|
useNewTransferProposalDetails,
|
||||||
useSuccessorMarketProposalDetails,
|
useSuccessorMarketProposalDetails,
|
||||||
} from '@vegaprotocol/proposals';
|
} from '@vegaprotocol/proposals';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import {
|
||||||
|
CONSOLE_MARKET_PAGE,
|
||||||
|
DApp,
|
||||||
|
useFeatureFlags,
|
||||||
|
useLinks,
|
||||||
|
} from '@vegaprotocol/environment';
|
||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import type { VoteState } from '../vote-details/use-user-vote';
|
import type { VoteState } from '../vote-details/use-user-vote';
|
||||||
@@ -32,6 +43,8 @@ export const ProposalHeader = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const change = proposal?.terms.change;
|
const change = proposal?.terms.change;
|
||||||
|
|
||||||
|
const consoleLink = useLinks(DApp.Console);
|
||||||
|
|
||||||
let details: ReactNode;
|
let details: ReactNode;
|
||||||
let proposalType = '';
|
let proposalType = '';
|
||||||
let fallbackTitle = '';
|
let fallbackTitle = '';
|
||||||
@@ -106,8 +119,33 @@ export const ProposalHeader = ({
|
|||||||
fallbackTitle = t('UpdateMarketProposal');
|
fallbackTitle = t('UpdateMarketProposal');
|
||||||
details = (
|
details = (
|
||||||
<>
|
<>
|
||||||
<span>{t('MarketChange')}:</span>{' '}
|
<span>{t('UpdateToMarket')}:</span>{' '}
|
||||||
<span>{truncateMiddle(change.marketId)}</span>
|
<span className="inline-flex items-start gap-2">
|
||||||
|
<span className="break-all">{change.marketId} </span>
|
||||||
|
<span className="inline-flex items-end gap-0">
|
||||||
|
<CopyWithTooltip
|
||||||
|
text={change.marketId}
|
||||||
|
description={t('copyToClipboard')}
|
||||||
|
>
|
||||||
|
<button className="inline-block px-1">
|
||||||
|
<VegaIcon size={20} name={VegaIconNames.COPY} />
|
||||||
|
</button>
|
||||||
|
</CopyWithTooltip>
|
||||||
|
<Tooltip description={t('OpenInConsole')} align="center">
|
||||||
|
<button
|
||||||
|
className="inline-block px-1"
|
||||||
|
onClick={() => {
|
||||||
|
const marketPageLink = consoleLink(
|
||||||
|
CONSOLE_MARKET_PAGE.replace(':marketId', change.marketId)
|
||||||
|
);
|
||||||
|
window.open(marketPageLink, '_blank');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VegaIcon size={20} name={VegaIconNames.OPEN_EXTERNAL} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const vegaWalletConfig: VegaWalletConfig = {
|
|||||||
chromeExtensionUrl: 'chrome',
|
chromeExtensionUrl: 'chrome',
|
||||||
mozillaExtensionUrl: 'mozilla',
|
mozillaExtensionUrl: 'mozilla',
|
||||||
},
|
},
|
||||||
|
chainId: 'VEGA_CHAIN_ID',
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
|||||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||||
|
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
|
||||||
|
|
||||||
interface VoteButtonsContainerProps {
|
interface VoteButtonsContainerProps {
|
||||||
voteState: VoteState | null;
|
voteState: VoteState | null;
|
||||||
@@ -42,8 +43,10 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
|||||||
skip: !pubKey,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
<AsyncRenderer loading={loading} error={filteredErrors} data={data}>
|
||||||
<VoteButtons
|
<VoteButtons
|
||||||
{...props}
|
{...props}
|
||||||
currentStakeAvailable={toBigNum(
|
currentStakeAvailable={toBigNum(
|
||||||
|
|||||||
@@ -205,7 +205,6 @@ query Proposal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
liquidityMonitoringParameters {
|
liquidityMonitoringParameters {
|
||||||
triggeringRatio
|
|
||||||
targetStakeParameters {
|
targetStakeParameters {
|
||||||
timeWindow
|
timeWindow
|
||||||
scalingFactor
|
scalingFactor
|
||||||
@@ -213,7 +212,6 @@ query Proposal(
|
|||||||
}
|
}
|
||||||
positionDecimalPlaces
|
positionDecimalPlaces
|
||||||
linearSlippageFactor
|
linearSlippageFactor
|
||||||
quadraticSlippageFactor
|
|
||||||
}
|
}
|
||||||
... on UpdateMarket {
|
... on UpdateMarket {
|
||||||
marketId
|
marketId
|
||||||
@@ -366,7 +364,6 @@ query Proposal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
liquidityMonitoringParameters {
|
liquidityMonitoringParameters {
|
||||||
triggeringRatio
|
|
||||||
targetStakeParameters {
|
targetStakeParameters {
|
||||||
timeWindow
|
timeWindow
|
||||||
scalingFactor
|
scalingFactor
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+23
-13
@@ -10,6 +10,7 @@ import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
|||||||
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
||||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||||
|
import { filterAcceptableGraphqlErrors } from '../../../lib/party';
|
||||||
|
|
||||||
const EPOCHS_PAGE_SIZE = 10;
|
const EPOCHS_PAGE_SIZE = 10;
|
||||||
|
|
||||||
@@ -99,17 +100,24 @@ export const EpochIndividualRewards = ({
|
|||||||
prevEpochIdRef.current = epochId;
|
prevEpochIdRef.current = epochId;
|
||||||
}, [epochId, refetchData]);
|
}, [epochId, refetchData]);
|
||||||
|
|
||||||
|
// Workarounds for the error handling of AsyncRenderer
|
||||||
|
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||||
|
const filteredData = data || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={filteredErrors}
|
||||||
data={data}
|
data={filteredData}
|
||||||
render={() => (
|
render={() => (
|
||||||
<div>
|
<div>
|
||||||
<p data-testid="connected-vega-key" className="mb-10">
|
<p data-testid="connected-vega-key" className="mb-10">
|
||||||
{t('Connected Vega key')}:{' '}
|
{t('Connected Vega key')}:{' '}
|
||||||
<span className="text-white">{pubKey}</span>
|
<span className="text-white">{pubKey}</span>
|
||||||
</p>
|
</p>
|
||||||
|
{epochIndividualRewardSummaries.length === 0 && (
|
||||||
|
<p>{t('No rewards for key')}</p>
|
||||||
|
)}
|
||||||
{epochIndividualRewardSummaries.map(
|
{epochIndividualRewardSummaries.map(
|
||||||
(epochIndividualRewardSummary) => (
|
(epochIndividualRewardSummary) => (
|
||||||
<EpochIndividualRewardsTable
|
<EpochIndividualRewardsTable
|
||||||
@@ -118,17 +126,19 @@ export const EpochIndividualRewards = ({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<Pagination
|
{epochIndividualRewardSummaries.length > 0 && (
|
||||||
isLoading={loading}
|
<Pagination
|
||||||
hasPrevPage={page > 1}
|
isLoading={loading}
|
||||||
hasNextPage={page < totalPages}
|
hasPrevPage={page > 1}
|
||||||
onBack={() => refetchData(page - 1)}
|
hasNextPage={page < totalPages}
|
||||||
onNext={() => refetchData(page + 1)}
|
onBack={() => refetchData(page - 1)}
|
||||||
onFirst={() => refetchData(1)}
|
onNext={() => refetchData(page + 1)}
|
||||||
onLast={() => refetchData(totalPages)}
|
onFirst={() => refetchData(1)}
|
||||||
>
|
onLast={() => refetchData(totalPages)}
|
||||||
{t('Page')} {page}
|
>
|
||||||
</Pagination>
|
{t('Page')} {page}
|
||||||
|
</Pagination>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+2
-2
@@ -16,7 +16,7 @@ import type { ValidatorsView } from './validator-tables';
|
|||||||
const nodeFactory = (
|
const nodeFactory = (
|
||||||
overrides?: PartialDeep<NodesFragmentFragment>
|
overrides?: PartialDeep<NodesFragmentFragment>
|
||||||
): NodesFragmentFragment => {
|
): NodesFragmentFragment => {
|
||||||
const defaultNode = {
|
const defaultNode: NodesFragmentFragment = {
|
||||||
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
|
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
|
||||||
name: 'high',
|
name: 'high',
|
||||||
avatarUrl: 'https://upload.wikimedia.org/wikipedia/en/2/25/Marvin-TV-3.jpg',
|
avatarUrl: 'https://upload.wikimedia.org/wikipedia/en/2/25/Marvin-TV-3.jpg',
|
||||||
@@ -288,7 +288,7 @@ describe('Consensus validators table', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
grid.querySelector('[role="gridcell"][col-id="totalPenalties"]')
|
grid.querySelector('[role="gridcell"][col-id="totalPenalties"]')
|
||||||
).toHaveTextContent('10.07%');
|
).toHaveTextContent('13.16%');
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
grid.querySelector('[role="gridcell"][col-id="normalisedVotingPower"]')
|
grid.querySelector('[role="gridcell"][col-id="normalisedVotingPower"]')
|
||||||
|
|||||||
+11
-2
@@ -185,6 +185,15 @@ export const ConsensusValidatorsTable = ({
|
|||||||
const { rawValidatorScore: previousEpochValidatorScore } =
|
const { rawValidatorScore: previousEpochValidatorScore } =
|
||||||
getLastEpochScoreAndPerformance(previousEpochData, id);
|
getLastEpochScoreAndPerformance(previousEpochData, id);
|
||||||
|
|
||||||
|
const overstakingPenalty = calculateOverallPenalty(
|
||||||
|
id,
|
||||||
|
allNodesInPreviousEpoch
|
||||||
|
);
|
||||||
|
const totalPenalty = calculateOverstakedPenalty(
|
||||||
|
id,
|
||||||
|
allNodesInPreviousEpoch
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
||||||
@@ -213,11 +222,11 @@ export const ConsensusValidatorsTable = ({
|
|||||||
2
|
2
|
||||||
),
|
),
|
||||||
[ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
|
[ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
|
||||||
calculateOverstakedPenalty(id, allNodesInPreviousEpoch),
|
overstakingPenalty,
|
||||||
2
|
2
|
||||||
),
|
),
|
||||||
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
|
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
|
||||||
calculateOverallPenalty(id, allNodesInPreviousEpoch),
|
totalPenalty,
|
||||||
2
|
2
|
||||||
),
|
),
|
||||||
[ValidatorFields.PENDING_STAKE]: pendingStake,
|
[ValidatorFields.PENDING_STAKE]: pendingStake,
|
||||||
|
|||||||
+11
-2
@@ -124,6 +124,15 @@ export const StandbyPendingValidatorsTable = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const overstakingPenalty = calculateOverallPenalty(
|
||||||
|
id,
|
||||||
|
allNodesInPreviousEpoch
|
||||||
|
);
|
||||||
|
const totalPenalty = calculateOverstakedPenalty(
|
||||||
|
id,
|
||||||
|
allNodesInPreviousEpoch
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
||||||
@@ -154,11 +163,11 @@ export const StandbyPendingValidatorsTable = ({
|
|||||||
2
|
2
|
||||||
),
|
),
|
||||||
[ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
|
[ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
|
||||||
calculateOverstakedPenalty(id, allNodesInPreviousEpoch),
|
overstakingPenalty,
|
||||||
2
|
2
|
||||||
),
|
),
|
||||||
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
|
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
|
||||||
calculateOverallPenalty(id, allNodesInPreviousEpoch),
|
totalPenalty,
|
||||||
2
|
2
|
||||||
),
|
),
|
||||||
[ValidatorFields.PENDING_STAKE]: pendingStake,
|
[ValidatorFields.PENDING_STAKE]: pendingStake,
|
||||||
|
|||||||
@@ -266,7 +266,9 @@ export const ValidatorTable = ({
|
|||||||
|
|
||||||
<Tooltip description={t('OverstakedPenaltyDescription')}>
|
<Tooltip description={t('OverstakedPenaltyDescription')}>
|
||||||
<span data-testid="overstaking-penalty">
|
<span data-testid="overstaking-penalty">
|
||||||
{formatNumberPercentage(penalties.overstaked, 2)}
|
{penalties.overstaked
|
||||||
|
? formatNumberPercentage(penalties.overstaked, 2)
|
||||||
|
: '-'}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
@@ -285,7 +287,9 @@ export const ValidatorTable = ({
|
|||||||
</span>
|
</span>
|
||||||
<span data-testid="total-penalties">
|
<span data-testid="total-penalties">
|
||||||
<strong>
|
<strong>
|
||||||
{formatNumberPercentage(penalties.overall, 2)}
|
{penalties.overall
|
||||||
|
? formatNumberPercentage(penalties.overall, 2)
|
||||||
|
: '-'}
|
||||||
</strong>
|
</strong>
|
||||||
</span>
|
</span>
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import {
|
|||||||
getLastEpochScoreAndPerformance,
|
getLastEpochScoreAndPerformance,
|
||||||
getNormalisedVotingPower,
|
getNormalisedVotingPower,
|
||||||
getUnnormalisedVotingPower,
|
getUnnormalisedVotingPower,
|
||||||
getOverstakingPenalty,
|
|
||||||
getFormattedPerformanceScore,
|
getFormattedPerformanceScore,
|
||||||
getPerformancePenalty,
|
getPerformancePenalty,
|
||||||
getTotalPenalties,
|
|
||||||
getStakePercentage,
|
getStakePercentage,
|
||||||
|
calculateOverallPenalty,
|
||||||
|
calculateOverstakedPenalty,
|
||||||
} from './shared';
|
} from './shared';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
|
||||||
@@ -106,38 +106,6 @@ describe('getUnnormalisedVotingPower', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getOverstakingPenalty', () => {
|
|
||||||
it('returns "0%" when both arguments are null or undefined', () => {
|
|
||||||
expect(getOverstakingPenalty(null, null)).toBe('0%');
|
|
||||||
expect(getOverstakingPenalty(undefined, undefined)).toBe('0%');
|
|
||||||
expect(getOverstakingPenalty(null, undefined)).toBe('0%');
|
|
||||||
expect(getOverstakingPenalty(undefined, null)).toBe('0%');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns "0%" when one argument is null or undefined', () => {
|
|
||||||
expect(getOverstakingPenalty('10', null)).toBe('0%');
|
|
||||||
expect(getOverstakingPenalty(null, '20')).toBe('0%');
|
|
||||||
expect(getOverstakingPenalty('10', undefined)).toBe('0%');
|
|
||||||
expect(getOverstakingPenalty(undefined, '20')).toBe('0%');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns "0%" when validatorScore or stakeScore is zero', () => {
|
|
||||||
expect(getOverstakingPenalty('0', '20')).toBe('0%');
|
|
||||||
expect(getOverstakingPenalty('10', '0')).toBe('0%');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the correct overstaking penalty', () => {
|
|
||||||
expect(getOverstakingPenalty('0.18', '0.2')).toBe('10.00%');
|
|
||||||
expect(getOverstakingPenalty('0.2', '0.2')).toBe('0.00%');
|
|
||||||
expect(getOverstakingPenalty('0.04', '0.2')).toBe('80.00%');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles string numbers with decimals', () => {
|
|
||||||
expect(getOverstakingPenalty('7.5', '15')).toBe('50.00%');
|
|
||||||
expect(getOverstakingPenalty('12.5', '25')).toBe('50.00%');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getFormattedPerformanceScore', () => {
|
describe('getFormattedPerformanceScore', () => {
|
||||||
it('should return the formatted performance score', () => {
|
it('should return the formatted performance score', () => {
|
||||||
expect(getFormattedPerformanceScore('0.25')).toEqual(new BigNumber(0.25));
|
expect(getFormattedPerformanceScore('0.25')).toEqual(new BigNumber(0.25));
|
||||||
@@ -152,17 +120,6 @@ describe('getPerformancePenalty', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getTotalPenalties', () => {
|
|
||||||
it('should return the total penalties', () => {
|
|
||||||
expect(getTotalPenalties('0.25', '1', '5000', '10000')).toEqual('50.00%');
|
|
||||||
expect(getTotalPenalties('0.25', '0.5', '5000', '10000')).toEqual('75.00%');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 0 if the total penalties is negative', () => {
|
|
||||||
expect(getTotalPenalties('0.25', '0.5', '1000', '10000')).toEqual('0.00%');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getStakePercentage', () => {
|
describe('getStakePercentage', () => {
|
||||||
it('should return the stake percentage', () => {
|
it('should return the stake percentage', () => {
|
||||||
expect(
|
expect(
|
||||||
@@ -182,3 +139,107 @@ describe('getStakePercentage', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('calculateOverallPenalty', () => {
|
||||||
|
it('returns null if rewardScore is null', () => {
|
||||||
|
const res = calculateOverallPenalty('1', [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
rewardScore: null,
|
||||||
|
stakedTotal: '',
|
||||||
|
rankingScore: {
|
||||||
|
stakeScore: '0.25',
|
||||||
|
performanceScore: '0.75',
|
||||||
|
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
previousStatus:
|
||||||
|
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
rankingScore: '',
|
||||||
|
votingPower: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(res).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null if rewardScore.rawValidatorScore is null (should not happen)', () => {
|
||||||
|
const res = calculateOverallPenalty('1', [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
stakedTotal: '',
|
||||||
|
rewardScore: {
|
||||||
|
rawValidatorScore: '0.25',
|
||||||
|
performanceScore: '0.75',
|
||||||
|
multisigScore: '',
|
||||||
|
validatorScore: null as unknown as string,
|
||||||
|
normalisedScore: '',
|
||||||
|
validatorStatus:
|
||||||
|
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
},
|
||||||
|
rankingScore: {
|
||||||
|
stakeScore: '0.25',
|
||||||
|
performanceScore: '0.75',
|
||||||
|
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
previousStatus:
|
||||||
|
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
rankingScore: '',
|
||||||
|
votingPower: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(res).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('calculateOverstakedPenalty', () => {
|
||||||
|
it('returns null if rewardScore is null', () => {
|
||||||
|
const res = calculateOverstakedPenalty('1', [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
rewardScore: null,
|
||||||
|
stakedTotal: '',
|
||||||
|
rankingScore: {
|
||||||
|
stakeScore: '0.25',
|
||||||
|
performanceScore: '0.75',
|
||||||
|
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
previousStatus:
|
||||||
|
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
rankingScore: '',
|
||||||
|
votingPower: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(res).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null if rewardScore.rawValidatorScore is null (should not happen)', () => {
|
||||||
|
const res = calculateOverstakedPenalty('1', [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
stakedTotal: '',
|
||||||
|
rewardScore: {
|
||||||
|
rawValidatorScore: null as unknown as string,
|
||||||
|
performanceScore: '0.75',
|
||||||
|
multisigScore: '',
|
||||||
|
validatorScore: '',
|
||||||
|
normalisedScore: '',
|
||||||
|
validatorStatus:
|
||||||
|
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
},
|
||||||
|
rankingScore: {
|
||||||
|
stakeScore: '0.25',
|
||||||
|
performanceScore: '0.75',
|
||||||
|
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
previousStatus:
|
||||||
|
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||||
|
rankingScore: '',
|
||||||
|
votingPower: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(res).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
import type { PreviousEpochQuery } from './__generated__/PreviousEpoch';
|
import type { PreviousEpochQuery } from './__generated__/PreviousEpoch';
|
||||||
import { BigNumber } from '../../lib/bignumber';
|
import { BigNumber } from '../../lib/bignumber';
|
||||||
import type { LastArrayElement } from 'type-fest';
|
import type { LastArrayElement } from 'type-fest';
|
||||||
|
import isNull from 'lodash/isNull';
|
||||||
|
|
||||||
type Node = NonNullable<
|
type Node = NonNullable<
|
||||||
LastArrayElement<
|
LastArrayElement<
|
||||||
@@ -21,7 +22,10 @@ type Node = NonNullable<
|
|||||||
* @returns Theoretical stake score for given node based on the staked total
|
* @returns Theoretical stake score for given node based on the staked total
|
||||||
* of all node of the same type (status)
|
* of all node of the same type (status)
|
||||||
*/
|
*/
|
||||||
const calculateTheoreticalStakeScore = (nodeId: string, nodes: Node[]) => {
|
const calculateTheoreticalStakeScore = (
|
||||||
|
nodeId: string,
|
||||||
|
nodes: Node[]
|
||||||
|
): BigNumber | null => {
|
||||||
const node = nodes.find((n) => n.id === nodeId);
|
const node = nodes.find((n) => n.id === nodeId);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return new BigNumber(0);
|
return new BigNumber(0);
|
||||||
@@ -42,14 +46,25 @@ const calculateTheoreticalStakeScore = (nodeId: string, nodes: Node[]) => {
|
|||||||
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
|
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
|
||||||
* @returns %
|
* @returns %
|
||||||
*/
|
*/
|
||||||
export const calculateOverallPenalty = (nodeId: string, nodes: Node[]) => {
|
export const calculateOverallPenalty = (
|
||||||
|
nodeId: string,
|
||||||
|
nodes: Node[]
|
||||||
|
): BigNumber | null => {
|
||||||
const node = nodes.find((n) => n.id === nodeId);
|
const node = nodes.find((n) => n.id === nodeId);
|
||||||
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
|
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
|
||||||
if (!node || tts.isZero()) {
|
if (
|
||||||
|
!node ||
|
||||||
|
isNull(tts) ||
|
||||||
|
!node.rewardScore ||
|
||||||
|
(node.rewardScore && isNull(node.rewardScore.validatorScore))
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (tts.isZero()) {
|
||||||
return new BigNumber(0);
|
return new BigNumber(0);
|
||||||
}
|
}
|
||||||
const penalty = new BigNumber(1)
|
const penalty = new BigNumber(1)
|
||||||
.minus(new BigNumber(node.rewardScore?.validatorScore || 0).dividedBy(tts))
|
.minus(new BigNumber(node.rewardScore.validatorScore).dividedBy(tts))
|
||||||
.times(100);
|
.times(100);
|
||||||
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
|
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
|
||||||
};
|
};
|
||||||
@@ -60,10 +75,21 @@ export const calculateOverallPenalty = (nodeId: string, nodes: Node[]) => {
|
|||||||
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
|
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
|
||||||
* @returns %
|
* @returns %
|
||||||
*/
|
*/
|
||||||
export const calculateOverstakedPenalty = (nodeId: string, nodes: Node[]) => {
|
export const calculateOverstakedPenalty = (
|
||||||
|
nodeId: string,
|
||||||
|
nodes: Node[]
|
||||||
|
): BigNumber | null => {
|
||||||
const node = nodes.find((n) => n.id === nodeId);
|
const node = nodes.find((n) => n.id === nodeId);
|
||||||
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
|
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
|
||||||
if (!node || tts.isZero()) {
|
if (
|
||||||
|
!node ||
|
||||||
|
isNull(tts) ||
|
||||||
|
isNull(node.rewardScore) ||
|
||||||
|
(node.rewardScore && node.rewardScore.rawValidatorScore === null)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (tts.isZero()) {
|
||||||
return new BigNumber(0);
|
return new BigNumber(0);
|
||||||
}
|
}
|
||||||
const penalty = new BigNumber(1)
|
const penalty = new BigNumber(1)
|
||||||
@@ -78,7 +104,9 @@ export const calculateOverstakedPenalty = (nodeId: string, nodes: Node[]) => {
|
|||||||
* Calculates performance penalty based on the given performance score.
|
* Calculates performance penalty based on the given performance score.
|
||||||
* @returns %
|
* @returns %
|
||||||
*/
|
*/
|
||||||
export const calculatesPerformancePenalty = (performanceScore: string) => {
|
export const calculatesPerformancePenalty = (
|
||||||
|
performanceScore: string
|
||||||
|
): BigNumber => {
|
||||||
const penalty = new BigNumber(1)
|
const penalty = new BigNumber(1)
|
||||||
.minus(new BigNumber(performanceScore))
|
.minus(new BigNumber(performanceScore))
|
||||||
.times(100);
|
.times(100);
|
||||||
@@ -123,60 +151,6 @@ export const getPerformancePenalty = (performanceScore?: string) =>
|
|||||||
2
|
2
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getOverstakingPenalty = (
|
|
||||||
validatorScore: string | null | undefined,
|
|
||||||
stakeScore: string | null | undefined
|
|
||||||
) => {
|
|
||||||
if (!validatorScore || !stakeScore) {
|
|
||||||
return '0%';
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid division by zero
|
|
||||||
if (
|
|
||||||
new BigNumber(validatorScore).isZero() ||
|
|
||||||
new BigNumber(stakeScore).isZero()
|
|
||||||
) {
|
|
||||||
return '0%';
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatNumberPercentage(
|
|
||||||
BigNumber.max(
|
|
||||||
new BigNumber(1)
|
|
||||||
.minus(
|
|
||||||
new BigNumber(validatorScore).dividedBy(new BigNumber(stakeScore))
|
|
||||||
)
|
|
||||||
.times(100),
|
|
||||||
new BigNumber(0)
|
|
||||||
),
|
|
||||||
2
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTotalPenalties = (
|
|
||||||
rawValidatorScore: string | null | undefined,
|
|
||||||
performanceScore: string | undefined,
|
|
||||||
stakedOnNode: string,
|
|
||||||
totalStake: string
|
|
||||||
) => {
|
|
||||||
const calc =
|
|
||||||
rawValidatorScore &&
|
|
||||||
performanceScore &&
|
|
||||||
new BigNumber(totalStake).isGreaterThan(0)
|
|
||||||
? new BigNumber(1).minus(
|
|
||||||
new BigNumber(performanceScore)
|
|
||||||
.times(new BigNumber(rawValidatorScore))
|
|
||||||
.dividedBy(
|
|
||||||
new BigNumber(stakedOnNode).dividedBy(new BigNumber(totalStake))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: new BigNumber(0);
|
|
||||||
|
|
||||||
return formatNumberPercentage(
|
|
||||||
calc.isPositive() ? calc.times(100) : new BigNumber(0),
|
|
||||||
2
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getStakePercentage = (total: BigNumber, stakedOnNode: BigNumber) =>
|
export const getStakePercentage = (total: BigNumber, stakedOnNode: BigNumber) =>
|
||||||
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
|
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
|
||||||
? '0%'
|
? '0%'
|
||||||
|
|||||||
@@ -1,14 +1,50 @@
|
|||||||
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
Intent,
|
||||||
|
ToastsContainer,
|
||||||
|
TradingButton,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
useToasts,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||||
import {
|
import {
|
||||||
useEthereumTransactionToasts,
|
useEthereumTransactionToasts,
|
||||||
useEthereumWithdrawApprovalsToasts,
|
useEthereumWithdrawApprovalsToasts,
|
||||||
useVegaTransactionToasts,
|
useVegaTransactionToasts,
|
||||||
|
useWalletDisconnectToastActions,
|
||||||
|
useWalletDisconnectedToasts,
|
||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const WalletDisconnectAdditionalContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { hideToast } = useWalletDisconnectToastActions();
|
||||||
|
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||||
|
(store) => store.openVegaWalletDialog
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<p className="mt-2">
|
||||||
|
<TradingButton
|
||||||
|
data-testid="connect-vega-wallet"
|
||||||
|
onClick={() => {
|
||||||
|
hideToast();
|
||||||
|
openVegaWalletDialog();
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
intent={Intent.Danger}
|
||||||
|
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
||||||
|
>
|
||||||
|
<span className="whitespace-nowrap uppercase">{t('Connect')}</span>
|
||||||
|
</TradingButton>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ToastsManager = () => {
|
export const ToastsManager = () => {
|
||||||
useVegaTransactionToasts();
|
useVegaTransactionToasts();
|
||||||
useEthereumTransactionToasts();
|
useEthereumTransactionToasts();
|
||||||
useEthereumWithdrawApprovalsToasts();
|
useEthereumWithdrawApprovalsToasts();
|
||||||
|
useWalletDisconnectedToasts(<WalletDisconnectAdditionalContent />);
|
||||||
|
|
||||||
const toasts = useToasts((store) => store.toasts);
|
const toasts = useToasts((store) => store.toasts);
|
||||||
return <ToastsContainer order="desc" toasts={toasts} />;
|
return <ToastsContainer order="desc" toasts={toasts} />;
|
||||||
|
|||||||
+8
-2
@@ -21,8 +21,14 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
|
NX_ISOLATED_MARGIN=true
|
||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
NX_TEAM_COMPETITION=true
|
# NX_DISABLE_CLOSE_POSITION=false
|
||||||
|
|
||||||
|
NX_TENDERMINT_URL=https://be.vega.community
|
||||||
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||||
|
|
||||||
|
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||||
|
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=false
|
NX_SUCCESSOR_MARKETS=false
|
||||||
NX_STOP_ORDERS=false
|
NX_STOP_ORDERS=false
|
||||||
|
NX_ISOLATED_MARGIN=true
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
|
NX_ISOLATED_MARGIN=true
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
|
NX_ISOLATED_MARGIN=false
|
||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
@@ -28,3 +29,6 @@ NX_REFERRALS=true
|
|||||||
|
|
||||||
NX_TENDERMINT_URL=https://be.vega.community
|
NX_TENDERMINT_URL=https://be.vega.community
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||||
|
|
||||||
|
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||||
|
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ NX_APP_VERSION=v0.20.19-core-0.71.6
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
|
NX_ISOLATED_MARGIN=false
|
||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
|
NX_ISOLATED_MARGIN=true
|
||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
|
NX_ISOLATED_MARGIN=true
|
||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
|
NX_ISOLATED_MARGIN=true
|
||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
|
|||||||
@@ -54,3 +54,5 @@ To run the UI automation tests with a mocked API, run:
|
|||||||
```bash
|
```bash
|
||||||
yarn nx run trading-e2e:e2e
|
yarn nx run trading-e2e:e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To run tests with market sim please read [the readme](e2e/README.md).
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ describe('Closed', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('display market actions', async () => {
|
it('display market actions', async () => {
|
||||||
// Use market with a succcessor Id as the actions dropdown will optionally
|
// Use market with a successor Id as the actions dropdown will optionally
|
||||||
// show a link to the successor market
|
// show a link to the successor market
|
||||||
const marketsWithSuccessorAndParent = [
|
const marketsWithSuccessorAndParent = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ const ClosedMarketsDataGrid = ({
|
|||||||
headerName: t('Market'),
|
headerName: t('Market'),
|
||||||
field: 'code',
|
field: 'code',
|
||||||
cellRenderer: 'MarketCodeCell',
|
cellRenderer: 'MarketCodeCell',
|
||||||
|
width: 150,
|
||||||
|
resizable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: t('Status'),
|
headerName: t('Status'),
|
||||||
@@ -280,6 +282,7 @@ const ClosedMarketsDataGrid = ({
|
|||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
rowData={rowData}
|
rowData={rowData}
|
||||||
|
defaultColDef={COL_DEFS.default}
|
||||||
columnDefs={colDefs}
|
columnDefs={colDefs}
|
||||||
getRowId={({ data }) => data.id}
|
getRowId={({ data }) => data.id}
|
||||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const defaultColDef = {
|
|||||||
filter: true,
|
filter: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
filterParams: { buttons: ['reset'] },
|
filterParams: { buttons: ['reset'] },
|
||||||
|
minWidth: 120,
|
||||||
};
|
};
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export const MarketsPage = () => {
|
|||||||
size="extra-small"
|
size="extra-small"
|
||||||
data-testid="propose-new-market"
|
data-testid="propose-new-market"
|
||||||
href={externalLink}
|
href={externalLink}
|
||||||
|
target="_blank"
|
||||||
>
|
>
|
||||||
{t('Propose a new market')}
|
{t('Propose a new market')}
|
||||||
</TradingAnchorButton>
|
</TradingAnchorButton>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const useColumnDefs = () => {
|
|||||||
{
|
{
|
||||||
headerName: t('Market'),
|
headerName: t('Market'),
|
||||||
field: 'tradableInstrument.instrument.code',
|
field: 'tradableInstrument.instrument.code',
|
||||||
|
pinned: true,
|
||||||
cellRenderer: ({
|
cellRenderer: ({
|
||||||
value,
|
value,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useT } from '../../lib/use-t';
|
|||||||
import { RewardsContainer } from '../../components/rewards-container';
|
import { RewardsContainer } from '../../components/rewards-container';
|
||||||
import { usePageTitleStore } from '../../stores';
|
import { usePageTitleStore } from '../../stores';
|
||||||
import { ErrorBoundary } from '../../components/error-boundary';
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
|
import { TinyScroll } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
export const Rewards = () => {
|
export const Rewards = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@@ -16,10 +17,10 @@ export const Rewards = () => {
|
|||||||
}, [updateTitle, title]);
|
}, [updateTitle, title]);
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary feature="rewards">
|
<ErrorBoundary feature="rewards">
|
||||||
<div className="container mx-auto p-4">
|
<TinyScroll className="p-4 max-h-full overflow-auto">
|
||||||
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
||||||
<RewardsContainer />
|
<RewardsContainer />
|
||||||
</div>
|
</TinyScroll>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { Web3Provider } from './web3-provider';
|
import { Web3Provider } from './web3-provider';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
import { DataLoader } from './data-loader';
|
||||||
|
import { useChainId } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@@ -25,13 +27,16 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
|||||||
CHROME_EXTENSION_URL,
|
CHROME_EXTENSION_URL,
|
||||||
} = useEnvironment();
|
} = useEnvironment();
|
||||||
|
|
||||||
|
const chainId = useChainId(VEGA_URL);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!VEGA_URL ||
|
!VEGA_URL ||
|
||||||
!VEGA_WALLET_URL ||
|
!VEGA_WALLET_URL ||
|
||||||
!VEGA_EXPLORER_URL ||
|
!VEGA_EXPLORER_URL ||
|
||||||
!CHROME_EXTENSION_URL ||
|
!CHROME_EXTENSION_URL ||
|
||||||
!MOZILLA_EXTENSION_URL ||
|
!MOZILLA_EXTENSION_URL ||
|
||||||
!DocsLinks
|
!DocsLinks ||
|
||||||
|
!chainId
|
||||||
) {
|
) {
|
||||||
return <AppLoader />;
|
return <AppLoader />;
|
||||||
}
|
}
|
||||||
@@ -52,28 +57,39 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Web3Provider
|
<DataLoader
|
||||||
skeleton={<AppLoader />}
|
skeleton={<AppLoader />}
|
||||||
failure={
|
failure={
|
||||||
<AppFailure title={t('Could not configure web3 provider')} />
|
<AppFailure
|
||||||
|
title={t('Could not load market data or asset data')}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<VegaWalletProvider
|
<Web3Provider
|
||||||
config={{
|
skeleton={<AppLoader />}
|
||||||
network: VEGA_ENV,
|
failure={
|
||||||
vegaUrl: VEGA_URL,
|
<AppFailure title={t('Could not configure web3 provider')} />
|
||||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
}
|
||||||
links: {
|
|
||||||
explorer: VEGA_EXPLORER_URL,
|
|
||||||
concepts: DocsLinks.VEGA_WALLET_CONCEPTS_URL,
|
|
||||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
|
||||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{children}
|
<VegaWalletProvider
|
||||||
</VegaWalletProvider>
|
config={{
|
||||||
</Web3Provider>
|
network: VEGA_ENV,
|
||||||
|
vegaUrl: VEGA_URL,
|
||||||
|
chainId,
|
||||||
|
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||||
|
links: {
|
||||||
|
explorer: VEGA_EXPLORER_URL,
|
||||||
|
concepts: DocsLinks.VEGA_WALLET_CONCEPTS_URL,
|
||||||
|
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||||
|
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</VegaWalletProvider>
|
||||||
|
</Web3Provider>
|
||||||
|
</DataLoader>
|
||||||
</NodeGuard>
|
</NodeGuard>
|
||||||
</NetworkLoader>
|
</NetworkLoader>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { useAssetsMapProvider } from '@vegaprotocol/assets';
|
||||||
|
import { useMarketsMapProvider } from '@vegaprotocol/markets';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export const DataLoader = ({
|
||||||
|
children,
|
||||||
|
failure,
|
||||||
|
skeleton,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
failure: ReactNode;
|
||||||
|
skeleton: ReactNode;
|
||||||
|
}) => {
|
||||||
|
// Query all markets and assets to ensure they are cached
|
||||||
|
const { data: markets, error, loading } = useMarketsMapProvider();
|
||||||
|
const {
|
||||||
|
data: assets,
|
||||||
|
error: errorAssets,
|
||||||
|
loading: loadingAssets,
|
||||||
|
} = useAssetsMapProvider();
|
||||||
|
|
||||||
|
if (loading || loadingAssets) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
return <>{skeleton}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || errorAssets || !markets || !assets) {
|
||||||
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <>{failure}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
@@ -25,12 +25,12 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
|
|||||||
overlays,
|
overlays,
|
||||||
studies,
|
studies,
|
||||||
studySizes,
|
studySizes,
|
||||||
tradingViewStudies,
|
|
||||||
setInterval,
|
setInterval,
|
||||||
setStudies,
|
setStudies,
|
||||||
setStudySizes,
|
setStudySizes,
|
||||||
setOverlays,
|
setOverlays,
|
||||||
setTradingViewStudies,
|
state,
|
||||||
|
setState,
|
||||||
} = useChartSettings();
|
} = useChartSettings();
|
||||||
|
|
||||||
const pennantChart = (
|
const pennantChart = (
|
||||||
@@ -64,13 +64,13 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
|
|||||||
libraryHash={CHARTING_LIBRARY_HASH}
|
libraryHash={CHARTING_LIBRARY_HASH}
|
||||||
marketId={marketId}
|
marketId={marketId}
|
||||||
interval={toTradingViewResolution(interval)}
|
interval={toTradingViewResolution(interval)}
|
||||||
studies={tradingViewStudies}
|
|
||||||
onIntervalChange={(newInterval) => {
|
onIntervalChange={(newInterval) => {
|
||||||
setInterval(fromTradingViewResolution(newInterval));
|
setInterval(fromTradingViewResolution(newInterval));
|
||||||
}}
|
}}
|
||||||
onAutoSaveNeeded={(data: { studies: string[] }) => {
|
onAutoSaveNeeded={(data) => {
|
||||||
setTradingViewStudies(data.studies);
|
setState(data);
|
||||||
}}
|
}}
|
||||||
|
state={state}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ describe('ChartMenu', () => {
|
|||||||
|
|
||||||
render(<ChartMenu />);
|
render(<ChartMenu />);
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: 'TradingView' }));
|
await userEvent.click(screen.getByTestId('chartlib-toggle-button'));
|
||||||
expect(useChartSettingsStore.getState().chartlib).toEqual('tradingview');
|
expect(useChartSettingsStore.getState().chartlib).toEqual('tradingview');
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: 'Vega chart' }));
|
await userEvent.click(screen.getByTestId('chartlib-toggle-button'));
|
||||||
expect(useChartSettingsStore.getState().chartlib).toEqual('pennant');
|
expect(useChartSettingsStore.getState().chartlib).toEqual('pennant');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export const ChartMenu = () => {
|
|||||||
setChartlib(isPennant ? 'tradingview' : 'pennant');
|
setChartlib(isPennant ? 'tradingview' : 'pennant');
|
||||||
}}
|
}}
|
||||||
size="extra-small"
|
size="extra-small"
|
||||||
|
testId="chartlib-toggle-button"
|
||||||
>
|
>
|
||||||
{isPennant ? 'TradingView' : t('Vega chart')}
|
{isPennant ? 'TradingView' : t('Vega chart')}
|
||||||
</TradingButton>
|
</TradingButton>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type StudySizes = { [S in Study]?: number };
|
|||||||
export type Chartlib = 'pennant' | 'tradingview';
|
export type Chartlib = 'pennant' | 'tradingview';
|
||||||
|
|
||||||
interface StoredSettings {
|
interface StoredSettings {
|
||||||
|
state: object | undefined; // Don't see a better type provided from TradingView type definitions
|
||||||
chartlib: Chartlib;
|
chartlib: Chartlib;
|
||||||
// For interval we use the enum from @vegaprotocol/types, this is to make mapping between different
|
// For interval we use the enum from @vegaprotocol/types, this is to make mapping between different
|
||||||
// chart types easier and more consistent
|
// chart types easier and more consistent
|
||||||
@@ -17,7 +18,6 @@ interface StoredSettings {
|
|||||||
overlays: Overlay[];
|
overlays: Overlay[];
|
||||||
studies: Study[];
|
studies: Study[];
|
||||||
studySizes: StudySizes;
|
studySizes: StudySizes;
|
||||||
tradingViewStudies: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const STUDY_SIZE = 90;
|
export const STUDY_SIZE = 90;
|
||||||
@@ -30,13 +30,13 @@ const STUDY_ORDER: Study[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_CHART_SETTINGS = {
|
export const DEFAULT_CHART_SETTINGS = {
|
||||||
|
state: undefined,
|
||||||
chartlib: 'pennant' as const,
|
chartlib: 'pennant' as const,
|
||||||
interval: Interval.INTERVAL_I15M,
|
interval: Interval.INTERVAL_I15M,
|
||||||
type: ChartType.CANDLE,
|
type: ChartType.CANDLE,
|
||||||
overlays: [Overlay.MOVING_AVERAGE],
|
overlays: [Overlay.MOVING_AVERAGE],
|
||||||
studies: [Study.MACD, Study.VOLUME],
|
studies: [Study.MACD, Study.VOLUME],
|
||||||
studySizes: {},
|
studySizes: {},
|
||||||
tradingViewStudies: ['Volume'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useChartSettingsStore = create<
|
export const useChartSettingsStore = create<
|
||||||
@@ -47,7 +47,7 @@ export const useChartSettingsStore = create<
|
|||||||
setStudies: (studies?: Study[]) => void;
|
setStudies: (studies?: Study[]) => void;
|
||||||
setStudySizes: (sizes: number[]) => void;
|
setStudySizes: (sizes: number[]) => void;
|
||||||
setChartlib: (lib: Chartlib) => void;
|
setChartlib: (lib: Chartlib) => void;
|
||||||
setTradingViewStudies: (studies: string[]) => void;
|
setState: (state: object) => void;
|
||||||
}
|
}
|
||||||
>()(
|
>()(
|
||||||
persist(
|
persist(
|
||||||
@@ -95,10 +95,8 @@ export const useChartSettingsStore = create<
|
|||||||
state.chartlib = lib;
|
state.chartlib = lib;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setTradingViewStudies: (studies: string[]) => {
|
setState: (state) => {
|
||||||
set((state) => {
|
set({ state });
|
||||||
state.tradingViewStudies = studies;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
@@ -147,13 +145,13 @@ export const useChartSettings = () => {
|
|||||||
overlays,
|
overlays,
|
||||||
studies,
|
studies,
|
||||||
studySizes,
|
studySizes,
|
||||||
tradingViewStudies: settings.tradingViewStudies,
|
|
||||||
setInterval: settings.setInterval,
|
setInterval: settings.setInterval,
|
||||||
setType: settings.setType,
|
setType: settings.setType,
|
||||||
setStudies: settings.setStudies,
|
setStudies: settings.setStudies,
|
||||||
setOverlays: settings.setOverlays,
|
setOverlays: settings.setOverlays,
|
||||||
setStudySizes: settings.setStudySizes,
|
setStudySizes: settings.setStudySizes,
|
||||||
setChartlib: settings.setChartlib,
|
setChartlib: settings.setChartlib,
|
||||||
setTradingViewStudies: settings.setTradingViewStudies,
|
state: settings.state,
|
||||||
|
setState: settings.setState,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ export const FeesContainer = () => {
|
|||||||
const { data: markets, loading: marketsLoading } = useMarketList();
|
const { data: markets, loading: marketsLoading } = useMarketList();
|
||||||
|
|
||||||
const { data: programData, loading: programLoading } =
|
const { data: programData, loading: programLoading } =
|
||||||
useDiscountProgramsQuery({ errorPolicy: 'ignore' });
|
useDiscountProgramsQuery({
|
||||||
|
errorPolicy: 'ignore',
|
||||||
|
fetchPolicy: 'cache-and-network',
|
||||||
|
pollInterval: 15000,
|
||||||
|
});
|
||||||
|
|
||||||
const volumeDiscountWindowLength =
|
const volumeDiscountWindowLength =
|
||||||
programData?.currentVolumeDiscountProgram?.windowLength || 1;
|
programData?.currentVolumeDiscountProgram?.windowLength || 1;
|
||||||
@@ -49,6 +53,8 @@ export const FeesContainer = () => {
|
|||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
},
|
},
|
||||||
skip: !pubKey,
|
skip: !pubKey,
|
||||||
|
fetchPolicy: 'cache-and-network',
|
||||||
|
pollInterval: 15000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const previousEpoch = (Number(feesData?.epoch.id) || 0) - 1;
|
const previousEpoch = (Number(feesData?.epoch.id) || 0) - 1;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { DiscountProgramsQuery, FeesQuery } from './__generated__/Fees';
|
import type { DiscountProgramsQuery, FeesQuery } from './__generated__/Fees';
|
||||||
|
|
||||||
export const useReferralStats = (
|
export const useReferralStats = (
|
||||||
previousEpoch?: number,
|
previousEpoch?: number,
|
||||||
referralStats?: NonNullable<
|
referralStats?: NonNullable<
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { GridSettings } from './grid-settings';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
|
describe('GridSettings', () => {
|
||||||
|
it('calls updateGridStore with correct arguments on button click', async () => {
|
||||||
|
const mockUpdateGridStore = jest.fn();
|
||||||
|
const { getByText } = render(
|
||||||
|
<GridSettings updateGridStore={mockUpdateGridStore} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await userEvent.click(getByText('Reset Columns'));
|
||||||
|
|
||||||
|
expect(mockUpdateGridStore).toHaveBeenCalledWith({
|
||||||
|
columnState: undefined,
|
||||||
|
filterModel: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -42,13 +42,9 @@ export const LiquidityHeader = () => {
|
|||||||
const assetDecimalPlaces = asset?.decimals || 0;
|
const assetDecimalPlaces = asset?.decimals || 0;
|
||||||
const symbol = asset?.symbol;
|
const symbol = asset?.symbol;
|
||||||
|
|
||||||
const triggeringRatio =
|
|
||||||
market?.liquidityMonitoringParameters.triggeringRatio || '1';
|
|
||||||
|
|
||||||
const { percentage, status } = useCheckLiquidityStatus({
|
const { percentage, status } = useCheckLiquidityStatus({
|
||||||
suppliedStake: suppliedStake || 0,
|
suppliedStake: suppliedStake || 0,
|
||||||
targetStake: targetStake || 0,
|
targetStake: targetStake || 0,
|
||||||
triggeringRatio,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const feesObject = feesPaidRes?.paidLiquidityFees?.edges?.find(
|
const feesObject = feesPaidRes?.paidLiquidityFees?.edges?.find(
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ export const MarketLiquiditySupplied = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
|
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
|
||||||
const triggeringRatio = Number(
|
|
||||||
params.market_liquidity_targetstake_triggering_ratio
|
|
||||||
);
|
|
||||||
|
|
||||||
const variables = useMemo(
|
const variables = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -94,7 +91,6 @@ export const MarketLiquiditySupplied = ({
|
|||||||
const { percentage, status } = useCheckLiquidityStatus({
|
const { percentage, status } = useCheckLiquidityStatus({
|
||||||
suppliedStake: market?.suppliedStake || 0,
|
suppliedStake: market?.suppliedStake || 0,
|
||||||
targetStake: market?.targetStake || 0,
|
targetStake: market?.targetStake || 0,
|
||||||
triggeringRatio,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const showMessage =
|
const showMessage =
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ const MarketData = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-2/5" role="gridcell">
|
<div className="w-2/6" role="gridcell">
|
||||||
<h3 className="flex items-baseline">
|
<h3 className="flex items-baseline">
|
||||||
<span className="overflow-hidden text-sm lg:text-base text-ellipsis whitespace-nowrap">
|
<span className="overflow-hidden text-xs md:text-sm lg:text-base text-ellipsis whitespace-nowrap">
|
||||||
{market.tradableInstrument.instrument.code}
|
{market.tradableInstrument.instrument.code}
|
||||||
</span>
|
</span>
|
||||||
{allProducts && productType && (
|
{allProducts && productType && (
|
||||||
@@ -113,7 +113,7 @@ const MarketData = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="w-1/5 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis"
|
className="w-2/6 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis text-right"
|
||||||
title={symbol}
|
title={symbol}
|
||||||
data-testid="market-selector-price"
|
data-testid="market-selector-price"
|
||||||
role="gridcell"
|
role="gridcell"
|
||||||
@@ -121,14 +121,14 @@ const MarketData = ({
|
|||||||
{price} {symbol}
|
{price} {symbol}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="w-1/5 overflow-hidden text-xs text-right lg:text-sm whitespace-nowrap text-ellipsis"
|
className="w-2/6 sm:w-1/6 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis text-right"
|
||||||
title={t('24h vol')}
|
title={t('24h vol')}
|
||||||
data-testid="market-selector-volume"
|
data-testid="market-selector-volume"
|
||||||
role="gridcell"
|
role="gridcell"
|
||||||
>
|
>
|
||||||
{volume}
|
{volume}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end w-1/5" role="gridcell">
|
<div className="hidden sm:w-1/6 sm:flex justify-end" role="gridcell">
|
||||||
{oneDayCandles && (
|
{oneDayCandles && (
|
||||||
<Sparkline
|
<Sparkline
|
||||||
width={64}
|
width={64}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const MarketSelector = ({
|
|||||||
setFilter((curr) => ({ ...curr, product }));
|
setFilter((curr) => ({ ...curr, product }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm grid grid-cols-[2fr_1fr_1fr] gap-1 ">
|
<div className="text-sm flex sm:grid grid-cols-[2fr_1fr_1fr] gap-1 ">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<TradingInput
|
<TradingInput
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -182,16 +182,16 @@ const MarketList = ({
|
|||||||
'p-2 mx-2 border-b border-default text-xs text-secondary'
|
'p-2 mx-2 border-b border-default text-xs text-secondary'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="w-2/5" role="columnheader">
|
<div className="w-2/6" role="columnheader">
|
||||||
{t('Name')}
|
{t('Name')}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/5" role="columnheader">
|
<div className="w-2/6 text-right pr-4" role="columnheader">
|
||||||
{t('Price')}
|
{t('Price')}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/5 text-right" role="columnheader">
|
<div className="w-2/6 sm:w-1/6 text-right" role="columnheader">
|
||||||
{t('24h volume')}
|
{t('24h volume')}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/5" role="columnheader" />
|
<div className="hidden sm:w-1/6" role="columnheader" />
|
||||||
</div>
|
</div>
|
||||||
<div ref={listRef}>
|
<div ref={listRef}>
|
||||||
<List
|
<List
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useParams } from 'react-router-dom';
|
|||||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is only rendered for the mobile navigation
|
* This is only rendered for the mobile navigation
|
||||||
@@ -30,7 +31,16 @@ export const NavHeader = () => {
|
|||||||
trigger={
|
trigger={
|
||||||
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-default text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default">
|
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-default text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default">
|
||||||
{data ? data.tradableInstrument.instrument.code : t('Select market')}
|
{data ? data.tradableInstrument.instrument.code : t('Select market')}
|
||||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'transition-transform ease-in-out duration-300',
|
||||||
|
{
|
||||||
|
'rotate-180': open,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
|||||||
const envNameMapping = useEnvNameMapping();
|
const envNameMapping = useEnvNameMapping();
|
||||||
const { VEGA_ENV, VEGA_NETWORKS, GITHUB_FEEDBACK_URL } = useEnvironment();
|
const { VEGA_ENV, VEGA_NETWORKS, GITHUB_FEEDBACK_URL } = useEnvironment();
|
||||||
const marketId = useGlobalStore((store) => store.marketId);
|
const marketId = useGlobalStore((store) => store.marketId);
|
||||||
|
const GOVERNANCE_LINK = useLinks(DApp.Governance)();
|
||||||
|
const EXPLORER_LINK = useLinks(DApp.Explorer)();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gap-3 lg:flex lg:h-full">
|
<div className="gap-3 lg:flex lg:h-full">
|
||||||
@@ -220,7 +222,7 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
|||||||
</NavbarLink>
|
</NavbarLink>
|
||||||
</NavbarItem>
|
</NavbarItem>
|
||||||
<NavbarItem>
|
<NavbarItem>
|
||||||
<NavbarLinkExternal to={useLinks(DApp.Governance)()}>
|
<NavbarLinkExternal to={GOVERNANCE_LINK}>
|
||||||
{t('Governance')}
|
{t('Governance')}
|
||||||
</NavbarLinkExternal>
|
</NavbarLinkExternal>
|
||||||
</NavbarItem>
|
</NavbarItem>
|
||||||
@@ -228,6 +230,13 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
|||||||
<NavbarTrigger>{t('Resources')}</NavbarTrigger>
|
<NavbarTrigger>{t('Resources')}</NavbarTrigger>
|
||||||
<NavbarContent data-testid="navbar-content-resources">
|
<NavbarContent data-testid="navbar-content-resources">
|
||||||
<ul className="lg:p-4">
|
<ul className="lg:p-4">
|
||||||
|
{EXPLORER_LINK && (
|
||||||
|
<NavbarSubItem>
|
||||||
|
<NavbarLinkExternal to={EXPLORER_LINK}>
|
||||||
|
{t('Explorer')}
|
||||||
|
</NavbarLinkExternal>
|
||||||
|
</NavbarSubItem>
|
||||||
|
)}
|
||||||
{DocsLinks?.NEW_TO_VEGA && (
|
{DocsLinks?.NEW_TO_VEGA && (
|
||||||
<NavbarSubItem>
|
<NavbarSubItem>
|
||||||
<NavbarLinkExternal to={DocsLinks?.NEW_TO_VEGA}>
|
<NavbarLinkExternal to={DocsLinks?.NEW_TO_VEGA}>
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ query RewardsPage($partyId: ID!) {
|
|||||||
party(id: $partyId) {
|
party(id: $partyId) {
|
||||||
id
|
id
|
||||||
vestingStats {
|
vestingStats {
|
||||||
# AKA hoarder reward multiplier
|
|
||||||
rewardBonusMultiplier
|
rewardBonusMultiplier
|
||||||
|
quantumBalance
|
||||||
|
epochSeq
|
||||||
}
|
}
|
||||||
activityStreak {
|
activityStreak {
|
||||||
# vesting multiplier
|
activeFor
|
||||||
rewardVestingMultiplier
|
isActive
|
||||||
# AKA streak multiplier
|
inactiveFor
|
||||||
rewardDistributionMultiplier
|
rewardDistributionMultiplier
|
||||||
|
rewardVestingMultiplier
|
||||||
|
epoch
|
||||||
|
tradedVolume
|
||||||
|
openVolume
|
||||||
}
|
}
|
||||||
vestingBalancesSummary {
|
vestingBalancesSummary {
|
||||||
epoch
|
epoch
|
||||||
@@ -36,6 +41,74 @@ query RewardsPage($partyId: ID!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query ActiveRewards(
|
||||||
|
$isReward: Boolean
|
||||||
|
$partyId: ID
|
||||||
|
$direction: TransferDirection
|
||||||
|
$pagination: Pagination
|
||||||
|
) {
|
||||||
|
transfersConnection(
|
||||||
|
partyId: $partyId
|
||||||
|
isReward: $isReward
|
||||||
|
direction: $direction
|
||||||
|
pagination: $pagination
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
transfer {
|
||||||
|
amount
|
||||||
|
id
|
||||||
|
from
|
||||||
|
fromAccountType
|
||||||
|
to
|
||||||
|
toAccountType
|
||||||
|
asset {
|
||||||
|
id
|
||||||
|
symbol
|
||||||
|
decimals
|
||||||
|
name
|
||||||
|
quantum
|
||||||
|
status
|
||||||
|
}
|
||||||
|
reference
|
||||||
|
status
|
||||||
|
timestamp
|
||||||
|
kind {
|
||||||
|
... on RecurringTransfer {
|
||||||
|
startEpoch
|
||||||
|
endEpoch
|
||||||
|
dispatchStrategy {
|
||||||
|
dispatchMetric
|
||||||
|
dispatchMetricAssetId
|
||||||
|
marketIdsInScope
|
||||||
|
entityScope
|
||||||
|
individualScope
|
||||||
|
teamScope
|
||||||
|
nTopPerformers
|
||||||
|
stakingRequirement
|
||||||
|
notionalTimeWeightedAveragePositionRequirement
|
||||||
|
windowLength
|
||||||
|
lockPeriod
|
||||||
|
distributionStrategy
|
||||||
|
rankTable {
|
||||||
|
startRank
|
||||||
|
shareRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reason
|
||||||
|
}
|
||||||
|
fees {
|
||||||
|
transferId
|
||||||
|
amount
|
||||||
|
epoch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query RewardsHistory(
|
query RewardsHistory(
|
||||||
$partyId: ID!
|
$partyId: ID!
|
||||||
$epochRewardSummariesPagination: Pagination
|
$epochRewardSummariesPagination: Pagination
|
||||||
@@ -92,3 +165,18 @@ query RewardsEpoch {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query MarketForRewards($marketId: ID!) {
|
||||||
|
market(id: $marketId) {
|
||||||
|
tradableInstrument {
|
||||||
|
instrument {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
code
|
||||||
|
metadata {
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+167
-3
@@ -8,7 +8,17 @@ export type RewardsPageQueryVariables = Types.Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type RewardsPageQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, vestingStats?: { __typename?: 'PartyVestingStats', rewardBonusMultiplier: string } | null, activityStreak?: { __typename?: 'PartyActivityStreak', rewardVestingMultiplier: string, rewardDistributionMultiplier: string } | null, vestingBalancesSummary: { __typename?: 'PartyVestingBalancesSummary', epoch?: number | null, vestingBalances?: Array<{ __typename?: 'PartyVestingBalance', balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null, lockedBalances?: Array<{ __typename?: 'PartyLockedBalance', balance: string, untilEpoch: number, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null } } | null };
|
export type RewardsPageQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, vestingStats?: { __typename?: 'PartyVestingStats', rewardBonusMultiplier: string, quantumBalance: string, epochSeq: number } | null, activityStreak?: { __typename?: 'PartyActivityStreak', activeFor: number, isActive: boolean, inactiveFor: number, rewardDistributionMultiplier: string, rewardVestingMultiplier: string, epoch: number, tradedVolume: string, openVolume: string } | null, vestingBalancesSummary: { __typename?: 'PartyVestingBalancesSummary', epoch?: number | null, vestingBalances?: Array<{ __typename?: 'PartyVestingBalance', balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null, lockedBalances?: Array<{ __typename?: 'PartyLockedBalance', balance: string, untilEpoch: number, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null } } | null };
|
||||||
|
|
||||||
|
export type ActiveRewardsQueryVariables = Types.Exact<{
|
||||||
|
isReward?: Types.InputMaybe<Types.Scalars['Boolean']>;
|
||||||
|
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
|
||||||
|
direction?: Types.InputMaybe<Types.TransferDirection>;
|
||||||
|
pagination?: Types.InputMaybe<Types.Pagination>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ActiveRewardsQuery = { __typename?: 'Query', transfersConnection?: { __typename?: 'TransferConnection', edges?: Array<{ __typename?: 'TransferEdge', node: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', amount: string, id: string, from: string, fromAccountType: Types.AccountType, to: string, toAccountType: Types.AccountType, reference?: string | null, status: Types.TransferStatus, timestamp: any, reason?: string | null, asset?: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string, quantum: string, status: Types.AssetStatus } | null, kind: { __typename?: 'OneOffGovernanceTransfer' } | { __typename?: 'OneOffTransfer' } | { __typename?: 'RecurringGovernanceTransfer' } | { __typename?: 'RecurringTransfer', startEpoch: number, endEpoch?: number | null, dispatchStrategy?: { __typename?: 'DispatchStrategy', dispatchMetric: Types.DispatchMetric, dispatchMetricAssetId: string, marketIdsInScope?: Array<string> | null, entityScope: Types.EntityScope, individualScope?: Types.IndividualScope | null, teamScope?: Array<string | null> | null, nTopPerformers?: string | null, stakingRequirement: string, notionalTimeWeightedAveragePositionRequirement: string, windowLength: number, lockPeriod: number, distributionStrategy: Types.DistributionStrategy, rankTable?: Array<{ __typename?: 'RankTable', startRank: number, shareRatio: number } | null> | null } | null } }, fees?: Array<{ __typename?: 'TransferFee', transferId: string, amount: string, epoch: number } | null> | null } } | null> | null } | null };
|
||||||
|
|
||||||
export type RewardsHistoryQueryVariables = Types.Exact<{
|
export type RewardsHistoryQueryVariables = Types.Exact<{
|
||||||
partyId: Types.Scalars['ID'];
|
partyId: Types.Scalars['ID'];
|
||||||
@@ -26,6 +36,13 @@ export type RewardsEpochQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
|||||||
|
|
||||||
export type RewardsEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string } };
|
export type RewardsEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string } };
|
||||||
|
|
||||||
|
export type MarketForRewardsQueryVariables = Types.Exact<{
|
||||||
|
marketId: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type MarketForRewardsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null } } } } | null };
|
||||||
|
|
||||||
|
|
||||||
export const RewardsPageDocument = gql`
|
export const RewardsPageDocument = gql`
|
||||||
query RewardsPage($partyId: ID!) {
|
query RewardsPage($partyId: ID!) {
|
||||||
@@ -33,10 +50,18 @@ export const RewardsPageDocument = gql`
|
|||||||
id
|
id
|
||||||
vestingStats {
|
vestingStats {
|
||||||
rewardBonusMultiplier
|
rewardBonusMultiplier
|
||||||
|
quantumBalance
|
||||||
|
epochSeq
|
||||||
}
|
}
|
||||||
activityStreak {
|
activityStreak {
|
||||||
rewardVestingMultiplier
|
activeFor
|
||||||
|
isActive
|
||||||
|
inactiveFor
|
||||||
rewardDistributionMultiplier
|
rewardDistributionMultiplier
|
||||||
|
rewardVestingMultiplier
|
||||||
|
epoch
|
||||||
|
tradedVolume
|
||||||
|
openVolume
|
||||||
}
|
}
|
||||||
vestingBalancesSummary {
|
vestingBalancesSummary {
|
||||||
epoch
|
epoch
|
||||||
@@ -91,6 +116,101 @@ export function useRewardsPageLazyQuery(baseOptions?: Apollo.LazyQueryHookOption
|
|||||||
export type RewardsPageQueryHookResult = ReturnType<typeof useRewardsPageQuery>;
|
export type RewardsPageQueryHookResult = ReturnType<typeof useRewardsPageQuery>;
|
||||||
export type RewardsPageLazyQueryHookResult = ReturnType<typeof useRewardsPageLazyQuery>;
|
export type RewardsPageLazyQueryHookResult = ReturnType<typeof useRewardsPageLazyQuery>;
|
||||||
export type RewardsPageQueryResult = Apollo.QueryResult<RewardsPageQuery, RewardsPageQueryVariables>;
|
export type RewardsPageQueryResult = Apollo.QueryResult<RewardsPageQuery, RewardsPageQueryVariables>;
|
||||||
|
export const ActiveRewardsDocument = gql`
|
||||||
|
query ActiveRewards($isReward: Boolean, $partyId: ID, $direction: TransferDirection, $pagination: Pagination) {
|
||||||
|
transfersConnection(
|
||||||
|
partyId: $partyId
|
||||||
|
isReward: $isReward
|
||||||
|
direction: $direction
|
||||||
|
pagination: $pagination
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
transfer {
|
||||||
|
amount
|
||||||
|
id
|
||||||
|
from
|
||||||
|
fromAccountType
|
||||||
|
to
|
||||||
|
toAccountType
|
||||||
|
asset {
|
||||||
|
id
|
||||||
|
symbol
|
||||||
|
decimals
|
||||||
|
name
|
||||||
|
quantum
|
||||||
|
status
|
||||||
|
}
|
||||||
|
reference
|
||||||
|
status
|
||||||
|
timestamp
|
||||||
|
kind {
|
||||||
|
... on RecurringTransfer {
|
||||||
|
startEpoch
|
||||||
|
endEpoch
|
||||||
|
dispatchStrategy {
|
||||||
|
dispatchMetric
|
||||||
|
dispatchMetricAssetId
|
||||||
|
marketIdsInScope
|
||||||
|
entityScope
|
||||||
|
individualScope
|
||||||
|
teamScope
|
||||||
|
nTopPerformers
|
||||||
|
stakingRequirement
|
||||||
|
notionalTimeWeightedAveragePositionRequirement
|
||||||
|
windowLength
|
||||||
|
lockPeriod
|
||||||
|
distributionStrategy
|
||||||
|
rankTable {
|
||||||
|
startRank
|
||||||
|
shareRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reason
|
||||||
|
}
|
||||||
|
fees {
|
||||||
|
transferId
|
||||||
|
amount
|
||||||
|
epoch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useActiveRewardsQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useActiveRewardsQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useActiveRewardsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useActiveRewardsQuery({
|
||||||
|
* variables: {
|
||||||
|
* isReward: // value for 'isReward'
|
||||||
|
* partyId: // value for 'partyId'
|
||||||
|
* direction: // value for 'direction'
|
||||||
|
* pagination: // value for 'pagination'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useActiveRewardsQuery(baseOptions?: Apollo.QueryHookOptions<ActiveRewardsQuery, ActiveRewardsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<ActiveRewardsQuery, ActiveRewardsQueryVariables>(ActiveRewardsDocument, options);
|
||||||
|
}
|
||||||
|
export function useActiveRewardsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ActiveRewardsQuery, ActiveRewardsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<ActiveRewardsQuery, ActiveRewardsQueryVariables>(ActiveRewardsDocument, options);
|
||||||
|
}
|
||||||
|
export type ActiveRewardsQueryHookResult = ReturnType<typeof useActiveRewardsQuery>;
|
||||||
|
export type ActiveRewardsLazyQueryHookResult = ReturnType<typeof useActiveRewardsLazyQuery>;
|
||||||
|
export type ActiveRewardsQueryResult = Apollo.QueryResult<ActiveRewardsQuery, ActiveRewardsQueryVariables>;
|
||||||
export const RewardsHistoryDocument = gql`
|
export const RewardsHistoryDocument = gql`
|
||||||
query RewardsHistory($partyId: ID!, $epochRewardSummariesPagination: Pagination, $partyRewardsPagination: Pagination, $fromEpoch: Int, $toEpoch: Int) {
|
query RewardsHistory($partyId: ID!, $epochRewardSummariesPagination: Pagination, $partyRewardsPagination: Pagination, $fromEpoch: Int, $toEpoch: Int) {
|
||||||
epochRewardSummaries(
|
epochRewardSummaries(
|
||||||
@@ -202,4 +322,48 @@ export function useRewardsEpochLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio
|
|||||||
}
|
}
|
||||||
export type RewardsEpochQueryHookResult = ReturnType<typeof useRewardsEpochQuery>;
|
export type RewardsEpochQueryHookResult = ReturnType<typeof useRewardsEpochQuery>;
|
||||||
export type RewardsEpochLazyQueryHookResult = ReturnType<typeof useRewardsEpochLazyQuery>;
|
export type RewardsEpochLazyQueryHookResult = ReturnType<typeof useRewardsEpochLazyQuery>;
|
||||||
export type RewardsEpochQueryResult = Apollo.QueryResult<RewardsEpochQuery, RewardsEpochQueryVariables>;
|
export type RewardsEpochQueryResult = Apollo.QueryResult<RewardsEpochQuery, RewardsEpochQueryVariables>;
|
||||||
|
export const MarketForRewardsDocument = gql`
|
||||||
|
query MarketForRewards($marketId: ID!) {
|
||||||
|
market(id: $marketId) {
|
||||||
|
tradableInstrument {
|
||||||
|
instrument {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
code
|
||||||
|
metadata {
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useMarketForRewardsQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useMarketForRewardsQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useMarketForRewardsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useMarketForRewardsQuery({
|
||||||
|
* variables: {
|
||||||
|
* marketId: // value for 'marketId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useMarketForRewardsQuery(baseOptions: Apollo.QueryHookOptions<MarketForRewardsQuery, MarketForRewardsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<MarketForRewardsQuery, MarketForRewardsQueryVariables>(MarketForRewardsDocument, options);
|
||||||
|
}
|
||||||
|
export function useMarketForRewardsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarketForRewardsQuery, MarketForRewardsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<MarketForRewardsQuery, MarketForRewardsQueryVariables>(MarketForRewardsDocument, options);
|
||||||
|
}
|
||||||
|
export type MarketForRewardsQueryHookResult = ReturnType<typeof useMarketForRewardsQuery>;
|
||||||
|
export type MarketForRewardsLazyQueryHookResult = ReturnType<typeof useMarketForRewardsLazyQuery>;
|
||||||
|
export type MarketForRewardsQueryResult = Apollo.QueryResult<MarketForRewardsQuery, MarketForRewardsQueryVariables>;
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import {
|
||||||
|
ActiveRewardCard,
|
||||||
|
applyFilter,
|
||||||
|
isActiveReward,
|
||||||
|
} from './active-rewards';
|
||||||
|
import {
|
||||||
|
AccountType,
|
||||||
|
AssetStatus,
|
||||||
|
DispatchMetric,
|
||||||
|
DistributionStrategy,
|
||||||
|
EntityScope,
|
||||||
|
IndividualScope,
|
||||||
|
type RecurringTransfer,
|
||||||
|
type TransferNode,
|
||||||
|
TransferStatus,
|
||||||
|
type Transfer,
|
||||||
|
} from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
jest.mock('./__generated__/Rewards', () => ({
|
||||||
|
useMarketForRewardsQuery: () => ({
|
||||||
|
data: undefined,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/assets', () => ({
|
||||||
|
useAssetDataProvider: () => {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
assetId: 'asset-1',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ActiveRewards', () => {
|
||||||
|
const mockRecurringTransfer: RecurringTransfer = {
|
||||||
|
__typename: 'RecurringTransfer',
|
||||||
|
startEpoch: 115332,
|
||||||
|
endEpoch: 115432,
|
||||||
|
factor: '1',
|
||||||
|
dispatchStrategy: {
|
||||||
|
__typename: 'DispatchStrategy',
|
||||||
|
dispatchMetric: DispatchMetric.DISPATCH_METRIC_LP_FEES_RECEIVED,
|
||||||
|
dispatchMetricAssetId:
|
||||||
|
'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
|
||||||
|
marketIdsInScope: null,
|
||||||
|
entityScope: EntityScope.ENTITY_SCOPE_INDIVIDUALS,
|
||||||
|
individualScope: IndividualScope.INDIVIDUAL_SCOPE_ALL,
|
||||||
|
teamScope: null,
|
||||||
|
nTopPerformers: '',
|
||||||
|
stakingRequirement: '',
|
||||||
|
notionalTimeWeightedAveragePositionRequirement: '',
|
||||||
|
windowLength: 1,
|
||||||
|
lockPeriod: 0,
|
||||||
|
distributionStrategy: DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
|
||||||
|
rankTable: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTransferNode: TransferNode = {
|
||||||
|
__typename: 'TransferNode',
|
||||||
|
transfer: {
|
||||||
|
__typename: 'Transfer',
|
||||||
|
amount: '1613000000',
|
||||||
|
id: 'c4e59bd389c8098e6c7669f2d3e1613b9f42d30b1c4c3793ac44380f4c522835',
|
||||||
|
from: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||||
|
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
|
to: 'network',
|
||||||
|
toAccountType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||||
|
asset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
id: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
|
||||||
|
symbol: 'tUSDC',
|
||||||
|
decimals: 5,
|
||||||
|
name: 'tUSDC TEST',
|
||||||
|
quantum: '1',
|
||||||
|
status: AssetStatus.STATUS_ENABLED,
|
||||||
|
source: {
|
||||||
|
__typename: 'ERC20' as const,
|
||||||
|
contractAddress: '0x123',
|
||||||
|
lifetimeLimit: '100',
|
||||||
|
withdrawThreshold: '100',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reference: 'reward',
|
||||||
|
status: TransferStatus.STATUS_PENDING,
|
||||||
|
timestamp: '2023-12-18T13:05:35.948706Z',
|
||||||
|
kind: mockRecurringTransfer,
|
||||||
|
reason: null,
|
||||||
|
},
|
||||||
|
fees: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('renders with valid props', () => {
|
||||||
|
render(
|
||||||
|
<ActiveRewardCard
|
||||||
|
transferNode={mockTransferNode}
|
||||||
|
currentEpoch={1}
|
||||||
|
kind={mockRecurringTransfer}
|
||||||
|
allMarkets={{}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Liquidity provision fees received/i)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Entity scope')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Average position')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Ends in')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('115431 epochs')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Assessed over')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('1 epoch')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isActiveReward', () => {
|
||||||
|
it('returns true for valid active reward', () => {
|
||||||
|
const node = {
|
||||||
|
transfer: {
|
||||||
|
kind: {
|
||||||
|
__typename: 'RecurringTransfer',
|
||||||
|
dispatchStrategy: {},
|
||||||
|
endEpoch: 10,
|
||||||
|
},
|
||||||
|
status: TransferStatus.STATUS_PENDING,
|
||||||
|
},
|
||||||
|
} as TransferNode;
|
||||||
|
expect(isActiveReward(node, 5)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for invalid active reward', () => {
|
||||||
|
const node = {
|
||||||
|
transfer: {
|
||||||
|
kind: {
|
||||||
|
__typename: 'RecurringTransfer',
|
||||||
|
dispatchStrategy: {},
|
||||||
|
endEpoch: 10,
|
||||||
|
},
|
||||||
|
status: TransferStatus.STATUS_PENDING,
|
||||||
|
},
|
||||||
|
} as TransferNode;
|
||||||
|
expect(isActiveReward(node, 15)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('applyFilter', () => {
|
||||||
|
it('returns true when filter matches dispatch metric label', () => {
|
||||||
|
const transfer = {
|
||||||
|
kind: {
|
||||||
|
__typename: 'RecurringTransfer',
|
||||||
|
dispatchStrategy: {
|
||||||
|
dispatchMetric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
asset: { symbol: 'XYZ' },
|
||||||
|
} as Transfer;
|
||||||
|
const filter = { searchTerm: 'average position' };
|
||||||
|
expect(applyFilter({ transfer }, filter)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true when filter matches asset symbol', () => {
|
||||||
|
const transfer = {
|
||||||
|
kind: {
|
||||||
|
__typename: 'RecurringTransfer',
|
||||||
|
dispatchStrategy: {
|
||||||
|
dispatchMetric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
asset: { symbol: 'XYZ' },
|
||||||
|
} as Transfer;
|
||||||
|
const filter = { searchTerm: 'average position' };
|
||||||
|
expect(applyFilter({ transfer }, filter)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,642 @@
|
|||||||
|
import { useActiveRewardsQuery } from './__generated__/Rewards';
|
||||||
|
import { useT } from '../../lib/use-t';
|
||||||
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
type IconName,
|
||||||
|
type VegaIconSize,
|
||||||
|
Icon,
|
||||||
|
Intent,
|
||||||
|
Tooltip,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
TradingInput,
|
||||||
|
TinyScroll,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
import {
|
||||||
|
type Maybe,
|
||||||
|
type Transfer,
|
||||||
|
type TransferNode,
|
||||||
|
type RecurringTransfer,
|
||||||
|
DistributionStrategyDescriptionMapping,
|
||||||
|
DistributionStrategyMapping,
|
||||||
|
EntityScope,
|
||||||
|
EntityScopeMapping,
|
||||||
|
TransferStatus,
|
||||||
|
TransferStatusMapping,
|
||||||
|
DispatchMetric,
|
||||||
|
DispatchMetricDescription,
|
||||||
|
DispatchMetricLabels,
|
||||||
|
EntityScopeLabelMapping,
|
||||||
|
MarketState,
|
||||||
|
} from '@vegaprotocol/types';
|
||||||
|
import { Card } from '../card/card';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
type AssetFieldsFragment,
|
||||||
|
useAssetsMapProvider,
|
||||||
|
} from '@vegaprotocol/assets';
|
||||||
|
import {
|
||||||
|
type MarketFieldsFragment,
|
||||||
|
useMarketsMapProvider,
|
||||||
|
getAsset,
|
||||||
|
} from '@vegaprotocol/markets';
|
||||||
|
|
||||||
|
export type Filter = {
|
||||||
|
searchTerm: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isActiveReward = (node: TransferNode, currentEpoch: number) => {
|
||||||
|
const { transfer } = node;
|
||||||
|
if (transfer.kind.__typename !== 'RecurringTransfer') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { dispatchStrategy } = transfer.kind;
|
||||||
|
|
||||||
|
if (!dispatchStrategy) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transfer.kind.endEpoch && transfer.kind.endEpoch < currentEpoch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transfer.status !== TransferStatus.STATUS_PENDING) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyFilter = (
|
||||||
|
node: TransferNode & {
|
||||||
|
asset?: AssetFieldsFragment | null;
|
||||||
|
markets?: (MarketFieldsFragment | null)[];
|
||||||
|
},
|
||||||
|
filter: Filter
|
||||||
|
) => {
|
||||||
|
const { transfer } = node;
|
||||||
|
if (
|
||||||
|
transfer.kind.__typename !== 'RecurringTransfer' ||
|
||||||
|
!transfer.kind.dispatchStrategy?.dispatchMetric
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
DispatchMetricLabels[transfer.kind.dispatchStrategy.dispatchMetric]
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(filter.searchTerm.toLowerCase()) ||
|
||||||
|
transfer.asset?.symbol
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(filter.searchTerm.toLowerCase()) ||
|
||||||
|
EntityScopeLabelMapping[transfer.kind.dispatchStrategy.entityScope]
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(filter.searchTerm.toLowerCase()) ||
|
||||||
|
node.asset?.name
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(filter.searchTerm.toLowerCase()) ||
|
||||||
|
node.markets?.some((m) =>
|
||||||
|
m?.tradableInstrument?.instrument?.name
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(filter.searchTerm.toLowerCase())
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
|
||||||
|
const t = useT();
|
||||||
|
const { data: activeRewardsData } = useActiveRewardsQuery({
|
||||||
|
variables: {
|
||||||
|
isReward: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [filter, setFilter] = useState<Filter>({
|
||||||
|
searchTerm: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: assets } = useAssetsMapProvider();
|
||||||
|
const { data: markets } = useMarketsMapProvider();
|
||||||
|
|
||||||
|
const enrichedTransfers = activeRewardsData?.transfersConnection?.edges
|
||||||
|
?.map((e) => e?.node as TransferNode)
|
||||||
|
.filter((node) => isActiveReward(node, currentEpoch))
|
||||||
|
.map((node) => {
|
||||||
|
if (node.transfer.kind.__typename !== 'RecurringTransfer') {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset =
|
||||||
|
assets &&
|
||||||
|
assets[
|
||||||
|
node.transfer.kind.dispatchStrategy?.dispatchMetricAssetId || ''
|
||||||
|
];
|
||||||
|
|
||||||
|
const marketsInScope =
|
||||||
|
node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map(
|
||||||
|
(id) => markets && markets[id]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...node, asset, markets: marketsInScope };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!enrichedTransfers || !enrichedTransfers.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title={t('Active rewards')} className="lg:col-span-full">
|
||||||
|
{enrichedTransfers.length > 1 && (
|
||||||
|
<TradingInput
|
||||||
|
onChange={(e) =>
|
||||||
|
setFilter((curr) => ({ ...curr, searchTerm: e.target.value }))
|
||||||
|
}
|
||||||
|
value={filter.searchTerm}
|
||||||
|
type="text"
|
||||||
|
placeholder={t(
|
||||||
|
'Search by reward dispatch metric, entity scope or asset name'
|
||||||
|
)}
|
||||||
|
data-testid="search-term"
|
||||||
|
className="mb-4 w-20 mr-2"
|
||||||
|
prependElement={<VegaIcon name={VegaIconNames.SEARCH} />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TinyScroll className="grid gap-x-8 gap-y-10 h-fit grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] md:grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] lg:grid-cols-[repeat(auto-fill,_minmax(320px,_1fr))] xl:grid-cols-[repeat(auto-fill,_minmax(335px,_1fr))] max-h-[40rem] overflow-auto pr-2">
|
||||||
|
{enrichedTransfers
|
||||||
|
.filter((n) => applyFilter(n, filter))
|
||||||
|
.map((node, i) => {
|
||||||
|
const { transfer } = node;
|
||||||
|
if (
|
||||||
|
transfer.kind.__typename !== 'RecurringTransfer' ||
|
||||||
|
!transfer.kind.dispatchStrategy?.dispatchMetric
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
node && (
|
||||||
|
<ActiveRewardCard
|
||||||
|
key={i}
|
||||||
|
transferNode={node}
|
||||||
|
kind={transfer.kind}
|
||||||
|
currentEpoch={currentEpoch}
|
||||||
|
allMarkets={markets || {}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TinyScroll>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This was built to be a status indicator for the rewards based on the transfer status
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const StatusIndicator = ({
|
||||||
|
status,
|
||||||
|
reason,
|
||||||
|
}: {
|
||||||
|
status: TransferStatus;
|
||||||
|
reason?: Maybe<string> | undefined;
|
||||||
|
}) => {
|
||||||
|
const t = useT();
|
||||||
|
const getIconIntent = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case TransferStatus.STATUS_DONE:
|
||||||
|
return { icon: IconNames.TICK_CIRCLE, intent: Intent.Success };
|
||||||
|
case TransferStatus.STATUS_REJECTED:
|
||||||
|
return { icon: IconNames.ERROR, intent: Intent.Danger };
|
||||||
|
default:
|
||||||
|
return { icon: IconNames.HELP, intent: Intent.Primary };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const { icon, intent } = getIconIntent(status);
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<span>
|
||||||
|
{t('Transfer status: {{status}} {{reason}}', {
|
||||||
|
status: TransferStatusMapping[status],
|
||||||
|
reason: reason ? `(${reason})` : '',
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
{
|
||||||
|
'text-gray-700 dark:text-gray-300': intent === Intent.None,
|
||||||
|
'text-vega-blue': intent === Intent.Primary,
|
||||||
|
'text-vega-green dark:text-vega-green': intent === Intent.Success,
|
||||||
|
'dark:text-yellow text-yellow-600': intent === Intent.Warning,
|
||||||
|
'text-vega-red': intent === Intent.Danger,
|
||||||
|
},
|
||||||
|
'flex items-start p-1 align-text-bottom'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon size={3} name={icon as IconName} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ActiveRewardCard = ({
|
||||||
|
transferNode,
|
||||||
|
currentEpoch,
|
||||||
|
kind,
|
||||||
|
allMarkets,
|
||||||
|
}: {
|
||||||
|
transferNode: TransferNode & {
|
||||||
|
asset?: AssetFieldsFragment | null;
|
||||||
|
markets?: (MarketFieldsFragment | null)[];
|
||||||
|
};
|
||||||
|
currentEpoch: number;
|
||||||
|
kind: RecurringTransfer;
|
||||||
|
allMarkets?: Record<string, MarketFieldsFragment | null>;
|
||||||
|
}) => {
|
||||||
|
const t = useT();
|
||||||
|
|
||||||
|
const { transfer } = transferNode;
|
||||||
|
const { dispatchStrategy } = kind;
|
||||||
|
|
||||||
|
const marketIdsInScope = dispatchStrategy?.marketIdsInScope;
|
||||||
|
const firstMarketData = transferNode.markets?.[0];
|
||||||
|
|
||||||
|
const specificMarkets = useMemo(() => {
|
||||||
|
if (
|
||||||
|
!firstMarketData ||
|
||||||
|
!marketIdsInScope ||
|
||||||
|
marketIdsInScope.length === 0
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (marketIdsInScope.length > 1) {
|
||||||
|
const marketNames =
|
||||||
|
allMarkets &&
|
||||||
|
marketIdsInScope
|
||||||
|
.map((id) => allMarkets[id]?.tradableInstrument?.instrument?.name)
|
||||||
|
.join(', ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip description={marketNames}>
|
||||||
|
<span>Specific markets</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>{firstMarketData?.tradableInstrument?.instrument?.name || ''}</span>
|
||||||
|
);
|
||||||
|
}, [firstMarketData, marketIdsInScope, allMarkets]);
|
||||||
|
|
||||||
|
const dispatchAsset = transferNode.asset;
|
||||||
|
|
||||||
|
if (!dispatchStrategy) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gray out/hide the cards that are related to not trading markets
|
||||||
|
const marketSettled = transferNode.markets?.some(
|
||||||
|
(m) =>
|
||||||
|
m?.state &&
|
||||||
|
[
|
||||||
|
MarketState.STATE_TRADING_TERMINATED,
|
||||||
|
MarketState.STATE_SETTLED,
|
||||||
|
MarketState.STATE_CANCELLED,
|
||||||
|
MarketState.STATE_CLOSED,
|
||||||
|
].includes(m.state)
|
||||||
|
);
|
||||||
|
|
||||||
|
const assetInSettledMarket =
|
||||||
|
allMarkets &&
|
||||||
|
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
|
||||||
|
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
|
||||||
|
return (
|
||||||
|
m?.state &&
|
||||||
|
[
|
||||||
|
MarketState.STATE_TRADING_TERMINATED,
|
||||||
|
MarketState.STATE_SETTLED,
|
||||||
|
MarketState.STATE_CANCELLED,
|
||||||
|
MarketState.STATE_CLOSED,
|
||||||
|
].includes(m.state)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (marketSettled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gray out the cards that are related to suspended markets
|
||||||
|
const suspended = transferNode.markets?.some(
|
||||||
|
(m) =>
|
||||||
|
m?.state === MarketState.STATE_SUSPENDED ||
|
||||||
|
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
|
||||||
|
);
|
||||||
|
|
||||||
|
const assetInSuspendedMarket =
|
||||||
|
allMarkets &&
|
||||||
|
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
|
||||||
|
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
|
||||||
|
return (
|
||||||
|
m?.state === MarketState.STATE_SUSPENDED ||
|
||||||
|
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gray out the cards that are related to suspended markets
|
||||||
|
const { gradientClassName, mainClassName } =
|
||||||
|
suspended || assetInSuspendedMarket || assetInSettledMarket
|
||||||
|
? {
|
||||||
|
gradientClassName: 'from-vega-cdark-500 to-vega-clight-400',
|
||||||
|
mainClassName: 'from-vega-cdark-400 dark:from-vega-cdark-600 to-20%',
|
||||||
|
}
|
||||||
|
: getGradientClasses(dispatchStrategy.dispatchMetric);
|
||||||
|
|
||||||
|
const entityScope = dispatchStrategy.entityScope;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'bg-gradient-to-r col-span-full p-0.5 lg:col-auto h-full',
|
||||||
|
'rounded-lg',
|
||||||
|
gradientClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
mainClassName,
|
||||||
|
'bg-gradient-to-b bg-vega-clight-800 dark:bg-vega-cdark-800 h-full w-full rounded p-4 flex flex-col gap-4'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between gap-4">
|
||||||
|
<div className="flex flex-col gap-2 items-center text-center">
|
||||||
|
<EntityIcon transfer={transfer} />
|
||||||
|
{entityScope && (
|
||||||
|
<span className="text-muted text-xs">
|
||||||
|
{EntityScopeLabelMapping[entityScope] || t('Unspecified')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 items-center text-center">
|
||||||
|
<h3 className="flex flex-col gap-1 text-2xl shrink-1 text-center">
|
||||||
|
<span className="font-glitch">
|
||||||
|
{addDecimalsFormatNumber(
|
||||||
|
transferNode.transfer.amount,
|
||||||
|
transferNode.transfer.asset?.decimals || 0,
|
||||||
|
6
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="font-alpha">
|
||||||
|
{transferNode.transfer.asset?.symbol}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
{
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
DistributionStrategyDescriptionMapping[
|
||||||
|
dispatchStrategy.distributionStrategy
|
||||||
|
]
|
||||||
|
)}
|
||||||
|
underline={true}
|
||||||
|
>
|
||||||
|
<span className="text-xs">
|
||||||
|
{
|
||||||
|
DistributionStrategyMapping[
|
||||||
|
dispatchStrategy.distributionStrategy
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 items-center text-center">
|
||||||
|
<CardIcon
|
||||||
|
iconName={VegaIconNames.LOCK}
|
||||||
|
tooltip={t(
|
||||||
|
'Number of epochs after distribution to delay vesting of rewards by'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span className="text-muted text-xs whitespace-nowrap">
|
||||||
|
{t('numberEpochs', '{{count}} epochs', {
|
||||||
|
count: kind.dispatchStrategy?.lockPeriod,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="border-[0.5px] border-gray-700" />
|
||||||
|
<span>
|
||||||
|
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} •{' '}
|
||||||
|
<Tooltip
|
||||||
|
underline={suspended}
|
||||||
|
description={
|
||||||
|
(suspended || assetInSuspendedMarket) &&
|
||||||
|
(specificMarkets
|
||||||
|
? t('Eligible market(s) currently suspended')
|
||||||
|
: assetInSuspendedMarket
|
||||||
|
? t('Currently no markets eligible for reward')
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>{specificMarkets || dispatchAsset?.name}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-8 flex-wrap">
|
||||||
|
{kind.endEpoch && (
|
||||||
|
<span className="flex flex-col">
|
||||||
|
<span className="text-muted text-xs">{t('Ends in')}</span>
|
||||||
|
<span>
|
||||||
|
{t('numberEpochs', '{{count}} epochs', {
|
||||||
|
count: kind.endEpoch - currentEpoch,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{
|
||||||
|
<span className="flex flex-col">
|
||||||
|
<span className="text-muted text-xs">{t('Assessed over')}</span>
|
||||||
|
<span>
|
||||||
|
{t('numberEpochs', '{{count}} epochs', {
|
||||||
|
count: dispatchStrategy.windowLength,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{dispatchStrategy?.dispatchMetric && (
|
||||||
|
<span className="text-muted text-sm h-[2rem]">
|
||||||
|
{t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="border-[0.5px] border-gray-700" />
|
||||||
|
|
||||||
|
<div className="flex justify-between flex-wrap items-center gap-3 text-xs">
|
||||||
|
<span className="flex flex-col gap-1">
|
||||||
|
<span className="flex items-center gap-1 text-muted">
|
||||||
|
{t('Entity scope')}{' '}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
{kind.dispatchStrategy?.teamScope && (
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<span>{kind.dispatchStrategy?.teamScope}</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="flex items-center p-1 rounded-full border border-gray-600">
|
||||||
|
{<VegaIcon name={VegaIconNames.TEAM} size={16} />}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{kind.dispatchStrategy?.individualScope && (
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<span>{kind.dispatchStrategy?.individualScope}</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="flex items-center p-1 rounded-full border border-gray-600">
|
||||||
|
{<VegaIcon name={VegaIconNames.MAN} size={16} />}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{/* Shows transfer status */}
|
||||||
|
{/* <StatusIndicator
|
||||||
|
status={transfer.status}
|
||||||
|
reason={transfer.reason}
|
||||||
|
/> */}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="flex flex-col gap-1">
|
||||||
|
<span className="flex items-center gap-1 text-muted">
|
||||||
|
{t('Staked VEGA')}{' '}
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
{addDecimalsFormatNumber(
|
||||||
|
kind.dispatchStrategy?.stakingRequirement || 0,
|
||||||
|
transfer.asset?.decimals || 0
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="flex flex-col gap-1">
|
||||||
|
<span className="flex items-center gap-1 text-muted">
|
||||||
|
{t('Average position')}{' '}
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
{addDecimalsFormatNumber(
|
||||||
|
kind.dispatchStrategy
|
||||||
|
?.notionalTimeWeightedAveragePositionRequirement || 0,
|
||||||
|
transfer.asset?.decimals || 0
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGradientClasses = (d: DispatchMetric | undefined) => {
|
||||||
|
switch (d) {
|
||||||
|
case DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION:
|
||||||
|
return {
|
||||||
|
gradientClassName: 'from-vega-pink-500 to-vega-purple-400',
|
||||||
|
mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%',
|
||||||
|
};
|
||||||
|
case DispatchMetric.DISPATCH_METRIC_LP_FEES_RECEIVED:
|
||||||
|
return {
|
||||||
|
gradientClassName: 'from-vega-green-500 to-vega-yellow-500',
|
||||||
|
mainClassName: 'from-vega-green-400 dark:from-vega-green-600 to-20%',
|
||||||
|
};
|
||||||
|
case DispatchMetric.DISPATCH_METRIC_MAKER_FEES_PAID:
|
||||||
|
return {
|
||||||
|
gradientClassName: 'from-vega-orange-500 to-vega-pink-400',
|
||||||
|
mainClassName: 'from-vega-orange-400 dark:from-vega-orange-600 to-20%',
|
||||||
|
};
|
||||||
|
case DispatchMetric.DISPATCH_METRIC_MARKET_VALUE:
|
||||||
|
case DispatchMetric.DISPATCH_METRIC_RELATIVE_RETURN:
|
||||||
|
return {
|
||||||
|
gradientClassName: 'from-vega-purple-500 to-vega-blue-400',
|
||||||
|
mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%',
|
||||||
|
};
|
||||||
|
case DispatchMetric.DISPATCH_METRIC_RETURN_VOLATILITY:
|
||||||
|
return {
|
||||||
|
gradientClassName: 'from-vega-blue-500 to-vega-green-400',
|
||||||
|
mainClassName: 'from-vega-blue-400 dark:from-vega-blue-600 to-20%',
|
||||||
|
};
|
||||||
|
case DispatchMetric.DISPATCH_METRIC_VALIDATOR_RANKING:
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
gradientClassName: 'from-vega-pink-500 to-vega-purple-400',
|
||||||
|
mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const CardIcon = ({
|
||||||
|
size = 18,
|
||||||
|
iconName,
|
||||||
|
tooltip,
|
||||||
|
}: {
|
||||||
|
size?: VegaIconSize;
|
||||||
|
iconName: VegaIconNames;
|
||||||
|
tooltip: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Tooltip description={<span>{tooltip}</span>}>
|
||||||
|
<span className="flex items-center p-2 rounded-full border border-gray-600">
|
||||||
|
<VegaIcon name={iconName} size={size} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EntityIcon = ({
|
||||||
|
transfer,
|
||||||
|
size = 18,
|
||||||
|
}: {
|
||||||
|
transfer: Transfer;
|
||||||
|
size?: VegaIconSize;
|
||||||
|
}) => {
|
||||||
|
if (transfer.kind.__typename !== 'RecurringTransfer') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const entityScope = transfer.kind.dispatchStrategy?.entityScope;
|
||||||
|
const getIconName = () => {
|
||||||
|
switch (entityScope) {
|
||||||
|
case EntityScope.ENTITY_SCOPE_TEAMS:
|
||||||
|
return VegaIconNames.TEAM;
|
||||||
|
case EntityScope.ENTITY_SCOPE_INDIVIDUALS:
|
||||||
|
return VegaIconNames.MAN;
|
||||||
|
default:
|
||||||
|
return VegaIconNames.QUESTION_MARK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const iconName = getIconName();
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<span>{entityScope ? EntityScopeMapping[entityScope] : ''}</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="flex items-center p-2 rounded-full border border-gray-600">
|
||||||
|
{iconName && <VegaIcon name={iconName} size={size} />}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -33,6 +33,10 @@ import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
|||||||
import { RewardsHistoryContainer } from './rewards-history';
|
import { RewardsHistoryContainer } from './rewards-history';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import { useAssetsMapProvider } from '@vegaprotocol/assets';
|
import { useAssetsMapProvider } from '@vegaprotocol/assets';
|
||||||
|
import { ActiveRewards } from './active-rewards';
|
||||||
|
import { ActivityStreak } from './streaks/activity-streaks';
|
||||||
|
import { RewardHoarderBonus } from './streaks/reward-hoarder-bonus';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA = [
|
const ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA = [
|
||||||
'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba', // USDT mainnet
|
'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba', // USDT mainnet
|
||||||
@@ -45,6 +49,7 @@ export const RewardsContainer = () => {
|
|||||||
const { params, loading: paramsLoading } = useNetworkParams([
|
const { params, loading: paramsLoading } = useNetworkParams([
|
||||||
NetworkParams.reward_asset,
|
NetworkParams.reward_asset,
|
||||||
NetworkParams.rewards_activityStreak_benefitTiers,
|
NetworkParams.rewards_activityStreak_benefitTiers,
|
||||||
|
NetworkParams.rewards_vesting_benefitTiers,
|
||||||
NetworkParams.rewards_vesting_baseRate,
|
NetworkParams.rewards_vesting_baseRate,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -54,6 +59,14 @@ export const RewardsContainer = () => {
|
|||||||
|
|
||||||
const { data: epochData } = useRewardsEpochQuery();
|
const { data: epochData } = useRewardsEpochQuery();
|
||||||
|
|
||||||
|
const { rewards_activityStreak_benefitTiers, rewards_vesting_benefitTiers } =
|
||||||
|
params || {};
|
||||||
|
|
||||||
|
const activityStreakBenefitTiers = JSON.parse(
|
||||||
|
rewards_activityStreak_benefitTiers
|
||||||
|
);
|
||||||
|
const vestingBenefitTiers = JSON.parse(rewards_vesting_benefitTiers);
|
||||||
|
|
||||||
// No need to specify the fromEpoch as it will by default give you the last
|
// No need to specify the fromEpoch as it will by default give you the last
|
||||||
// Note activityStreak in query will fail
|
// Note activityStreak in query will fail
|
||||||
const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({
|
const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({
|
||||||
@@ -68,6 +81,9 @@ export const RewardsContainer = () => {
|
|||||||
pollInterval: 10000,
|
pollInterval: 10000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const partyActivityStreak = rewardsData?.party?.activityStreak;
|
||||||
|
const vestingDetails = rewardsData?.party?.vestingStats;
|
||||||
|
|
||||||
if (!epochData?.epoch || !assetMap) return null;
|
if (!epochData?.epoch || !assetMap) return null;
|
||||||
|
|
||||||
const loading = paramsLoading || accountsLoading || rewardsLoading;
|
const loading = paramsLoading || accountsLoading || rewardsLoading;
|
||||||
@@ -114,74 +130,98 @@ export const RewardsContainer = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid auto-rows-min grid-cols-6 gap-3">
|
<div className="flex flex-col w-full gap-3">
|
||||||
{/* Always show reward information for vega */}
|
<div className="grid auto-rows-min grid-cols-6 gap-3">
|
||||||
<Card
|
{/* Always show reward information for vega */}
|
||||||
key={params.reward_asset}
|
<Card
|
||||||
title={t('Vega Reward pot')}
|
key={params.reward_asset}
|
||||||
className="lg:col-span-3 xl:col-span-2"
|
title={t('Vega Reward pot')}
|
||||||
loading={loading}
|
className="lg:col-span-3 xl:col-span-2"
|
||||||
highlight={true}
|
loading={loading}
|
||||||
>
|
highlight={true}
|
||||||
<RewardPot
|
>
|
||||||
pubKey={pubKey}
|
<RewardPot
|
||||||
accounts={accounts}
|
pubKey={pubKey}
|
||||||
assetId={params.reward_asset}
|
accounts={accounts}
|
||||||
vestingBalancesSummary={rewardsData?.party?.vestingBalancesSummary}
|
assetId={params.reward_asset}
|
||||||
/>
|
vestingBalancesSummary={rewardsData?.party?.vestingBalancesSummary}
|
||||||
</Card>
|
/>
|
||||||
<Card
|
</Card>
|
||||||
title={t('Vesting')}
|
<Card
|
||||||
className="lg:col-span-3 xl:col-span-2"
|
title={t('Vesting')}
|
||||||
loading={loading}
|
className="lg:col-span-3 xl:col-span-2"
|
||||||
>
|
loading={loading}
|
||||||
<Vesting
|
>
|
||||||
pubKey={pubKey}
|
<Vesting
|
||||||
baseRate={params.rewards_vesting_baseRate}
|
pubKey={pubKey}
|
||||||
multiplier={
|
baseRate={params.rewards_vesting_baseRate}
|
||||||
rewardsData?.party?.activityStreak?.rewardVestingMultiplier
|
multiplier={
|
||||||
}
|
rewardsData?.party?.activityStreak?.rewardVestingMultiplier
|
||||||
/>
|
}
|
||||||
</Card>
|
/>
|
||||||
<Card
|
</Card>
|
||||||
title={t('Rewards multipliers')}
|
<Card
|
||||||
className="lg:col-span-3 xl:col-span-2"
|
title={t('Rewards multipliers')}
|
||||||
loading={loading}
|
className="lg:col-span-3 xl:col-span-2"
|
||||||
highlight={true}
|
loading={loading}
|
||||||
>
|
highlight={true}
|
||||||
<Multipliers
|
>
|
||||||
pubKey={pubKey}
|
<Multipliers
|
||||||
hoarderMultiplier={
|
pubKey={pubKey}
|
||||||
rewardsData?.party?.vestingStats?.rewardBonusMultiplier
|
hoarderMultiplier={
|
||||||
}
|
rewardsData?.party?.vestingStats?.rewardBonusMultiplier
|
||||||
streakMultiplier={
|
}
|
||||||
rewardsData?.party?.activityStreak?.rewardDistributionMultiplier
|
streakMultiplier={
|
||||||
}
|
rewardsData?.party?.activityStreak?.rewardDistributionMultiplier
|
||||||
/>
|
}
|
||||||
</Card>
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Show all other reward pots, most of the time users will not have other rewards */}
|
{/* Show all other reward pots, most of the time users will not have other rewards */}
|
||||||
{assets
|
{assets
|
||||||
.filter((assetId) => assetId !== params.reward_asset)
|
.filter((assetId) => assetId !== params.reward_asset)
|
||||||
.map((assetId) => {
|
.map((assetId) => {
|
||||||
const asset = assetMap ? assetMap[assetId] : null;
|
const asset = assetMap ? assetMap[assetId] : null;
|
||||||
|
|
||||||
if (!asset) return null;
|
if (!asset) return null;
|
||||||
|
|
||||||
// Following code is for mitigating an issue due to a core bug where locked and vesting
|
// Following code is for mitigating an issue due to a core bug where locked and vesting
|
||||||
// balances were incorrectly increased for infrastructure rewards for USDT on mainnet
|
// balances were incorrectly increased for infrastructure rewards for USDT on mainnet
|
||||||
//
|
//
|
||||||
// We don't want to incorrectly show the wring locked/vesting values, but we DO want to
|
// We don't want to incorrectly show the wring locked/vesting values, but we DO want to
|
||||||
// show the user that they have rewards available to withdraw
|
// show the user that they have rewards available to withdraw
|
||||||
if (ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA.includes(asset.id)) {
|
if (ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA.includes(asset.id)) {
|
||||||
const accountsForAsset = rewardAccountsAssetMap[asset.id];
|
const accountsForAsset = rewardAccountsAssetMap[asset.id];
|
||||||
const vestedAccount = accountsForAsset?.find(
|
const vestedAccount = accountsForAsset?.find(
|
||||||
(a) => a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
(a) => a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||||
);
|
);
|
||||||
|
|
||||||
// No vested rewards available to withdraw, so skip over USDT
|
// No vested rewards available to withdraw, so skip over USDT
|
||||||
if (!vestedAccount || Number(vestedAccount.balance) <= 0) {
|
if (!vestedAccount || Number(vestedAccount.balance) <= 0) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={assetId}
|
||||||
|
title={t('{{assetSymbol}} Reward pot', {
|
||||||
|
assetSymbol: asset.symbol,
|
||||||
|
})}
|
||||||
|
className="lg:col-span-3 xl:col-span-2"
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
<RewardPot
|
||||||
|
pubKey={pubKey}
|
||||||
|
accounts={accounts}
|
||||||
|
assetId={assetId}
|
||||||
|
// Ensure that these values are shown as 0
|
||||||
|
vestingBalancesSummary={{
|
||||||
|
lockedBalances: [],
|
||||||
|
vestingBalances: [],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -197,48 +237,69 @@ export const RewardsContainer = () => {
|
|||||||
pubKey={pubKey}
|
pubKey={pubKey}
|
||||||
accounts={accounts}
|
accounts={accounts}
|
||||||
assetId={assetId}
|
assetId={assetId}
|
||||||
// Ensure that these values are shown as 0
|
vestingBalancesSummary={
|
||||||
vestingBalancesSummary={{
|
rewardsData?.party?.vestingBalancesSummary
|
||||||
lockedBalances: [],
|
}
|
||||||
vestingBalances: [],
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
|
</div>
|
||||||
return (
|
<div className="grid auto-rows-min grid-cols-6 gap-3">
|
||||||
<Card
|
{pubKey && activityStreakBenefitTiers.tiers?.length > 0 && (
|
||||||
key={assetId}
|
<Card
|
||||||
title={t('{{assetSymbol}} Reward pot', {
|
title={t('Activity Streak')}
|
||||||
assetSymbol: asset.symbol,
|
className={classNames(
|
||||||
})}
|
{
|
||||||
className="lg:col-span-3 xl:col-span-2"
|
'lg:col-span-6 xl:col-span-3':
|
||||||
loading={loading}
|
activityStreakBenefitTiers.tiers.length <= 4,
|
||||||
>
|
'xl:col-span-6': activityStreakBenefitTiers.tiers.length > 4,
|
||||||
<RewardPot
|
},
|
||||||
pubKey={pubKey}
|
'hidden md:block'
|
||||||
accounts={accounts}
|
)}
|
||||||
assetId={assetId}
|
>
|
||||||
vestingBalancesSummary={
|
<span className="flex flex-col mr-8 pr-4">
|
||||||
rewardsData?.party?.vestingBalancesSummary
|
<ActivityStreak
|
||||||
}
|
tiers={activityStreakBenefitTiers.tiers}
|
||||||
|
streak={partyActivityStreak}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</span>
|
||||||
);
|
</Card>
|
||||||
})}
|
)}
|
||||||
<Card
|
{pubKey && vestingBenefitTiers.tiers?.length > 0 && (
|
||||||
title={t('Rewards history')}
|
<Card
|
||||||
className="lg:col-span-full"
|
title={t('Reward Hoarder Bonus')}
|
||||||
loading={rewardsLoading}
|
className={classNames(
|
||||||
noBackgroundOnMobile={true}
|
{
|
||||||
>
|
'lg:col-span-6 xl:col-span-3':
|
||||||
<RewardsHistoryContainer
|
vestingBenefitTiers.tiers.length <= 4,
|
||||||
epoch={Number(epochData?.epoch.id)}
|
'xl:col-span-6': vestingBenefitTiers.tiers.length > 4,
|
||||||
pubKey={pubKey}
|
},
|
||||||
assets={assetMap}
|
'hidden md:block'
|
||||||
/>
|
)}
|
||||||
</Card>
|
>
|
||||||
|
<span className="flex flex-col mr-8 pr-4">
|
||||||
|
<RewardHoarderBonus
|
||||||
|
tiers={vestingBenefitTiers.tiers}
|
||||||
|
vestingDetails={vestingDetails}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
<ActiveRewards currentEpoch={Number(epochData?.epoch.id)} />
|
||||||
|
<Card
|
||||||
|
title={t('Rewards history')}
|
||||||
|
className="lg:col-span-full hidden md:block"
|
||||||
|
loading={rewardsLoading}
|
||||||
|
noBackgroundOnMobile={true}
|
||||||
|
>
|
||||||
|
<RewardsHistoryContainer
|
||||||
|
epoch={Number(epochData?.epoch.id)}
|
||||||
|
pubKey={pubKey}
|
||||||
|
assets={assetMap}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -343,7 +404,7 @@ export const RewardPot = ({
|
|||||||
})}
|
})}
|
||||||
<VegaIcon name={VegaIconNames.LOCK} size={12} />
|
<VegaIcon name={VegaIconNames.LOCK} size={12} />
|
||||||
</CardTableTH>
|
</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD data-testid="locked-value">
|
||||||
{addDecimalsFormatNumberQuantum(
|
{addDecimalsFormatNumberQuantum(
|
||||||
totalLocked.toString(),
|
totalLocked.toString(),
|
||||||
rewardAsset.decimals,
|
rewardAsset.decimals,
|
||||||
@@ -357,7 +418,7 @@ export const RewardPot = ({
|
|||||||
assetSymbol: rewardAsset.symbol,
|
assetSymbol: rewardAsset.symbol,
|
||||||
})}
|
})}
|
||||||
</CardTableTH>
|
</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD data-testid="vesting-value">
|
||||||
{addDecimalsFormatNumberQuantum(
|
{addDecimalsFormatNumberQuantum(
|
||||||
totalVesting.toString(),
|
totalVesting.toString(),
|
||||||
rewardAsset.decimals,
|
rewardAsset.decimals,
|
||||||
@@ -369,7 +430,7 @@ export const RewardPot = ({
|
|||||||
<CardTableTH>
|
<CardTableTH>
|
||||||
{t('Available to withdraw this epoch')}
|
{t('Available to withdraw this epoch')}
|
||||||
</CardTableTH>
|
</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD data-testid="available-to-withdraw-value">
|
||||||
{addDecimalsFormatNumberQuantum(
|
{addDecimalsFormatNumberQuantum(
|
||||||
totalVestedRewardsByRewardAsset.toString(),
|
totalVestedRewardsByRewardAsset.toString(),
|
||||||
rewardAsset.decimals,
|
rewardAsset.decimals,
|
||||||
@@ -388,6 +449,7 @@ export const RewardPot = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
size="small"
|
size="small"
|
||||||
|
data-testid="redeem-rewards-button"
|
||||||
>
|
>
|
||||||
{t('Redeem rewards')}
|
{t('Redeem rewards')}
|
||||||
</TradingButton>
|
</TradingButton>
|
||||||
@@ -422,12 +484,16 @@ export const Vesting = ({
|
|||||||
<CardTable>
|
<CardTable>
|
||||||
<tr>
|
<tr>
|
||||||
<CardTableTH>{t('Base rate')}</CardTableTH>
|
<CardTableTH>{t('Base rate')}</CardTableTH>
|
||||||
<CardTableTD>{baseRateFormatted}%</CardTableTD>
|
<CardTableTD data-testid="base-rate-value">
|
||||||
|
{baseRateFormatted}%
|
||||||
|
</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
{pubKey && (
|
{pubKey && (
|
||||||
<tr>
|
<tr>
|
||||||
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
|
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
|
||||||
<CardTableTD>{multiplier ? `${multiplier}x` : '-'}</CardTableTD>
|
<CardTableTD data-testid="vesting multiplier-value">
|
||||||
|
{multiplier ? `${multiplier}x` : '-'}
|
||||||
|
</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</CardTable>
|
</CardTable>
|
||||||
@@ -467,13 +533,13 @@ export const Multipliers = ({
|
|||||||
<CardTable>
|
<CardTable>
|
||||||
<tr>
|
<tr>
|
||||||
<CardTableTH>{t('Streak reward multiplier')}</CardTableTH>
|
<CardTableTH>{t('Streak reward multiplier')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD data-testid="streak-reward-multiplier-value">
|
||||||
{streakMultiplier ? `${streakMultiplier}x` : '-'}
|
{streakMultiplier ? `${streakMultiplier}x` : '-'}
|
||||||
</CardTableTD>
|
</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<CardTableTH>{t('Hoarder reward multiplier')}</CardTableTH>
|
<CardTableTH>{t('Hoarder reward multiplier')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD data-testid="hoarder-reward-multiplier-value">
|
||||||
{hoarderMultiplier ? `${hoarderMultiplier}x` : '-'}
|
{hoarderMultiplier ? `${hoarderMultiplier}x` : '-'}
|
||||||
</CardTableTD>
|
</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ export const RewardHistoryTable = ({
|
|||||||
onClick={() => setIsParty(false)}
|
onClick={() => setIsParty(false)}
|
||||||
size="extra-small"
|
size="extra-small"
|
||||||
minimal={isParty}
|
minimal={isParty}
|
||||||
|
data-testid="total-distributed-button"
|
||||||
>
|
>
|
||||||
{t('Total distributed')}
|
{t('Total distributed')}
|
||||||
</TradingButton>
|
</TradingButton>
|
||||||
@@ -327,6 +328,7 @@ export const RewardHistoryTable = ({
|
|||||||
size="extra-small"
|
size="extra-small"
|
||||||
disabled={!pubKey}
|
disabled={!pubKey}
|
||||||
minimal={!isParty}
|
minimal={!isParty}
|
||||||
|
data-testid="earned-by-me-button"
|
||||||
>
|
>
|
||||||
{t('Earned by me')}
|
{t('Earned by me')}
|
||||||
</TradingButton>
|
</TradingButton>
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { ActivityStreak } from './activity-streaks';
|
||||||
|
|
||||||
|
describe('ActivityStreak', () => {
|
||||||
|
it('renders null when streak is not active', () => {
|
||||||
|
const tiers: {
|
||||||
|
minimum_activity_streak: number;
|
||||||
|
reward_multiplier: string;
|
||||||
|
vesting_multiplier: string;
|
||||||
|
}[] = [];
|
||||||
|
const streak = null;
|
||||||
|
render(<ActivityStreak tiers={tiers} streak={streak} />);
|
||||||
|
const component = screen.queryByText(/epochs streak/i);
|
||||||
|
expect(component).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders null when tiers are empty', () => {
|
||||||
|
const tiers: {
|
||||||
|
minimum_activity_streak: number;
|
||||||
|
reward_multiplier: string;
|
||||||
|
vesting_multiplier: string;
|
||||||
|
}[] = [];
|
||||||
|
const streak = {
|
||||||
|
activeFor: 10,
|
||||||
|
isActive: true,
|
||||||
|
inactiveFor: 10,
|
||||||
|
rewardDistributionMultiplier: '45678',
|
||||||
|
rewardVestingMultiplier: '45678',
|
||||||
|
epoch: 10,
|
||||||
|
tradedVolume: '45678',
|
||||||
|
openVolume: '45678',
|
||||||
|
};
|
||||||
|
render(<ActivityStreak tiers={tiers} streak={streak} />);
|
||||||
|
const component = screen.queryByText(/epochs streak/i);
|
||||||
|
expect(component).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the component with tiers and active streak', () => {
|
||||||
|
const tiers = [
|
||||||
|
{
|
||||||
|
minimum_activity_streak: 5,
|
||||||
|
reward_multiplier: '1.5x',
|
||||||
|
vesting_multiplier: '2x',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimum_activity_streak: 10,
|
||||||
|
reward_multiplier: '2x',
|
||||||
|
vesting_multiplier: '3x',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const streak = {
|
||||||
|
activeFor: 7,
|
||||||
|
isActive: true,
|
||||||
|
inactiveFor: 10,
|
||||||
|
rewardDistributionMultiplier: '45678',
|
||||||
|
rewardVestingMultiplier: '45678',
|
||||||
|
epoch: 10,
|
||||||
|
tradedVolume: '45678',
|
||||||
|
openVolume: '45678',
|
||||||
|
};
|
||||||
|
render(<ActivityStreak tiers={tiers} streak={streak} />);
|
||||||
|
|
||||||
|
const tierLabels = screen.getAllByText(/Tier/i);
|
||||||
|
expect(tierLabels.length).toBe(3); // 2 tiers + 1 label
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useT } from '../../../lib/use-t';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { PartyActivityStreak } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
export const safeProgress = (
|
||||||
|
i: number,
|
||||||
|
userTierIndex: number,
|
||||||
|
total: number | string,
|
||||||
|
progress?: number | string
|
||||||
|
) => {
|
||||||
|
if (i < userTierIndex) return 100;
|
||||||
|
if (i > userTierIndex) return 0;
|
||||||
|
|
||||||
|
if (!progress || !total) return 0;
|
||||||
|
if (new BigNumber(progress).isGreaterThan(total)) return 100;
|
||||||
|
return new BigNumber(progress)
|
||||||
|
.multipliedBy(100)
|
||||||
|
.dividedBy(total || 1)
|
||||||
|
.toNumber();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetUserTier = (
|
||||||
|
tiers: {
|
||||||
|
minimum_activity_streak: number;
|
||||||
|
reward_multiplier: string;
|
||||||
|
vesting_multiplier: string;
|
||||||
|
}[],
|
||||||
|
progress?: number
|
||||||
|
) => {
|
||||||
|
if (!progress) return 0;
|
||||||
|
if (!tiers || tiers.length === 0) return 0;
|
||||||
|
|
||||||
|
let userTier = 0;
|
||||||
|
let i = 0;
|
||||||
|
while (
|
||||||
|
i < tiers.length &&
|
||||||
|
tiers[userTier].minimum_activity_streak < progress
|
||||||
|
) {
|
||||||
|
userTier = i;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
i === tiers.length &&
|
||||||
|
tiers[userTier].minimum_activity_streak <= progress
|
||||||
|
) {
|
||||||
|
userTier = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userTier > tiers.length) {
|
||||||
|
userTier--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userTier;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ActivityStreak = ({
|
||||||
|
tiers,
|
||||||
|
streak,
|
||||||
|
}: {
|
||||||
|
tiers: {
|
||||||
|
minimum_activity_streak: number;
|
||||||
|
reward_multiplier: string;
|
||||||
|
vesting_multiplier: string;
|
||||||
|
}[];
|
||||||
|
streak?: PartyActivityStreak | null;
|
||||||
|
}) => {
|
||||||
|
const t = useT();
|
||||||
|
const userTierIndex = useGetUserTier(tiers, streak?.activeFor);
|
||||||
|
|
||||||
|
if (!tiers || tiers.length === 0) return null;
|
||||||
|
|
||||||
|
const progressBarHeight = 'h-10';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-1 w-full">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div
|
||||||
|
className="grid"
|
||||||
|
style={{
|
||||||
|
gridTemplateColumns:
|
||||||
|
'repeat(' + tiers.length + ', minmax(0, 1fr))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tiers.map((tier, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className="flex justify-end -mr-[2.85rem]">
|
||||||
|
<span className="flex flex-col items-center gap-4 justify-between">
|
||||||
|
<span className="flex flex-col items-center gap-1">
|
||||||
|
<span className="flex flex-col items-center font-medium">
|
||||||
|
<span className="text-sm">
|
||||||
|
{t('Tier {{tier}}', {
|
||||||
|
tier: index + 1,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted text-xs">
|
||||||
|
{t('numberEpochs', '{{count}} epochs', {
|
||||||
|
count: tier.minimum_activity_streak,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'text-xs flex flex-col items-center justify-center px-2 py-1 rounded-lg text-white border',
|
||||||
|
{
|
||||||
|
'border-pink-600 bg-pink-900': index % 6 === 0,
|
||||||
|
'border-purple-600 bg-purple-900': index % 6 === 1,
|
||||||
|
'border-blue-600 bg-blue-900': index % 6 === 2,
|
||||||
|
'border-orange-600 bg-orange-900': index % 6 === 3,
|
||||||
|
'border-green-600 bg-green-900': index % 6 === 4,
|
||||||
|
'border-yellow-600 bg-yellow-900': index % 6 === 5,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{t('Reward {{reward}}x', {
|
||||||
|
reward: tier.reward_multiplier,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{t('Vesting {{vesting}}x', {
|
||||||
|
vesting: tier.vesting_multiplier,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
{
|
||||||
|
'text-pink-500': index % 6 === 0,
|
||||||
|
'text-purple-500': index % 6 === 1,
|
||||||
|
'text-blue-500': index % 6 === 2,
|
||||||
|
'text-orange-500': index % 6 === 3,
|
||||||
|
'text-green-500': index % 6 === 4,
|
||||||
|
'text-yellow-500': index % 6 === 5,
|
||||||
|
},
|
||||||
|
'leading-[0] font-sans text-[48px]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
•
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{tiers.map((_tier, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="bg-white dark:bg-gray-800 shadow-card rounded-[100px] grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'relative w-full rounded-[100px] bg-gray-200 dark:bg-gray-800',
|
||||||
|
progressBarHeight
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'absolute left-0 top-0 h-full rounded-[100px] bg-gradient-to-r',
|
||||||
|
{
|
||||||
|
'from-vega-dark-400 to-vega-dark-200':
|
||||||
|
userTierIndex === 0 || streak?.isActive === false,
|
||||||
|
'from-vega-pink-600 to-vega-pink-500':
|
||||||
|
userTierIndex % 6 === 1,
|
||||||
|
'from-vega-purple-600 to-vega-purple-500':
|
||||||
|
userTierIndex % 6 === 2,
|
||||||
|
'from-vega-blue-600 to-vega-blue-500':
|
||||||
|
userTierIndex % 6 === 3,
|
||||||
|
'from-vega-orange-600 to-vega-orange-500':
|
||||||
|
userTierIndex % 6 === 4,
|
||||||
|
'from-vega-green-600 to-vega-green-500':
|
||||||
|
userTierIndex % 6 === 5,
|
||||||
|
'from-vega-yellow-600 to-vega-yellow-500':
|
||||||
|
userTierIndex % 6 === 0,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
width:
|
||||||
|
safeProgress(
|
||||||
|
index,
|
||||||
|
userTierIndex,
|
||||||
|
tiers[index].minimum_activity_streak,
|
||||||
|
streak?.activeFor
|
||||||
|
) + '%',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<VegaIcon name={VegaIconNames.STREAK} />
|
||||||
|
|
||||||
|
<span className="flex flex-col">
|
||||||
|
{streak?.isActive && (
|
||||||
|
<span data-testid="epoch-streak">
|
||||||
|
{t('userActive', '{{active}} trader: {{count}} epochs so far', {
|
||||||
|
active: streak?.isActive ? 'Active' : 'Inactive',
|
||||||
|
count: streak?.activeFor || 0,
|
||||||
|
})}{' '}
|
||||||
|
{userTierIndex > 0 &&
|
||||||
|
new BigNumber(
|
||||||
|
tiers[0].minimum_activity_streak
|
||||||
|
).isLessThanOrEqualTo(streak?.activeFor || 0) &&
|
||||||
|
t('(Tier {{tier}} as of last epoch)', {
|
||||||
|
tier: userTierIndex,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { RewardHoarderBonus } from './reward-hoarder-bonus';
|
||||||
|
import type { PartyVestingStats } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
describe('RewardHoarderBonus', () => {
|
||||||
|
it('renders null when vestingDetails is not provided', () => {
|
||||||
|
const tiers: {
|
||||||
|
minimum_quantum_balance: string;
|
||||||
|
reward_multiplier: string;
|
||||||
|
}[] = [];
|
||||||
|
const vestingDetails = null;
|
||||||
|
render(
|
||||||
|
<RewardHoarderBonus tiers={tiers} vestingDetails={vestingDetails} />
|
||||||
|
);
|
||||||
|
const component = screen.queryByText(/Reward bonus/i);
|
||||||
|
expect(component).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders null when tiers are empty', () => {
|
||||||
|
const tiers: {
|
||||||
|
minimum_quantum_balance: string;
|
||||||
|
reward_multiplier: string;
|
||||||
|
}[] = [];
|
||||||
|
const vestingDetails: PartyVestingStats = {
|
||||||
|
epochSeq: 0,
|
||||||
|
rewardBonusMultiplier: '1.5',
|
||||||
|
quantumBalance: '100',
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<RewardHoarderBonus tiers={tiers} vestingDetails={vestingDetails} />
|
||||||
|
);
|
||||||
|
const component = screen.queryByText(/Reward bonus/i);
|
||||||
|
expect(component).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the component with tiers and vestingDetails', () => {
|
||||||
|
const tiers = [
|
||||||
|
{
|
||||||
|
minimum_quantum_balance: '50',
|
||||||
|
reward_multiplier: '1.5x',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimum_quantum_balance: '100',
|
||||||
|
reward_multiplier: '2x',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const vestingDetails = {
|
||||||
|
epochSeq: 0,
|
||||||
|
rewardBonusMultiplier: '1.5',
|
||||||
|
quantumBalance: '75',
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<RewardHoarderBonus tiers={tiers} vestingDetails={vestingDetails} />
|
||||||
|
);
|
||||||
|
const tierLabels = screen.getAllByText(/Tier/i);
|
||||||
|
expect(tierLabels.length).toBe(3); // 2 tiers + 1 label
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useT } from '../../../lib/use-t';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type { PartyVestingStats } from '@vegaprotocol/types';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { formatNumber } from '@vegaprotocol/utils';
|
||||||
|
import { safeProgress } from './activity-streaks';
|
||||||
|
|
||||||
|
export const useGetUserTier = (
|
||||||
|
tiers: {
|
||||||
|
minimum_quantum_balance: string;
|
||||||
|
reward_multiplier: string;
|
||||||
|
}[],
|
||||||
|
progress?: number | string
|
||||||
|
) => {
|
||||||
|
if (!progress) return 0;
|
||||||
|
if (!tiers || tiers.length === 0) return 0;
|
||||||
|
|
||||||
|
let userTier = 0;
|
||||||
|
let i = 0;
|
||||||
|
let minProgress = '0';
|
||||||
|
while (i < tiers.length && new BigNumber(minProgress).isLessThan(progress)) {
|
||||||
|
userTier = i;
|
||||||
|
i++;
|
||||||
|
minProgress = tiers[userTier].minimum_quantum_balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
i === tiers.length &&
|
||||||
|
new BigNumber(minProgress).isLessThanOrEqualTo(progress)
|
||||||
|
) {
|
||||||
|
userTier = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userTier > tiers.length) {
|
||||||
|
userTier--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userTier;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RewardHoarderBonus = ({
|
||||||
|
tiers,
|
||||||
|
vestingDetails,
|
||||||
|
}: {
|
||||||
|
tiers: {
|
||||||
|
minimum_quantum_balance: string;
|
||||||
|
reward_multiplier: string;
|
||||||
|
}[];
|
||||||
|
vestingDetails?: PartyVestingStats | null;
|
||||||
|
}) => {
|
||||||
|
const t = useT();
|
||||||
|
|
||||||
|
const userTierIndex = useGetUserTier(tiers, vestingDetails?.quantumBalance);
|
||||||
|
|
||||||
|
if (!tiers || tiers.length === 0) return null;
|
||||||
|
|
||||||
|
// There is only value to compare to the tiers that covers all the user' rewards across all assets
|
||||||
|
const qUSD = 'qUSD';
|
||||||
|
|
||||||
|
const progressBarHeight = 'h-10';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-1 w-full">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div
|
||||||
|
className="grid"
|
||||||
|
style={{
|
||||||
|
gridTemplateColumns:
|
||||||
|
'repeat(' + tiers.length + ', minmax(0, 1fr))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tiers.map((tier, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className="flex justify-end -mr-[2.95rem]">
|
||||||
|
<span className="flex flex-col items-center gap-4 justify-between">
|
||||||
|
<span className="flex flex-col items-center gap-1">
|
||||||
|
<span className="flex flex-col items-center font-medium">
|
||||||
|
<span className="text-sm">
|
||||||
|
{t('Tier {{tier}}', {
|
||||||
|
tier: index + 1,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted text-xs">
|
||||||
|
{formatNumber(tier.minimum_quantum_balance)} {qUSD}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'text-xs flex flex-col items-center justify-center px-2 py-1 rounded-lg text-white border',
|
||||||
|
{
|
||||||
|
'border-pink-600 bg-pink-900': index % 6 === 0,
|
||||||
|
'border-purple-600 bg-purple-900': index % 6 === 1,
|
||||||
|
'border-blue-600 bg-blue-900': index % 6 === 2,
|
||||||
|
'border-orange-600 bg-orange-900': index % 6 === 3,
|
||||||
|
'border-green-600 bg-green-900': index % 6 === 4,
|
||||||
|
'border-yellow-600 bg-yellow-900': index % 6 === 5,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{t('Reward bonus')}</span>
|
||||||
|
<span>
|
||||||
|
{t('{{reward}}x', {
|
||||||
|
reward: tier.reward_multiplier,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
{
|
||||||
|
'text-pink-500': index % 6 === 0,
|
||||||
|
'text-purple-500': index % 6 === 1,
|
||||||
|
'text-blue-500': index % 6 === 2,
|
||||||
|
'text-orange-500': index % 6 === 3,
|
||||||
|
'text-green-500': index % 6 === 4,
|
||||||
|
'text-yellow-500': index % 6 === 5,
|
||||||
|
},
|
||||||
|
'leading-[0] font-sans text-[48px]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
•
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{tiers.map((_tier, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="bg-white dark:bg-gray-800 shadow-card rounded-[100px] grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'relative w-full rounded-[100px] bg-gray-200 dark:bg-gray-800',
|
||||||
|
progressBarHeight
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'absolute left-0 top-0 h-full rounded-[100px] bg-gradient-to-r',
|
||||||
|
{
|
||||||
|
'from-vega-dark-400 to-vega-dark-200':
|
||||||
|
userTierIndex === 0,
|
||||||
|
'from-vega-pink-600 to-vega-pink-500':
|
||||||
|
userTierIndex % 6 === 1,
|
||||||
|
'from-vega-purple-600 to-vega-purple-500':
|
||||||
|
userTierIndex % 6 === 2,
|
||||||
|
'from-vega-blue-600 to-vega-blue-500':
|
||||||
|
userTierIndex % 6 === 3,
|
||||||
|
'from-vega-orange-600 to-vega-orange-500':
|
||||||
|
userTierIndex % 6 === 4,
|
||||||
|
'from-vega-green-600 to-vega-green-500':
|
||||||
|
userTierIndex % 6 === 5,
|
||||||
|
'from-vega-yellow-600 to-vega-yellow-500':
|
||||||
|
userTierIndex % 6 === 0,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
width:
|
||||||
|
safeProgress(
|
||||||
|
index,
|
||||||
|
userTierIndex,
|
||||||
|
tiers[index].minimum_quantum_balance,
|
||||||
|
vestingDetails?.quantumBalance
|
||||||
|
) + '%',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<VegaIcon name={VegaIconNames.STREAK} />
|
||||||
|
<span data-testid="hoarder-bonus-total-hoarded">
|
||||||
|
{formatNumber(vestingDetails?.quantumBalance || 0)} {qUSD}{' '}
|
||||||
|
{userTierIndex > 0 &&
|
||||||
|
new BigNumber(
|
||||||
|
tiers[0].minimum_quantum_balance
|
||||||
|
).isLessThanOrEqualTo(vestingDetails?.quantumBalance || 0) &&
|
||||||
|
t('(Tier {{tier}} as of last epoch)', { tier: userTierIndex })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
|
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
|
||||||
VEGA_VERSION=v0.73.10
|
VEGA_VERSION=v0.74.0-preview.2
|
||||||
LOCAL_SERVER=false
|
LOCAL_SERVER=false
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
|
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
|
||||||
VEGA_VERSION=v0.73.10
|
VEGA_VERSION=v0.74.0-preview.2
|
||||||
LOCAL_SERVER=false
|
LOCAL_SERVER=false
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import docker
|
|||||||
import http.server
|
import http.server
|
||||||
import sys
|
import sys
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
from playwright.sync_api import Error as PlaywrightError
|
||||||
from docker.models.containers import Container
|
from docker.models.containers import Container
|
||||||
from docker.errors import APIError
|
from docker.errors import APIError
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
@@ -274,3 +274,18 @@ def perps_market(vega, request):
|
|||||||
if hasattr(request, "param"):
|
if hasattr(request, "param"):
|
||||||
kwargs.update(request.param)
|
kwargs.update(request.param)
|
||||||
return setup_perps_market(vega, **kwargs)
|
return setup_perps_market(vega, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def retry_on_http_error(request):
|
||||||
|
retry_count = 3
|
||||||
|
for i in range(retry_count):
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
if i < retry_count - 1:
|
||||||
|
print(f"Retrying due to HTTPError (attempt {i+1}/{retry_count})")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ def setup_simple_market(
|
|||||||
custom_market_name=market_name,
|
custom_market_name=market_name,
|
||||||
custom_asset_name="tDAI",
|
custom_asset_name="tDAI",
|
||||||
custom_asset_symbol="tDAI",
|
custom_asset_symbol="tDAI",
|
||||||
custom_quantum=1
|
custom_quantum=1,
|
||||||
):
|
):
|
||||||
for wallet in wallets:
|
for wallet in wallets:
|
||||||
vega.create_key(wallet.name)
|
vega.create_key(wallet.name)
|
||||||
|
|
||||||
vega.mint(
|
vega.mint(
|
||||||
MM_WALLET.name,
|
MM_WALLET.name,
|
||||||
asset="VOTE",
|
asset=vega.find_asset_id(symbol="VOTE", enabled=True),
|
||||||
amount=mint_amount,
|
amount=mint_amount,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,18 +117,30 @@ def setup_simple_successor_market(
|
|||||||
return market_id
|
return market_id
|
||||||
|
|
||||||
|
|
||||||
def setup_opening_auction_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs):
|
def setup_opening_auction_market(
|
||||||
|
vega: VegaService,
|
||||||
|
market_id: str = None,
|
||||||
|
buy_orders=default_buy_orders,
|
||||||
|
sell_orders=default_sell_orders,
|
||||||
|
add_liquidity=True,
|
||||||
|
custom_market_name="BTC:DAI_2023",
|
||||||
|
custom_asset_name="tDAI",
|
||||||
|
custom_asset_symbol="tDAI",
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
if not market_exists(vega, market_id):
|
if not market_exists(vega, market_id):
|
||||||
market_id = setup_simple_market(vega, **kwargs)
|
market_id = setup_simple_market(
|
||||||
|
vega,
|
||||||
|
custom_market_name=custom_market_name,
|
||||||
|
custom_asset_name=custom_asset_name,
|
||||||
|
custom_asset_symbol=custom_asset_symbol,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
if add_liquidity:
|
if add_liquidity:
|
||||||
submit_liquidity(vega, MM_WALLET.name, market_id)
|
submit_liquidity(vega, MM_WALLET.name, market_id)
|
||||||
submit_multiple_orders(
|
submit_multiple_orders(vega, MM_WALLET.name, market_id, "SIDE_SELL", sell_orders)
|
||||||
vega, MM_WALLET.name, market_id, "SIDE_SELL", sell_orders
|
submit_multiple_orders(vega, MM_WALLET2.name, market_id, "SIDE_BUY", buy_orders)
|
||||||
)
|
|
||||||
submit_multiple_orders(
|
|
||||||
vega, MM_WALLET2.name, market_id, "SIDE_BUY", buy_orders
|
|
||||||
)
|
|
||||||
|
|
||||||
vega.forward("10s")
|
vega.forward("10s")
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
@@ -146,13 +158,37 @@ def market_exists(vega: VegaService, market_id: str):
|
|||||||
|
|
||||||
|
|
||||||
# Add sell orders and buy orders to put on the book
|
# Add sell orders and buy orders to put on the book
|
||||||
def setup_continuous_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs):
|
def setup_continuous_market(
|
||||||
if not market_exists(vega, market_id) or buy_orders != default_buy_orders or sell_orders != default_sell_orders:
|
vega: VegaService,
|
||||||
|
market_id: str = None,
|
||||||
|
buy_orders=default_buy_orders,
|
||||||
|
sell_orders=default_sell_orders,
|
||||||
|
add_liquidity=True,
|
||||||
|
custom_market_name="BTC:DAI_2023",
|
||||||
|
custom_asset_name="tDAI",
|
||||||
|
custom_asset_symbol="tDAI",
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
not market_exists(vega, market_id)
|
||||||
|
or buy_orders != default_buy_orders
|
||||||
|
or sell_orders != default_sell_orders
|
||||||
|
):
|
||||||
market_id = setup_opening_auction_market(
|
market_id = setup_opening_auction_market(
|
||||||
vega, market_id, buy_orders, sell_orders, add_liquidity, **kwargs)
|
vega,
|
||||||
|
market_id,
|
||||||
|
buy_orders,
|
||||||
|
sell_orders,
|
||||||
|
add_liquidity,
|
||||||
|
custom_market_name=custom_market_name,
|
||||||
|
custom_asset_name=custom_asset_name,
|
||||||
|
custom_asset_symbol=custom_asset_symbol,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
submit_order(vega, "Key 1", market_id, "SIDE_BUY",
|
submit_order(
|
||||||
sell_orders[0][0], sell_orders[0][1])
|
vega, "Key 1", market_id, "SIDE_BUY", sell_orders[0][0], sell_orders[0][1]
|
||||||
|
)
|
||||||
|
|
||||||
vega.forward("10s")
|
vega.forward("10s")
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
@@ -171,7 +207,7 @@ def setup_perps_market(
|
|||||||
|
|
||||||
vega.mint(
|
vega.mint(
|
||||||
MM_WALLET.name,
|
MM_WALLET.name,
|
||||||
asset="VOTE",
|
asset=vega.find_asset_id(symbol="VOTE", enabled=True),
|
||||||
amount=mint_amount,
|
amount=mint_amount,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -250,6 +286,8 @@ def setup_perps_market(
|
|||||||
def market_exists(vega: VegaService, market_id: str):
|
def market_exists(vega: VegaService, market_id: str):
|
||||||
if market_id is None:
|
if market_id is None:
|
||||||
return False
|
return False
|
||||||
all_markets = vega.all_markets()
|
all_markets = vega.all_markets()
|
||||||
market_ids = [market.id for market in all_markets]
|
market_ids = [market.id for market in all_markets]
|
||||||
return market_id in market_ids
|
print("Checking for market ID:", market_id)
|
||||||
|
print("Available market IDs:", market_ids)
|
||||||
|
return market_id in market_ids
|
||||||
Generated
+3
-3
@@ -1160,8 +1160,8 @@ profile = ["pytest-profiling", "snakeviz"]
|
|||||||
[package.source]
|
[package.source]
|
||||||
type = "git"
|
type = "git"
|
||||||
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
|
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
|
||||||
reference = "fix/genesis_panic"
|
reference = "HEAD"
|
||||||
resolved_reference = "de30d2d4c7a1b81a830527ca76473e23ef59de12"
|
resolved_reference = "4440abbb6ce0d3e80beba5cd01f20cd21983cbf8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "websocket-client"
|
name = "websocket-client"
|
||||||
@@ -1342,4 +1342,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9,<3.11"
|
python-versions = ">=3.9,<3.11"
|
||||||
content-hash = "68ed0de55290a3b929d47eb7f7b031fb7e172261c7bbeb4f554b7c27a4462754"
|
content-hash = "39ce8400de7bf060857447281ef27bd78c9b1d9639da063b051e3ae6e7887a67"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ packages = [{include = "trading market-sim e2e"}]
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.9,<3.11"
|
python = ">=3.9,<3.11"
|
||||||
psutil = "^5.9.5"
|
psutil = "^5.9.5"
|
||||||
vega-sim = {git = "https://github.com/vegaprotocol/vega-market-sim.git/", branch = "fix/genesis_panic"}
|
vega-sim = {git = "https://github.com/vegaprotocol/vega-market-sim.git/"}
|
||||||
pytest-playwright = "^0.4.2"
|
pytest-playwright = "^0.4.2"
|
||||||
docker = "^6.1.3"
|
docker = "^6.1.3"
|
||||||
pytest-xdist = "^3.3.1"
|
pytest-xdist = "^3.3.1"
|
||||||
|
|||||||
+36
-23
@@ -14,15 +14,18 @@ market_order = "order-type-Market"
|
|||||||
tif = "order-tif"
|
tif = "order-tif"
|
||||||
expire = "expire"
|
expire = "expire"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def vega(request):
|
def vega(request):
|
||||||
with init_vega(request) as vega:
|
with init_vega(request) as vega:
|
||||||
yield vega
|
yield vega
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def continuous_market(vega):
|
def continuous_market(vega):
|
||||||
return setup_continuous_market(vega)
|
return setup_continuous_market(vega)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
def test_limit_buy_order_GTT(continuous_market, vega: VegaServiceNull, page: Page):
|
def test_limit_buy_order_GTT(continuous_market, vega: VegaServiceNull, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
@@ -42,31 +45,26 @@ def test_limit_buy_order_GTT(continuous_market, vega: VegaServiceNull, page: Pag
|
|||||||
)
|
)
|
||||||
page.get_by_test_id(place_order).click()
|
page.get_by_test_id(place_order).click()
|
||||||
wait_for_toast_confirmation(page)
|
wait_for_toast_confirmation(page)
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
page.get_by_test_id("All").click()
|
page.get_by_test_id("All").click()
|
||||||
# 7002-SORD-017
|
# 7002-SORD-017
|
||||||
expect(page.get_by_role("row").nth(2)).to_contain_text(
|
expect(page.get_by_role("row").nth(5)).to_contain_text("10+10LimitFilled120.00GTT:")
|
||||||
"BTC:DAI_2023Futr10+10LimitFilled120.00GTT:"
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
def test_limit_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
|
def test_limit_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
|
|
||||||
page.get_by_test_id(order_size).fill("10")
|
page.get_by_test_id(order_size).fill("10")
|
||||||
page.get_by_test_id(order_price).fill("120")
|
page.get_by_test_id(order_price).fill("120")
|
||||||
page.get_by_test_id(place_order).click()
|
page.get_by_test_id(place_order).click()
|
||||||
wait_for_toast_confirmation(page)
|
wait_for_toast_confirmation(page)
|
||||||
vega.forward("10s")
|
vega.wait_fn(2)
|
||||||
vega.wait_fn(1)
|
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
page.get_by_test_id("All").click()
|
page.get_by_test_id("All").click()
|
||||||
# 7002-SORD-017
|
# 7002-SORD-017
|
||||||
expect(page.get_by_role("row").nth(2)).to_contain_text(
|
expect(page.get_by_role("row").nth(6)).to_contain_text("10+10LimitFilled120.00GTC")
|
||||||
"BTC:DAI_2023Futr10+10LimitFilled120.00GTC"
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
def test_limit_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
|
def test_limit_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
|
||||||
@@ -84,13 +82,11 @@ def test_limit_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
|
|||||||
)
|
)
|
||||||
page.get_by_test_id(place_order).click()
|
page.get_by_test_id(place_order).click()
|
||||||
wait_for_toast_confirmation(page)
|
wait_for_toast_confirmation(page)
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
page.get_by_test_id("All").click()
|
page.get_by_test_id("All").click()
|
||||||
expect(page.get_by_role("row").nth(2)).to_contain_text(
|
expect(page.get_by_role("row").nth(7)).to_contain_text("10-10LimitFilled100.00GFN")
|
||||||
"BTC:DAI_2023Futr10-10LimitFilled100.00GFN"
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
def test_market_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
|
def test_market_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
|
||||||
@@ -107,14 +103,12 @@ def test_market_sell_order(continuous_market, vega: VegaServiceNull, page: Page)
|
|||||||
)
|
)
|
||||||
page.get_by_test_id(place_order).click()
|
page.get_by_test_id(place_order).click()
|
||||||
wait_for_toast_confirmation(page)
|
wait_for_toast_confirmation(page)
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
|
|
||||||
page.get_by_test_id("All").click()
|
page.get_by_test_id("All").click()
|
||||||
expect(page.get_by_role("row").nth(2)).to_contain_text(
|
expect(page.get_by_role("row").nth(8)).to_contain_text("10-10MarketFilled-IOC")
|
||||||
"BTC:DAI_2023Futr10-10MarketFilled-IOC"
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
def test_market_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
|
def test_market_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
|
||||||
@@ -124,13 +118,32 @@ def test_market_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
|
|||||||
page.get_by_test_id(tif).select_option("Fill or Kill (FOK)")
|
page.get_by_test_id(tif).select_option("Fill or Kill (FOK)")
|
||||||
page.get_by_test_id(place_order).click()
|
page.get_by_test_id(place_order).click()
|
||||||
wait_for_toast_confirmation(page)
|
wait_for_toast_confirmation(page)
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
page.get_by_test_id("All").click()
|
page.get_by_test_id("All").click()
|
||||||
# 7002-SORD-010
|
# 7002-SORD-010
|
||||||
# 0003-WTXN-012
|
# 0003-WTXN-012
|
||||||
# 0003-WTXN-003
|
# 0003-WTXN-003
|
||||||
expect(page.get_by_role("row").nth(2)).to_contain_text(
|
expect(page.get_by_role("row").nth(9)).to_contain_text("10+10MarketFilled-FOK")
|
||||||
"BTC:DAI_2023Futr10+10MarketFilled-FOK"
|
|
||||||
)
|
@pytest.mark.usefixtures("risk_accepted")
|
||||||
|
def test_sidebar_should_be_open_after_reload(continuous_market, page: Page):
|
||||||
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
|
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
|
||||||
|
page.get_by_test_id("Order").click()
|
||||||
|
expect(page.get_by_test_id("deal-ticket-form")).not_to_be_visible()
|
||||||
|
page.reload()
|
||||||
|
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
|
||||||
|
|
||||||
|
@pytest.mark.skip("We currently can't approve wallet connection through Sim")
|
||||||
|
@pytest.mark.usefixtures("risk_accepted")
|
||||||
|
def test_connect_vega_wallet(continuous_market, page: Page):
|
||||||
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
|
page.get_by_test_id("order-price").fill("101")
|
||||||
|
page.get_by_test_id("order-connect-wallet").click()
|
||||||
|
expect(page.locator('[role="dialog"]')).to_be_visible()
|
||||||
|
page.get_by_test_id("connector-jsonRpc").click()
|
||||||
|
expect(page.get_by_test_id("wallet-dialog-title")).to_be_visible()
|
||||||
|
# TODO: accept wallet connection and assert wallet is connected.
|
||||||
|
expect(page.get_by_test_id("order-type-Limit")).to_be_checked()
|
||||||
|
expect(page.get_by_test_id("order-price")).to_have_value("101")
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from playwright.sync_api import Page, expect
|
|
||||||
from conftest import init_vega
|
|
||||||
from fixtures.market import setup_continuous_market
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def vega(request):
|
|
||||||
with init_vega(request) as vega:
|
|
||||||
yield vega
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def continuous_market(vega):
|
|
||||||
return setup_continuous_market(vega)
|
|
||||||
|
|
||||||
@pytest.mark.skip("We currently can't approve wallet connection through Sim")
|
|
||||||
@pytest.mark.usefixtures("risk_accepted")
|
|
||||||
def test_connect_vega_wallet(continuous_market, page: Page):
|
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
|
||||||
page.get_by_test_id("order-price").fill("101")
|
|
||||||
page.get_by_test_id("order-connect-wallet").click()
|
|
||||||
expect(page.locator('[role="dialog"]')).to_be_visible()
|
|
||||||
page.get_by_test_id("connector-jsonRpc").click()
|
|
||||||
expect(page.get_by_test_id("wallet-dialog-title")).to_be_visible()
|
|
||||||
# TODO: accept wallet connection and assert wallet is connected.
|
|
||||||
expect(page.get_by_test_id("order-type-Limit")).to_be_checked()
|
|
||||||
expect(page.get_by_test_id("order-price")).to_have_value("101")
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("risk_accepted")
|
|
||||||
def test_sidebar_should_be_open_after_reload(continuous_market, page: Page):
|
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
|
||||||
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
|
|
||||||
page.get_by_test_id("Order").click()
|
|
||||||
expect(page.get_by_test_id("deal-ticket-form")).not_to_be_visible()
|
|
||||||
page.reload()
|
|
||||||
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import pytest
|
||||||
|
from playwright.sync_api import Page, expect
|
||||||
|
from vega_sim.null_service import VegaServiceNull
|
||||||
|
from actions.vega import submit_order
|
||||||
|
from actions.utils import next_epoch, wait_for_toast_confirmation
|
||||||
|
|
||||||
|
tooltip_content = "tooltip-content"
|
||||||
|
leverage_input = "#leverage-input"
|
||||||
|
tab_positions = "tab-positions"
|
||||||
|
margin_row = '[col-id="margin"]'
|
||||||
|
|
||||||
|
|
||||||
|
def create_position(vega: VegaServiceNull, market_id):
|
||||||
|
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
|
||||||
|
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110)
|
||||||
|
vega.wait_fn(1)
|
||||||
|
vega.wait_for_total_catchup
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
|
def test_switch_cross_isolated_margin(
|
||||||
|
continuous_market, vega: VegaServiceNull, page: Page):
|
||||||
|
create_position(vega, continuous_market)
|
||||||
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
|
expect(page.locator(margin_row).nth(1)).to_have_text("874.21992Cross1.0x")
|
||||||
|
# tbd - tooltip is not visible without this wait
|
||||||
|
page.wait_for_timeout(1000)
|
||||||
|
page.get_by_test_id(tab_positions).get_by_text("Cross").hover()
|
||||||
|
expect(page.get_by_test_id(tooltip_content).nth(0)).to_have_text(
|
||||||
|
"Liquidation: 582.81328Margin: 874.21992General account: 998,084.95183"
|
||||||
|
)
|
||||||
|
page.get_by_role("button", name="Isolated 10x").click()
|
||||||
|
page.locator(leverage_input).clear()
|
||||||
|
page.locator(leverage_input).type("1")
|
||||||
|
page.get_by_role("button", name="Confirm").click()
|
||||||
|
wait_for_toast_confirmation(page)
|
||||||
|
next_epoch(vega=vega)
|
||||||
|
expect(page.get_by_test_id("toast-content")).to_have_text(
|
||||||
|
"ConfirmedYour transaction has been confirmedView in block explorerUpdate margin modeBTC:DAI_2023Isolated margin mode, leverage: 1.0x")
|
||||||
|
expect(page.locator(margin_row).nth(1)
|
||||||
|
).to_have_text("11,109.99996Isolated1.0x")
|
||||||
|
# tbd - tooltip is not visible without this wait
|
||||||
|
page.wait_for_timeout(1000)
|
||||||
|
page.get_by_test_id(tab_positions).get_by_text("Isolated").hover()
|
||||||
|
expect(page.get_by_test_id(tooltip_content).nth(0)).to_have_text(
|
||||||
|
"Liquidation: 583.62409Margin: 11,109.99996Order: 11,000.00"
|
||||||
|
)
|
||||||
|
page.get_by_role("button", name="Cross").click()
|
||||||
|
page.get_by_role("button", name="Confirm").click()
|
||||||
|
wait_for_toast_confirmation(page)
|
||||||
|
next_epoch(vega=vega)
|
||||||
|
expect(page.locator(margin_row).nth(1)).to_have_text(
|
||||||
|
"22,109.99996Cross1.0x")
|
||||||
@@ -27,7 +27,7 @@ submit_stop_order = "place-order"
|
|||||||
stop_orders_tab = "Stop orders"
|
stop_orders_tab = "Stop orders"
|
||||||
row_table = "row"
|
row_table = "row"
|
||||||
cancel = "cancel"
|
cancel = "cancel"
|
||||||
market_name_col = '[col-id="market.tradableInstrument.instrument.code"]'
|
market_name_col = '[data-testid="market-code"]'
|
||||||
trigger_col = '[col-id="trigger"]'
|
trigger_col = '[col-id="trigger"]'
|
||||||
expiresAt_col = '[col-id="expiresAt"]'
|
expiresAt_col = '[col-id="expiresAt"]'
|
||||||
size_col = '[col-id="submission.size"]'
|
size_col = '[col-id="submission.size"]'
|
||||||
@@ -41,7 +41,6 @@ close_toast = "toast-close"
|
|||||||
def create_position(vega: VegaServiceNull, market_id):
|
def create_position(vega: VegaServiceNull, market_id):
|
||||||
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
|
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
|
||||||
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110)
|
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110)
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup
|
vega.wait_for_total_catchup
|
||||||
|
|
||||||
@@ -78,7 +77,6 @@ def test_submit_stop_order_rejected(continuous_market, vega: VegaServiceNull, pa
|
|||||||
page.get_by_test_id(trigger_price).fill("103")
|
page.get_by_test_id(trigger_price).fill("103")
|
||||||
page.get_by_test_id(order_size).fill("3")
|
page.get_by_test_id(order_size).fill("3")
|
||||||
page.get_by_test_id(submit_stop_order).click()
|
page.get_by_test_id(submit_stop_order).click()
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
page.get_by_test_id(close_toast).click()
|
page.get_by_test_id(close_toast).click()
|
||||||
@@ -269,82 +267,6 @@ class TestStopOcoValidation:
|
|||||||
def continuous_market(self, vega):
|
def continuous_market(self, vega):
|
||||||
return setup_continuous_market(vega)
|
return setup_continuous_market(vega)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
|
||||||
def test_stop_market_order_form_validation(self, continuous_market, page: Page):
|
|
||||||
# 7002-SORD-052
|
|
||||||
# 7002-SORD-055
|
|
||||||
# 7002-SORD-056
|
|
||||||
# 7002-SORD-057
|
|
||||||
# 7002-SORD-058
|
|
||||||
# 7002-SORD-064
|
|
||||||
# 7002-SORD-065
|
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
|
||||||
page.get_by_test_id(stop_order_btn).click()
|
|
||||||
page.get_by_test_id(stop_market_order_btn).is_visible()
|
|
||||||
page.get_by_test_id(stop_market_order_btn).click()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id("sidebar-content").get_by_text("Trigger").first
|
|
||||||
).to_be_visible()
|
|
||||||
expect(page.locator('[for="triggerDirection-risesAbove"]')).to_have_text(
|
|
||||||
"Rises above"
|
|
||||||
)
|
|
||||||
expect(page.locator('[for="triggerDirection-fallsBelow"]')).to_have_text(
|
|
||||||
"Falls below"
|
|
||||||
)
|
|
||||||
page.get_by_test_id(trigger_price).click()
|
|
||||||
expect(page.get_by_test_id(trigger_price)).to_be_empty
|
|
||||||
expect(page.locator('[for="triggerType-price"]')).to_have_text("Price")
|
|
||||||
expect(page.locator('[for="triggerType-trailingPercentOffset"]')).to_have_text(
|
|
||||||
"Trailing Percent Offset"
|
|
||||||
)
|
|
||||||
expect(page.locator('[for="order-size"]')).to_have_text("Size")
|
|
||||||
page.get_by_test_id(order_size).click()
|
|
||||||
expect(page.get_by_test_id(order_size)).to_be_empty
|
|
||||||
expect(page.get_by_test_id(order_price)).not_to_be_visible()
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
|
||||||
def test_stop_limit_order_form_validation(self, continuous_market, page: Page):
|
|
||||||
# 7002-SORD-020
|
|
||||||
# 7002-SORD-021
|
|
||||||
# 7002-SORD-022
|
|
||||||
# 7002-SORD-033
|
|
||||||
# 7002-SORD-034
|
|
||||||
# 7002-SORD-035
|
|
||||||
# 7002-SORD-036
|
|
||||||
# 7002-SORD-037
|
|
||||||
# 7002-SORD-038
|
|
||||||
# 7002-SORD-049
|
|
||||||
# 7002-SORD-050
|
|
||||||
# 7002-SORD-051
|
|
||||||
|
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
|
||||||
page.get_by_test_id(stop_order_btn).click()
|
|
||||||
page.get_by_test_id(stop_limit_order_btn).is_visible()
|
|
||||||
page.get_by_test_id(stop_limit_order_btn).click()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id("sidebar-content").get_by_text("Trigger").first
|
|
||||||
).to_be_visible()
|
|
||||||
expect(page.locator('[for="triggerDirection-risesAbove"]')).to_have_text(
|
|
||||||
"Rises above"
|
|
||||||
)
|
|
||||||
expect(page.locator('[for="triggerDirection-risesAbove"]')).to_be_checked
|
|
||||||
expect(page.locator('[for="triggerDirection-fallsBelow"]')).to_have_text(
|
|
||||||
"Falls below"
|
|
||||||
)
|
|
||||||
page.get_by_test_id(trigger_price).click()
|
|
||||||
expect(page.get_by_test_id(trigger_price)).to_be_empty
|
|
||||||
expect(page.locator('[for="triggerType-price"]')).to_have_text("Price")
|
|
||||||
expect(page.locator('[for="triggerType-price"]')).to_be_checked
|
|
||||||
expect(page.locator('[for="triggerType-trailingPercentOffset"]')).to_have_text(
|
|
||||||
"Trailing Percent Offset"
|
|
||||||
)
|
|
||||||
expect(page.locator('[for="order-size"]').first).to_have_text("Size")
|
|
||||||
expect(page.locator('[for="order-price"]').last).to_have_text("Price")
|
|
||||||
page.get_by_test_id(order_size).click()
|
|
||||||
expect(page.get_by_test_id(order_size)).to_be_empty
|
|
||||||
page.get_by_test_id(order_price).click()
|
|
||||||
expect(page.get_by_test_id(order_price)).to_be_empty()
|
|
||||||
|
|
||||||
@pytest.mark.skip("core issue")
|
@pytest.mark.skip("core issue")
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
def test_maximum_number_of_active_stop_orders(
|
def test_maximum_number_of_active_stop_orders(
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ FEE_BREAKDOWN_TOOLTIP = "fee-breakdown-tooltip"
|
|||||||
PINNED_ROW_LOCATOR = ".ag-pinned-left-cols-container .ag-row"
|
PINNED_ROW_LOCATOR = ".ag-pinned-left-cols-container .ag-row"
|
||||||
ROW_LOCATOR = ".ag-center-cols-container .ag-row"
|
ROW_LOCATOR = ".ag-center-cols-container .ag-row"
|
||||||
# Col-Ids:
|
# Col-Ids:
|
||||||
COL_INSTRUMENT_CODE = '[col-id="market.tradableInstrument.instrument.code"]'
|
COL_INSTRUMENT_CODE = '[data-testid="market-code"]'
|
||||||
COL_CODE = '[col-id="code"]'
|
COL_CODE = '[col-id="code"]'
|
||||||
COL_SIZE = '[col-id="size"]'
|
COL_SIZE = '[col-id="size"]'
|
||||||
COL_PRICE = '[col-id="price"]'
|
COL_PRICE = '[col-id="price"]'
|
||||||
@@ -563,7 +563,6 @@ def test_fills_taker_discount_program(
|
|||||||
page.goto(f"/#/markets/{market_id}")
|
page.goto(f"/#/markets/{market_id}")
|
||||||
page.get_by_test_id(FILLS).click()
|
page.get_by_test_id(FILLS).click()
|
||||||
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
|
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
|
||||||
expect(row.locator(COL_INSTRUMENT_CODE)).to_have_text("BTC:DAI_2023Futr")
|
|
||||||
expect(row.locator(COL_SIZE)).to_have_text(size)
|
expect(row.locator(COL_SIZE)).to_have_text(size)
|
||||||
expect(row.locator(COL_PRICE)).to_have_text("103.50 tDAI")
|
expect(row.locator(COL_PRICE)).to_have_text("103.50 tDAI")
|
||||||
expect(row.locator(COL_PRICE_1)).to_have_text(price_1)
|
expect(row.locator(COL_PRICE_1)).to_have_text(price_1)
|
||||||
@@ -605,7 +604,6 @@ def test_fills_maker_discount_program(
|
|||||||
change_keys(page, vega_instance, MM_WALLET.name)
|
change_keys(page, vega_instance, MM_WALLET.name)
|
||||||
page.get_by_test_id(FILLS).click()
|
page.get_by_test_id(FILLS).click()
|
||||||
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
|
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
|
||||||
expect(row.locator(COL_INSTRUMENT_CODE)).to_have_text("BTC:DAI_2023Futr")
|
|
||||||
expect(row.locator(COL_SIZE)).to_have_text(size)
|
expect(row.locator(COL_SIZE)).to_have_text(size)
|
||||||
expect(row.locator(COL_PRICE)).to_have_text("103.50 tDAI")
|
expect(row.locator(COL_PRICE)).to_have_text("103.50 tDAI")
|
||||||
expect(row.locator(COL_PRICE_1)).to_have_text(price_1)
|
expect(row.locator(COL_PRICE_1)).to_have_text(price_1)
|
||||||
@@ -683,4 +681,4 @@ def test_fills_taker_fee_tooltip_discount_program(
|
|||||||
row.locator(COL_FEE).hover()
|
row.locator(COL_FEE).hover()
|
||||||
expect(page.get_by_test_id(FEE_BREAKDOWN_TOOLTIP)).to_have_text(
|
expect(page.get_by_test_id(FEE_BREAKDOWN_TOOLTIP)).to_have_text(
|
||||||
f"If the market was activeFees to be paid by the taker; discounts are already applied.Infrastructure fee{infra_fee} tDAILiquidity fee0.00 tDAIMaker fee{maker_fee} tDAITotal fees{total_fee} tDAI"
|
f"If the market was activeFees to be paid by the taker; discounts are already applied.Infrastructure fee{infra_fee} tDAILiquidity fee0.00 tDAIMaker fee{maker_fee} tDAITotal fees{total_fee} tDAI"
|
||||||
)
|
)
|
||||||
@@ -77,7 +77,7 @@ class TestGetStarted:
|
|||||||
|
|
||||||
vega.mint(
|
vega.mint(
|
||||||
MM_WALLET.name,
|
MM_WALLET.name,
|
||||||
asset="VOTE",
|
asset=vega.find_asset_id(symbol="VOTE", enabled=True),
|
||||||
amount=mint_amount,
|
amount=mint_amount,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,6 +105,8 @@ class TestGetStarted:
|
|||||||
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
|
page.reload()
|
||||||
|
|
||||||
# Assert step 2 complete
|
# Assert step 2 complete
|
||||||
expect(page.get_by_test_id("icon-tick")).to_have_count(2)
|
expect(page.get_by_test_id("icon-tick")).to_have_count(2)
|
||||||
|
|
||||||
@@ -144,29 +146,6 @@ class TestGetStarted:
|
|||||||
# 0007-FUGS-007
|
# 0007-FUGS-007
|
||||||
expect(page.get_by_test_id("dialog-content").nth(1)).to_be_visible()
|
expect(page.get_by_test_id("dialog-content").nth(1)).to_be_visible()
|
||||||
|
|
||||||
def test_browser_wallet_installed(self, simple_market, page: Page):
|
|
||||||
page.add_init_script("window.vega = {}")
|
|
||||||
page.goto(f"/#/markets/{simple_market}")
|
|
||||||
locator = page.get_by_test_id("connect-vega-wallet")
|
|
||||||
page.wait_for_selector('[data-testid="connect-vega-wallet"]', state="attached")
|
|
||||||
expect(locator).to_be_enabled
|
|
||||||
expect(locator).to_be_visible
|
|
||||||
expect(locator).to_have_text("Connect")
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("risk_accepted")
|
|
||||||
def test_get_started_deal_ticket(self,simple_market, page: Page):
|
|
||||||
page.goto(f"/#/markets/{simple_market}")
|
|
||||||
expect(page.get_by_test_id("order-connect-wallet")).to_have_text("Connect wallet")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("risk_accepted")
|
|
||||||
def test_browser_wallet_installed_deal_ticket(simple_market, page: Page):
|
|
||||||
page.add_init_script("window.vega = {}")
|
|
||||||
page.goto(f"/#/markets/{simple_market}")
|
|
||||||
# 0007-FUGS-013
|
|
||||||
page.wait_for_selector('[data-testid="sidebar-content"]', state="visible")
|
|
||||||
expect(page.get_by_test_id("get-started-banner")).not_to_be_visible()
|
|
||||||
|
|
||||||
@pytest.mark.skip("tbd-market-sim")
|
@pytest.mark.skip("tbd-market-sim")
|
||||||
def test_redirect_default_market(self, continuous_market, vega: VegaServiceNull, page: Page):
|
def test_redirect_default_market(self, continuous_market, vega: VegaServiceNull, page: Page):
|
||||||
page.goto("/")
|
page.goto("/")
|
||||||
@@ -177,11 +156,3 @@ class TestGetStarted:
|
|||||||
page.get_by_test_id("icon-cross").click()
|
page.get_by_test_id("icon-cross").click()
|
||||||
# 0007-FUGS-018
|
# 0007-FUGS-018
|
||||||
expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible()
|
expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible()
|
||||||
|
|
||||||
class TestBrowseAll:
|
|
||||||
def test_get_started_browse_all(self, simple_market, vega: VegaServiceNull, page: Page):
|
|
||||||
page.goto("/")
|
|
||||||
print(simple_market)
|
|
||||||
page.get_by_test_id("browse-markets-button").click()
|
|
||||||
# 0007-FUGS-005
|
|
||||||
expect(page).to_have_url(f"http://localhost:{vega.console_port}/#/markets/{simple_market}")
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class TestIcebergOrdersValidations:
|
|||||||
"Awaiting confirmationPlease wait for your transaction to be confirmedView in block explorer"
|
"Awaiting confirmationPlease wait for your transaction to be confirmedView in block explorer"
|
||||||
)
|
)
|
||||||
|
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
expect(page.get_by_test_id("toast-content")).to_have_text(
|
expect(page.get_by_test_id("toast-content")).to_have_text(
|
||||||
@@ -51,7 +50,6 @@ def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page
|
|||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
|
|
||||||
submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 102, 101, 2, 1)
|
submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 102, 101, 2, 1)
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
|
|
||||||
@@ -70,7 +68,6 @@ def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page
|
|||||||
expect(
|
expect(
|
||||||
page.locator(".ag-center-cols-container .ag-row [col-id='size']").first
|
page.locator(".ag-center-cols-container .ag-row [col-id='size']").first
|
||||||
).to_have_text("-102")
|
).to_have_text("-102")
|
||||||
page.pause()
|
|
||||||
expect(
|
expect(
|
||||||
page.locator(".ag-center-cols-container .ag-row [col-id='type'] ").first
|
page.locator(".ag-center-cols-container .ag-row [col-id='type'] ").first
|
||||||
).to_have_text("Limit (Iceberg)")
|
).to_have_text("Limit (Iceberg)")
|
||||||
@@ -85,7 +82,6 @@ def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page
|
|||||||
|
|
||||||
submit_order(vega, MM_WALLET2.name, continuous_market, "SIDE_BUY", 103, 101)
|
submit_order(vega, MM_WALLET2.name, continuous_market, "SIDE_BUY", 103, 101)
|
||||||
|
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -18,33 +18,42 @@ def continuous_market(vega):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
def test_liquidity_provision_amendment(continuous_market, vega: VegaServiceNull, page: Page):
|
def test_liquidity_provision_amendment(
|
||||||
|
continuous_market, vega: VegaServiceNull, page: Page
|
||||||
|
):
|
||||||
# TODO Refactor asserting the grid
|
# TODO Refactor asserting the grid
|
||||||
page.goto(f"/#/liquidity/{continuous_market}")
|
page.goto(f"/#/liquidity/{continuous_market}")
|
||||||
change_keys(page, vega, "market_maker")
|
change_keys(page, vega, "market_maker")
|
||||||
row = page.get_by_test_id(
|
row = (
|
||||||
"tab-myLP").locator(".ag-center-cols-container .ag-row").first
|
page.get_by_test_id("tab-myLP")
|
||||||
expect(row).to_contain_text(
|
.locator(".ag-center-cols-container .ag-row")
|
||||||
"Active"
|
.first
|
||||||
)
|
)
|
||||||
|
expect(row).to_contain_text("Active")
|
||||||
# 5002-LIQP-006
|
# 5002-LIQP-006
|
||||||
expect(page.get_by_test_id("target-stake")
|
expect(page.get_by_test_id("target-stake")).to_have_text("Target stake5.82757 tDAI")
|
||||||
).to_have_text("Target stake5.82757 tDAI")
|
|
||||||
# 5002-LIQP-007
|
# 5002-LIQP-007
|
||||||
expect(page.get_by_test_id("supplied-stake")
|
expect(page.get_by_test_id("supplied-stake")).to_have_text(
|
||||||
).to_have_text("Supplied stake10,000.00 tDAI")
|
"Supplied stake10,000.00 tDAI"
|
||||||
|
)
|
||||||
# 5002-LIQP-008
|
# 5002-LIQP-008
|
||||||
expect(page.get_by_test_id("liquidity-supplied")
|
expect(page.get_by_test_id("liquidity-supplied")).to_have_text(
|
||||||
).to_have_text("Liquidity supplied 171,598.11%")
|
"Liquidity supplied 171,598.11%"
|
||||||
|
)
|
||||||
expect(page.get_by_test_id("fees-paid")).to_have_text("Fees paid-")
|
expect(page.get_by_test_id("fees-paid")).to_have_text("Fees paid-")
|
||||||
# 5002-LIQP-009
|
# 5002-LIQP-009
|
||||||
expect(page.get_by_test_id("liquidity-market-id")
|
expect(page.get_by_test_id("liquidity-market-id")).to_have_text(
|
||||||
).to_have_text("Market ID" + truncate_middle(continuous_market))
|
"Market ID" + truncate_middle(continuous_market)
|
||||||
expect(page.get_by_test_id("liquidity-learn-more")
|
)
|
||||||
).to_have_text("Learn moreProviding liquidity")
|
expect(page.get_by_test_id("liquidity-learn-more")).to_have_text(
|
||||||
|
"Learn moreProviding liquidity"
|
||||||
|
)
|
||||||
# 002-LIQP-010
|
# 002-LIQP-010
|
||||||
expect(page.get_by_test_id("liquidity-learn-more").get_by_test_id("external-link")
|
expect(
|
||||||
).to_have_attribute("href", "https://docs.vega.xyz/testnet/concepts/liquidity/provision")
|
page.get_by_test_id("liquidity-learn-more").get_by_test_id("external-link")
|
||||||
|
).to_have_attribute(
|
||||||
|
"href", "https://docs.vega.xyz/testnet/concepts/liquidity/provision"
|
||||||
|
)
|
||||||
|
|
||||||
vega.submit_simple_liquidity(
|
vega.submit_simple_liquidity(
|
||||||
key_name="market_maker",
|
key_name="market_maker",
|
||||||
@@ -57,41 +66,50 @@ def test_liquidity_provision_amendment(continuous_market, vega: VegaServiceNull,
|
|||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
page.reload()
|
page.reload()
|
||||||
row = page.get_by_test_id(
|
row = (
|
||||||
"tab-myLP").locator(".ag-center-cols-container .ag-row").first
|
page.get_by_test_id("tab-myLP")
|
||||||
expect(row).to_contain_text(
|
.locator(".ag-center-cols-container .ag-row")
|
||||||
"Updating next epoch"
|
.first
|
||||||
|
)
|
||||||
|
expect(row).to_contain_text("Updating next epoch")
|
||||||
|
next_epoch(vega=vega)
|
||||||
|
page.reload()
|
||||||
|
expect(page.get_by_test_id("supplied-stake")).to_have_text(
|
||||||
|
"Supplied stake1.00001 tDAI"
|
||||||
|
)
|
||||||
|
expect(page.get_by_test_id("liquidity-supplied")).to_have_text(
|
||||||
|
"Liquidity supplied 17.16%"
|
||||||
|
)
|
||||||
|
row = (
|
||||||
|
page.get_by_test_id("tab-myLP")
|
||||||
|
.locator(".ag-center-cols-container .ag-row")
|
||||||
|
.first
|
||||||
|
)
|
||||||
|
expect(row).to_contain_text("Active")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
|
def test_liquidity_provision_cancelled(
|
||||||
|
continuous_market, vega: VegaServiceNull, page: Page
|
||||||
|
):
|
||||||
|
page.goto(f"/#/liquidity/{continuous_market}")
|
||||||
|
change_keys(page, vega, "market_maker")
|
||||||
|
row = (
|
||||||
|
page.get_by_test_id("tab-myLP")
|
||||||
|
.locator(".ag-center-cols-container .ag-row")
|
||||||
|
.first
|
||||||
|
)
|
||||||
|
expect(row).to_contain_text("Active")
|
||||||
|
vega.cancel_liquidity(
|
||||||
|
key_name="market_maker",
|
||||||
|
market_id=continuous_market,
|
||||||
)
|
)
|
||||||
next_epoch(vega=vega)
|
next_epoch(vega=vega)
|
||||||
page.reload()
|
page.reload()
|
||||||
expect(page.get_by_test_id("supplied-stake")
|
expect(page.get_by_test_id("supplied-stake")).to_have_text(
|
||||||
).to_have_text("Supplied stake1.00001 tDAI")
|
"Supplied stake0.00 tDAI"
|
||||||
expect(page.get_by_test_id("liquidity-supplied")
|
|
||||||
).to_have_text("Liquidity supplied 17.16%")
|
|
||||||
row = page.get_by_test_id(
|
|
||||||
"tab-myLP").locator(".ag-center-cols-container .ag-row").first
|
|
||||||
expect(row).to_contain_text(
|
|
||||||
"Active"
|
|
||||||
)
|
)
|
||||||
|
expect(page.get_by_test_id("liquidity-supplied")).to_have_text(
|
||||||
|
"Liquidity supplied 0.00%"
|
||||||
@pytest.mark.skip("Waiting for the ability to cancel LP")
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
|
||||||
def test_liquidity_provision_inactive(continuous_market, vega: VegaServiceNull, page: Page):
|
|
||||||
# TODO Refactor asserting the grid
|
|
||||||
page.goto(f"/#/liquidity/{continuous_market}")
|
|
||||||
change_keys(page, vega, "market_maker")
|
|
||||||
row = page.get_by_test_id(
|
|
||||||
"tab-myLP").locator(".ag-center-cols-container .ag-row").first
|
|
||||||
expect(row).to_contain_text(
|
|
||||||
"Active"
|
|
||||||
)
|
)
|
||||||
vega.submit_simple_liquidity(
|
expect(page.locator(".ag-overlay-panel")).to_have_text("No data")
|
||||||
key_name="market_maker",
|
|
||||||
market_id=continuous_market,
|
|
||||||
commitment_amount=0,
|
|
||||||
fee=0,
|
|
||||||
is_amendment=False,
|
|
||||||
)
|
|
||||||
vega.wait_fn(1)
|
|
||||||
vega.wait_for_total_catchup()
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from vega_sim.null_service import VegaServiceNull
|
|||||||
from playwright.sync_api import Page, expect
|
from playwright.sync_api import Page, expect
|
||||||
from fixtures.market import setup_continuous_market
|
from fixtures.market import setup_continuous_market
|
||||||
from conftest import init_vega
|
from conftest import init_vega
|
||||||
|
from actions.utils import next_epoch
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
@@ -21,9 +22,7 @@ def create_settled_market(vega: VegaServiceNull):
|
|||||||
settlement_price=110,
|
settlement_price=110,
|
||||||
market_id=market_id,
|
market_id=market_id,
|
||||||
)
|
)
|
||||||
vega.forward("10s")
|
next_epoch(vega=vega)
|
||||||
vega.wait_fn(10)
|
|
||||||
vega.wait_for_total_catchup()
|
|
||||||
|
|
||||||
|
|
||||||
class TestSettledMarket:
|
class TestSettledMarket:
|
||||||
@@ -123,9 +122,7 @@ def test_terminated_market_no_settlement_date(page: Page, vega: VegaServiceNull)
|
|||||||
payload={"trading.terminated": "true"},
|
payload={"trading.terminated": "true"},
|
||||||
key_name="FJMKnwfZdd48C8NqvYrG",
|
key_name="FJMKnwfZdd48C8NqvYrG",
|
||||||
)
|
)
|
||||||
vega.forward("60s")
|
next_epoch(vega=vega)
|
||||||
vega.wait_fn(10)
|
|
||||||
vega.wait_for_total_catchup()
|
|
||||||
page.goto(f"/#/markets/all")
|
page.goto(f"/#/markets/all")
|
||||||
page.get_by_test_id("Closed markets").click()
|
page.get_by_test_id("Closed markets").click()
|
||||||
row_selector = page.locator(
|
row_selector = page.locator(
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from playwright.sync_api import Page, expect
|
|
||||||
from vega_sim.null_service import VegaServiceNull
|
|
||||||
from actions.vega import submit_order
|
|
||||||
from actions.utils import change_keys
|
|
||||||
from wallet_config import MM_WALLET, MM_WALLET2
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
|
||||||
|
|
||||||
table_row_selector = (
|
|
||||||
'[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row'
|
|
||||||
)
|
|
||||||
trading_mode_col = '[col-id="tradingMode"]'
|
|
||||||
state_col = '[col-id="state"]'
|
|
||||||
item_value = "item-value"
|
|
||||||
price_monitoring_bounds_row = "key-value-table-row"
|
|
||||||
market_trading_mode = "market-trading-mode"
|
|
||||||
market_state = "market-state"
|
|
||||||
liquidity_supplied = "liquidity-supplied"
|
|
||||||
item_value = "item-value"
|
|
||||||
price_monitoring_bounds_row = "key-value-table-row"
|
|
||||||
market_trading_mode = "market-trading-mode"
|
|
||||||
market_state = "market-state"
|
|
||||||
liquidity_supplied = "liquidity-supplied"
|
|
||||||
|
|
||||||
initial_commitment: float = 100
|
|
||||||
initial_price: float = 1
|
|
||||||
initial_volume: float = 1
|
|
||||||
initial_spread: float = 0.1
|
|
||||||
market_name = "BTC:DAI_2023"
|
|
||||||
|
|
||||||
@pytest.mark.skip("tbd")
|
|
||||||
@pytest.mark.usefixtures("risk_accepted", "auth")
|
|
||||||
def test_price_monitoring(simple_market, vega: VegaServiceNull, page: Page):
|
|
||||||
page.goto(f"/#/markets/all")
|
|
||||||
expect(page.locator(table_row_selector).locator(trading_mode_col)).to_have_text(
|
|
||||||
"Opening auction"
|
|
||||||
)
|
|
||||||
expect(page.locator(table_row_selector).locator('[col-id="state"]')).to_have_text(
|
|
||||||
"Pending"
|
|
||||||
)
|
|
||||||
result = page.get_by_text(market_name)
|
|
||||||
result.first.click()
|
|
||||||
page.get_by_test_id(market_trading_mode).get_by_text("Opening auction").hover()
|
|
||||||
expect(page.get_by_test_id("opening-auction-sub-status").first).to_have_text(
|
|
||||||
"Opening auction: Not enough liquidity to open"
|
|
||||||
)
|
|
||||||
logger.info(page.get_by_test_id("opening-auction-sub-status").inner_text)
|
|
||||||
vega.submit_liquidity(
|
|
||||||
key_name=MM_WALLET.name,
|
|
||||||
market_id=simple_market,
|
|
||||||
commitment_amount=initial_commitment,
|
|
||||||
fee=0.002,
|
|
||||||
is_amendment=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
vega.submit_order(
|
|
||||||
market_id=simple_market,
|
|
||||||
trading_key=MM_WALLET.name,
|
|
||||||
side="SIDE_BUY",
|
|
||||||
order_type="TYPE_LIMIT",
|
|
||||||
price=initial_price - 0.0005,
|
|
||||||
wait=False,
|
|
||||||
time_in_force="TIME_IN_FORCE_GTC",
|
|
||||||
volume=99,
|
|
||||||
)
|
|
||||||
vega.submit_order(
|
|
||||||
market_id=simple_market,
|
|
||||||
trading_key=MM_WALLET.name,
|
|
||||||
side="SIDE_SELL",
|
|
||||||
order_type="TYPE_LIMIT",
|
|
||||||
price=initial_price + 0.0005,
|
|
||||||
wait=False,
|
|
||||||
time_in_force="TIME_IN_FORCE_GTC",
|
|
||||||
volume=99,
|
|
||||||
)
|
|
||||||
# 6002-MDET-009
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
|
|
||||||
).to_have_text("0.00 (0.00%)")
|
|
||||||
|
|
||||||
# add orders to provide liquidity
|
|
||||||
submit_order(
|
|
||||||
vega, MM_WALLET.name, simple_market, "SIDE_BUY", initial_volume, initial_price
|
|
||||||
)
|
|
||||||
submit_order(
|
|
||||||
vega, MM_WALLET.name, simple_market, "SIDE_SELL", initial_volume, initial_price
|
|
||||||
)
|
|
||||||
submit_order(
|
|
||||||
vega,
|
|
||||||
MM_WALLET.name,
|
|
||||||
simple_market,
|
|
||||||
"SIDE_BUY",
|
|
||||||
initial_volume,
|
|
||||||
initial_price + initial_spread / 2,
|
|
||||||
)
|
|
||||||
submit_order(
|
|
||||||
vega,
|
|
||||||
MM_WALLET.name,
|
|
||||||
simple_market,
|
|
||||||
"SIDE_SELL",
|
|
||||||
initial_volume,
|
|
||||||
initial_price + initial_spread / 2,
|
|
||||||
)
|
|
||||||
submit_order(
|
|
||||||
vega, MM_WALLET2.name, simple_market, "SIDE_SELL", initial_volume, initial_price
|
|
||||||
)
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
|
|
||||||
).to_have_text("100.00 (>100%)")
|
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(10)
|
|
||||||
vega.wait_for_total_catchup()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
|
|
||||||
).to_have_text("50.00 (>100%)")
|
|
||||||
|
|
||||||
page.goto(f"/#/markets/all")
|
|
||||||
# temporary skip
|
|
||||||
# expect(page.locator(table_row_selector).locator(trading_mode_col)).to_have_text(
|
|
||||||
# "Continuous"
|
|
||||||
# )
|
|
||||||
|
|
||||||
# commented out because we have an issue #4233
|
|
||||||
# expect(page.locator(row_selector).locator(state_col)
|
|
||||||
# ).to_have_text("Pending")
|
|
||||||
|
|
||||||
page.goto(f"/#/markets/all")
|
|
||||||
result = page.get_by_text(market_name)
|
|
||||||
result.first.click()
|
|
||||||
|
|
||||||
page.get_by_test_id("Info").click()
|
|
||||||
page.get_by_test_id("accordion-title").get_by_text(
|
|
||||||
"Price monitoring bounds 1"
|
|
||||||
).click()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(price_monitoring_bounds_row).first.get_by_text(
|
|
||||||
"1.32217 BTC"
|
|
||||||
)
|
|
||||||
).to_be_visible()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(price_monitoring_bounds_row).last.get_by_text("0.79245 BTC")
|
|
||||||
).to_be_visible()
|
|
||||||
|
|
||||||
# add orders that change the price so that it goes beyond the limits of price monitoring
|
|
||||||
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 110)
|
|
||||||
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 90)
|
|
||||||
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 105)
|
|
||||||
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 95)
|
|
||||||
|
|
||||||
# add order at the current price so that it is possible to change the status to price monitoring
|
|
||||||
to_cancel = submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 1, 105)
|
|
||||||
|
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
|
||||||
vega.wait_for_total_catchup()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(price_monitoring_bounds_row).first.get_by_text(
|
|
||||||
"135.44204 BTC"
|
|
||||||
)
|
|
||||||
).to_be_visible()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(price_monitoring_bounds_row).last.get_by_text(
|
|
||||||
"81.17758 BTC"
|
|
||||||
)
|
|
||||||
).to_be_visible()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(market_trading_mode).get_by_test_id(item_value)
|
|
||||||
).to_have_text("Monitoring auction - price")
|
|
||||||
expect(page.get_by_test_id(market_state).get_by_test_id(item_value)).to_have_text(
|
|
||||||
"Suspended"
|
|
||||||
)
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
|
|
||||||
).to_have_text("50.00 (8.78%)")
|
|
||||||
|
|
||||||
# cancel order to increase liquidity
|
|
||||||
vega.cancel_order(MM_WALLET2.name, simple_market, to_cancel)
|
|
||||||
|
|
||||||
vega.forward("10s")
|
|
||||||
vega.wait_fn(1)
|
|
||||||
vega.wait_for_total_catchup()
|
|
||||||
|
|
||||||
expect(page.get_by_text(market_name).first).to_be_attached()
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(market_trading_mode).get_by_test_id(item_value)
|
|
||||||
).to_have_text("Continuous")
|
|
||||||
expect(page.get_by_test_id(market_state).get_by_test_id(item_value)).to_have_text(
|
|
||||||
"Active"
|
|
||||||
)
|
|
||||||
# commented out because we have an issue #4233
|
|
||||||
# expect(page.get_by_text("Opening auction")).to_be_hidden()
|
|
||||||
|
|
||||||
# 6002-MDET-009
|
|
||||||
expect(
|
|
||||||
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
|
|
||||||
).to_have_text("50.00 (>100%)")
|
|
||||||
|
|
||||||
|
|
||||||
COL_ID_FEE = ".ag-center-cols-container [col-id='fee'] .ag-cell-value"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("risk_accepted", "auth")
|
|
||||||
def test_auction_uncross_fees(continuous_market, vega: VegaServiceNull, page: Page):
|
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
|
||||||
page.get_by_test_id("Fills").click()
|
|
||||||
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
|
|
||||||
# tbd - tooltip is not visible without this wait
|
|
||||||
page.wait_for_timeout(1000)
|
|
||||||
page.get_by_role("gridcell", name="0.00 tDAI").nth(0).hover()
|
|
||||||
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text(
|
|
||||||
"If the market was suspendedDuring auction, half the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI"
|
|
||||||
)
|
|
||||||
change_keys(page, vega, "market_maker")
|
|
||||||
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
|
|
||||||
# tbd - tooltip is not visible without this wait
|
|
||||||
page.wait_for_timeout(1000)
|
|
||||||
page.get_by_role("gridcell", name="0.00 tDAI").nth(0).hover()
|
|
||||||
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text(
|
|
||||||
"If the market was suspendedDuring auction, half the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI"
|
|
||||||
)
|
|
||||||
@@ -25,65 +25,3 @@ def test_market_selector(continuous_market, page: Page):
|
|||||||
expect(btc_market.locator("span.rounded-md.leading-none")).to_be_visible()
|
expect(btc_market.locator("span.rounded-md.leading-none")).to_be_visible()
|
||||||
expect(btc_market.locator("span.rounded-md.leading-none")).to_have_text("Futr")
|
expect(btc_market.locator("span.rounded-md.leading-none")).to_have_text("Futr")
|
||||||
expect(btc_market.locator('[data-testid="sparkline-svg"]')).not_to_be_visible
|
expect(btc_market.locator('[data-testid="sparkline-svg"]')).not_to_be_visible
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("simple_market", "auth", "risk_accepted")
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"simple_market",
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"custom_market_name": "APPL.MF21",
|
|
||||||
"custom_asset_name": "tUSDC",
|
|
||||||
"custom_asset_symbol": "tUSDC",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
indirect=True,
|
|
||||||
)
|
|
||||||
def test_market_selector_filter(continuous_market, page: Page):
|
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
|
||||||
page.get_by_test_id("header-title").click()
|
|
||||||
# 6001-MARK-027
|
|
||||||
page.get_by_test_id("product-Spot").click()
|
|
||||||
expect(page.get_by_test_id("market-selector-list")).to_contain_text(
|
|
||||||
"Spot markets coming soon."
|
|
||||||
)
|
|
||||||
page.get_by_test_id("product-Perpetual").click()
|
|
||||||
expect(page.get_by_test_id("market-selector-list")).to_contain_text(
|
|
||||||
"No perpetual markets."
|
|
||||||
)
|
|
||||||
page.get_by_test_id("product-Future").click()
|
|
||||||
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(2)
|
|
||||||
|
|
||||||
# 6001-MARK-029
|
|
||||||
page.get_by_test_id("search-term").fill("btc")
|
|
||||||
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
|
|
||||||
# tbd - 5465
|
|
||||||
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_contain_text(
|
|
||||||
"BTC:DAI_2023107.50 tDAI"
|
|
||||||
)
|
|
||||||
|
|
||||||
page.get_by_test_id("search-term").clear()
|
|
||||||
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(2)
|
|
||||||
|
|
||||||
# 6001-MARK-030
|
|
||||||
# 6001-MARK-031
|
|
||||||
# 6001-MARK-032
|
|
||||||
# 6001-MARK-033
|
|
||||||
page.get_by_test_id("sort-trigger").click()
|
|
||||||
|
|
||||||
expect(page.get_by_test_id("sort-item-Gained")).to_have_text("Top gaining")
|
|
||||||
expect(page.get_by_test_id("sort-item-Gained")).to_be_visible()
|
|
||||||
expect(page.get_by_test_id("sort-item-Lost")).to_have_text("Top losing")
|
|
||||||
expect(page.get_by_test_id("sort-item-Lost")).to_be_visible()
|
|
||||||
expect(page.get_by_test_id("sort-item-New")).to_have_text("New markets")
|
|
||||||
expect(page.get_by_test_id("sort-item-New")).to_be_visible()
|
|
||||||
|
|
||||||
# 6001-MARK-028
|
|
||||||
page.get_by_test_id("sort-trigger").click(force=True)
|
|
||||||
page.get_by_test_id("asset-trigger").click()
|
|
||||||
page.get_by_role("menuitemcheckbox").nth(0).get_by_text("tDAI").click()
|
|
||||||
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
|
|
||||||
# tbd - 5465
|
|
||||||
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_contain_text(
|
|
||||||
"BTC:DAI_2023107.50 tDAI"
|
|
||||||
)
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user