feat(trading): activity streaks, reward hoarder bonus and active rewards (#5491)
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> Co-authored-by: Matthew Russell <mattrussell36@gmail.com> Co-authored-by: bwallacee <ben@vega.xyz>
This commit is contained in:
parent
462066959d
commit
78f5a9c520
@ -12,6 +12,7 @@ import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Web3Provider } from './web3-provider';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { DataLoader } from './data-loader';
|
||||
|
||||
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
||||
const t = useT();
|
||||
@ -52,28 +53,38 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Web3Provider
|
||||
<DataLoader
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<AppFailure title={t('Could not configure web3 provider')} />
|
||||
<AppFailure
|
||||
title={t('Could not load market data or asset data')}
|
||||
error={error}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
<Web3Provider
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<AppFailure title={t('Could not configure web3 provider')} />
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</VegaWalletProvider>
|
||||
</Web3Provider>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
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>
|
||||
</NetworkLoader>
|
||||
);
|
||||
|
34
apps/trading/components/bootstrapper/data-loader.tsx
Normal file
34
apps/trading/components/bootstrapper/data-loader.tsx
Normal file
@ -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}</>;
|
||||
};
|
@ -2,14 +2,19 @@ query RewardsPage($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
vestingStats {
|
||||
# AKA hoarder reward multiplier
|
||||
rewardBonusMultiplier
|
||||
quantumBalance
|
||||
epochSeq
|
||||
}
|
||||
activityStreak {
|
||||
# vesting multiplier
|
||||
rewardVestingMultiplier
|
||||
# AKA streak multiplier
|
||||
activeFor
|
||||
isActive
|
||||
inactiveFor
|
||||
rewardDistributionMultiplier
|
||||
rewardVestingMultiplier
|
||||
epoch
|
||||
tradedVolume
|
||||
openVolume
|
||||
}
|
||||
vestingBalancesSummary {
|
||||
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(
|
||||
$partyId: ID!
|
||||
$epochRewardSummariesPagination: Pagination
|
||||
@ -92,3 +165,18 @@ query RewardsEpoch {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
query MarketForRewards($marketId: ID!) {
|
||||
market(id: $marketId) {
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
id
|
||||
name
|
||||
code
|
||||
metadata {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<{
|
||||
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 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`
|
||||
query RewardsPage($partyId: ID!) {
|
||||
@ -33,10 +50,18 @@ export const RewardsPageDocument = gql`
|
||||
id
|
||||
vestingStats {
|
||||
rewardBonusMultiplier
|
||||
quantumBalance
|
||||
epochSeq
|
||||
}
|
||||
activityStreak {
|
||||
rewardVestingMultiplier
|
||||
activeFor
|
||||
isActive
|
||||
inactiveFor
|
||||
rewardDistributionMultiplier
|
||||
rewardVestingMultiplier
|
||||
epoch
|
||||
tradedVolume
|
||||
openVolume
|
||||
}
|
||||
vestingBalancesSummary {
|
||||
epoch
|
||||
@ -91,6 +116,101 @@ export function useRewardsPageLazyQuery(baseOptions?: Apollo.LazyQueryHookOption
|
||||
export type RewardsPageQueryHookResult = ReturnType<typeof useRewardsPageQuery>;
|
||||
export type RewardsPageLazyQueryHookResult = ReturnType<typeof useRewardsPageLazyQuery>;
|
||||
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`
|
||||
query RewardsHistory($partyId: ID!, $epochRewardSummariesPagination: Pagination, $partyRewardsPagination: Pagination, $fromEpoch: Int, $toEpoch: Int) {
|
||||
epochRewardSummaries(
|
||||
@ -202,4 +322,48 @@ export function useRewardsEpochLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio
|
||||
}
|
||||
export type RewardsEpochQueryHookResult = ReturnType<typeof useRewardsEpochQuery>;
|
||||
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,174 @@
|
||||
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}
|
||||
/>
|
||||
);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
569
apps/trading/components/rewards-container/active-rewards.tsx
Normal file
569
apps/trading/components/rewards-container/active-rewards.tsx
Normal file
@ -0,0 +1,569 @@
|
||||
import {
|
||||
useActiveRewardsQuery,
|
||||
useMarketForRewardsQuery,
|
||||
} from './__generated__/Rewards';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Icon,
|
||||
type IconName,
|
||||
Intent,
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
type VegaIconSize,
|
||||
TradingInput,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import {
|
||||
DistributionStrategyDescriptionMapping,
|
||||
DistributionStrategyMapping,
|
||||
EntityScope,
|
||||
EntityScopeMapping,
|
||||
type Maybe,
|
||||
type Transfer,
|
||||
type TransferNode,
|
||||
TransferStatus,
|
||||
TransferStatusMapping,
|
||||
DispatchMetric,
|
||||
DispatchMetricDescription,
|
||||
DispatchMetricLabels,
|
||||
type RecurringTransfer,
|
||||
EntityScopeLabelMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import { Card } from '../card/card';
|
||||
import { useMemo, useState } from 'react';
|
||||
import {
|
||||
type AssetFieldsFragment,
|
||||
useAssetDataProvider,
|
||||
useAssetsMapProvider,
|
||||
} from '@vegaprotocol/assets';
|
||||
import {
|
||||
type MarketFieldsFragment,
|
||||
useMarketsMapProvider,
|
||||
} 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;
|
||||
marketIds?: (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.marketIds?.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 transfers = 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 marketIds =
|
||||
node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map(
|
||||
(id) => markets && markets[id]
|
||||
);
|
||||
|
||||
return { ...node, asset, marketIds };
|
||||
});
|
||||
|
||||
if (!transfers || !transfers.length) return null;
|
||||
|
||||
return (
|
||||
<Card title={t('Active rewards')} className="lg:col-span-full">
|
||||
<div className="">
|
||||
{transfers.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"
|
||||
prependElement={<VegaIcon name={VegaIconNames.SEARCH} />}
|
||||
/>
|
||||
)}
|
||||
<div 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(343px,_1fr))] max-h-[40rem] overflow-auto">
|
||||
{transfers
|
||||
.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}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</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_CANCELLED:
|
||||
return { icon: IconNames.MOON, intent: Intent.None };
|
||||
case TransferStatus.STATUS_PENDING:
|
||||
return { icon: IconNames.HELP, intent: Intent.Primary };
|
||||
case TransferStatus.STATUS_REJECTED:
|
||||
return { icon: IconNames.ERROR, intent: Intent.Danger };
|
||||
case TransferStatus.STATUS_STOPPED:
|
||||
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,
|
||||
}: {
|
||||
transferNode: TransferNode;
|
||||
currentEpoch: number;
|
||||
kind: RecurringTransfer;
|
||||
}) => {
|
||||
const t = useT();
|
||||
|
||||
const { transfer } = transferNode;
|
||||
const { dispatchStrategy } = kind;
|
||||
const marketIds = dispatchStrategy?.marketIdsInScope;
|
||||
|
||||
const { data: marketNameData } = useMarketForRewardsQuery({
|
||||
variables: {
|
||||
marketId: marketIds ? marketIds[0] : '',
|
||||
},
|
||||
});
|
||||
|
||||
const marketName = useMemo(() => {
|
||||
if (marketNameData && marketIds && marketIds.length > 1) {
|
||||
return 'Specific markets';
|
||||
} else if (
|
||||
marketNameData &&
|
||||
marketIds &&
|
||||
marketNameData &&
|
||||
marketIds.length === 1
|
||||
) {
|
||||
return marketNameData?.market?.tradableInstrument?.instrument?.name || '';
|
||||
}
|
||||
return '';
|
||||
}, [marketIds, marketNameData]);
|
||||
|
||||
const { data: dispatchAsset } = useAssetDataProvider(
|
||||
dispatchStrategy?.dispatchMetricAssetId || ''
|
||||
);
|
||||
|
||||
if (!dispatchStrategy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { gradientClassName, mainClassName } = 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]}
|
||||
{marketName ? ` • ${marketName}` : ` • ${dispatchAsset?.name}`}
|
||||
</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 { useT } from '../../lib/use-t';
|
||||
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 = [
|
||||
'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba', // USDT mainnet
|
||||
@ -45,6 +49,7 @@ export const RewardsContainer = () => {
|
||||
const { params, loading: paramsLoading } = useNetworkParams([
|
||||
NetworkParams.reward_asset,
|
||||
NetworkParams.rewards_activityStreak_benefitTiers,
|
||||
NetworkParams.rewards_vesting_benefitTiers,
|
||||
NetworkParams.rewards_vesting_baseRate,
|
||||
]);
|
||||
|
||||
@ -54,6 +59,14 @@ export const RewardsContainer = () => {
|
||||
|
||||
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
|
||||
// Note activityStreak in query will fail
|
||||
const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({
|
||||
@ -68,6 +81,9 @@ export const RewardsContainer = () => {
|
||||
pollInterval: 10000,
|
||||
});
|
||||
|
||||
const partyActivityStreak = rewardsData?.party?.activityStreak;
|
||||
const vestingDetails = rewardsData?.party?.vestingStats;
|
||||
|
||||
if (!epochData?.epoch || !assetMap) return null;
|
||||
|
||||
const loading = paramsLoading || accountsLoading || rewardsLoading;
|
||||
@ -114,74 +130,98 @@ export const RewardsContainer = () => {
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="grid auto-rows-min grid-cols-6 gap-3">
|
||||
{/* Always show reward information for vega */}
|
||||
<Card
|
||||
key={params.reward_asset}
|
||||
title={t('Vega Reward pot')}
|
||||
className="lg:col-span-3 xl:col-span-2"
|
||||
loading={loading}
|
||||
highlight={true}
|
||||
>
|
||||
<RewardPot
|
||||
pubKey={pubKey}
|
||||
accounts={accounts}
|
||||
assetId={params.reward_asset}
|
||||
vestingBalancesSummary={rewardsData?.party?.vestingBalancesSummary}
|
||||
/>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('Vesting')}
|
||||
className="lg:col-span-3 xl:col-span-2"
|
||||
loading={loading}
|
||||
>
|
||||
<Vesting
|
||||
pubKey={pubKey}
|
||||
baseRate={params.rewards_vesting_baseRate}
|
||||
multiplier={
|
||||
rewardsData?.party?.activityStreak?.rewardVestingMultiplier
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('Rewards multipliers')}
|
||||
className="lg:col-span-3 xl:col-span-2"
|
||||
loading={loading}
|
||||
highlight={true}
|
||||
>
|
||||
<Multipliers
|
||||
pubKey={pubKey}
|
||||
hoarderMultiplier={
|
||||
rewardsData?.party?.vestingStats?.rewardBonusMultiplier
|
||||
}
|
||||
streakMultiplier={
|
||||
rewardsData?.party?.activityStreak?.rewardDistributionMultiplier
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<div className="flex flex-col w-full gap-3">
|
||||
<div className="grid auto-rows-min grid-cols-6 gap-3">
|
||||
{/* Always show reward information for vega */}
|
||||
<Card
|
||||
key={params.reward_asset}
|
||||
title={t('Vega Reward pot')}
|
||||
className="lg:col-span-3 xl:col-span-2"
|
||||
loading={loading}
|
||||
highlight={true}
|
||||
>
|
||||
<RewardPot
|
||||
pubKey={pubKey}
|
||||
accounts={accounts}
|
||||
assetId={params.reward_asset}
|
||||
vestingBalancesSummary={rewardsData?.party?.vestingBalancesSummary}
|
||||
/>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('Vesting')}
|
||||
className="lg:col-span-3 xl:col-span-2"
|
||||
loading={loading}
|
||||
>
|
||||
<Vesting
|
||||
pubKey={pubKey}
|
||||
baseRate={params.rewards_vesting_baseRate}
|
||||
multiplier={
|
||||
rewardsData?.party?.activityStreak?.rewardVestingMultiplier
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('Rewards multipliers')}
|
||||
className="lg:col-span-3 xl:col-span-2"
|
||||
loading={loading}
|
||||
highlight={true}
|
||||
>
|
||||
<Multipliers
|
||||
pubKey={pubKey}
|
||||
hoarderMultiplier={
|
||||
rewardsData?.party?.vestingStats?.rewardBonusMultiplier
|
||||
}
|
||||
streakMultiplier={
|
||||
rewardsData?.party?.activityStreak?.rewardDistributionMultiplier
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* Show all other reward pots, most of the time users will not have other rewards */}
|
||||
{assets
|
||||
.filter((assetId) => assetId !== params.reward_asset)
|
||||
.map((assetId) => {
|
||||
const asset = assetMap ? assetMap[assetId] : null;
|
||||
{/* Show all other reward pots, most of the time users will not have other rewards */}
|
||||
{assets
|
||||
.filter((assetId) => assetId !== params.reward_asset)
|
||||
.map((assetId) => {
|
||||
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
|
||||
// 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
|
||||
// show the user that they have rewards available to withdraw
|
||||
if (ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA.includes(asset.id)) {
|
||||
const accountsForAsset = rewardAccountsAssetMap[asset.id];
|
||||
const vestedAccount = accountsForAsset?.find(
|
||||
(a) => a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
);
|
||||
// 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
|
||||
//
|
||||
// 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
|
||||
if (ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA.includes(asset.id)) {
|
||||
const accountsForAsset = rewardAccountsAssetMap[asset.id];
|
||||
const vestedAccount = accountsForAsset?.find(
|
||||
(a) => a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
);
|
||||
|
||||
// No vested rewards available to withdraw, so skip over USDT
|
||||
if (!vestedAccount || Number(vestedAccount.balance) <= 0) {
|
||||
return null;
|
||||
// No vested rewards available to withdraw, so skip over USDT
|
||||
if (!vestedAccount || Number(vestedAccount.balance) <= 0) {
|
||||
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 (
|
||||
@ -197,48 +237,69 @@ export const RewardsContainer = () => {
|
||||
pubKey={pubKey}
|
||||
accounts={accounts}
|
||||
assetId={assetId}
|
||||
// Ensure that these values are shown as 0
|
||||
vestingBalancesSummary={{
|
||||
lockedBalances: [],
|
||||
vestingBalances: [],
|
||||
}}
|
||||
vestingBalancesSummary={
|
||||
rewardsData?.party?.vestingBalancesSummary
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
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}
|
||||
vestingBalancesSummary={
|
||||
rewardsData?.party?.vestingBalancesSummary
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
<div className="grid auto-rows-min grid-cols-6 gap-3">
|
||||
{pubKey && activityStreakBenefitTiers.tiers?.length > 0 && (
|
||||
<Card
|
||||
title={t('Activity Streak')}
|
||||
className={classNames(
|
||||
{
|
||||
'lg:col-span-6 xl:col-span-3':
|
||||
activityStreakBenefitTiers.tiers.length <= 4,
|
||||
'xl:col-span-6': activityStreakBenefitTiers.tiers.length > 4,
|
||||
},
|
||||
'hidden md:block'
|
||||
)}
|
||||
>
|
||||
<span className="flex flex-col mr-8 pr-4">
|
||||
<ActivityStreak
|
||||
tiers={activityStreakBenefitTiers.tiers}
|
||||
streak={partyActivityStreak}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
<Card
|
||||
title={t('Rewards history')}
|
||||
className="lg:col-span-full"
|
||||
loading={rewardsLoading}
|
||||
noBackgroundOnMobile={true}
|
||||
>
|
||||
<RewardsHistoryContainer
|
||||
epoch={Number(epochData?.epoch.id)}
|
||||
pubKey={pubKey}
|
||||
assets={assetMap}
|
||||
/>
|
||||
</Card>
|
||||
</span>
|
||||
</Card>
|
||||
)}
|
||||
{pubKey && vestingBenefitTiers.tiers?.length > 0 && (
|
||||
<Card
|
||||
title={t('Reward Hoarder Bonus')}
|
||||
className={classNames(
|
||||
{
|
||||
'lg:col-span-6 xl:col-span-3':
|
||||
vestingBenefitTiers.tiers.length <= 4,
|
||||
'xl:col-span-6': vestingBenefitTiers.tiers.length > 4,
|
||||
},
|
||||
'hidden md:block'
|
||||
)}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
@ -343,7 +404,7 @@ export const RewardPot = ({
|
||||
})}
|
||||
<VegaIcon name={VegaIconNames.LOCK} size={12} />
|
||||
</CardTableTH>
|
||||
<CardTableTD>
|
||||
<CardTableTD data-testid="locked-value">
|
||||
{addDecimalsFormatNumberQuantum(
|
||||
totalLocked.toString(),
|
||||
rewardAsset.decimals,
|
||||
@ -357,7 +418,7 @@ export const RewardPot = ({
|
||||
assetSymbol: rewardAsset.symbol,
|
||||
})}
|
||||
</CardTableTH>
|
||||
<CardTableTD>
|
||||
<CardTableTD data-testid="vesting-value">
|
||||
{addDecimalsFormatNumberQuantum(
|
||||
totalVesting.toString(),
|
||||
rewardAsset.decimals,
|
||||
@ -369,7 +430,7 @@ export const RewardPot = ({
|
||||
<CardTableTH>
|
||||
{t('Available to withdraw this epoch')}
|
||||
</CardTableTH>
|
||||
<CardTableTD>
|
||||
<CardTableTD data-testid="available-to-withdraw-value">
|
||||
{addDecimalsFormatNumberQuantum(
|
||||
totalVestedRewardsByRewardAsset.toString(),
|
||||
rewardAsset.decimals,
|
||||
@ -388,6 +449,7 @@ export const RewardPot = ({
|
||||
)
|
||||
}
|
||||
size="small"
|
||||
data-testid="redeem-rewards-button"
|
||||
>
|
||||
{t('Redeem rewards')}
|
||||
</TradingButton>
|
||||
@ -422,12 +484,16 @@ export const Vesting = ({
|
||||
<CardTable>
|
||||
<tr>
|
||||
<CardTableTH>{t('Base rate')}</CardTableTH>
|
||||
<CardTableTD>{baseRateFormatted}%</CardTableTD>
|
||||
<CardTableTD data-testid="base-rate-value">
|
||||
{baseRateFormatted}%
|
||||
</CardTableTD>
|
||||
</tr>
|
||||
{pubKey && (
|
||||
<tr>
|
||||
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
|
||||
<CardTableTD>{multiplier ? `${multiplier}x` : '-'}</CardTableTD>
|
||||
<CardTableTD data-testid="vesting multiplier-value">
|
||||
{multiplier ? `${multiplier}x` : '-'}
|
||||
</CardTableTD>
|
||||
</tr>
|
||||
)}
|
||||
</CardTable>
|
||||
@ -467,13 +533,13 @@ export const Multipliers = ({
|
||||
<CardTable>
|
||||
<tr>
|
||||
<CardTableTH>{t('Streak reward multiplier')}</CardTableTH>
|
||||
<CardTableTD>
|
||||
<CardTableTD data-testid="streak-reward-multiplier-value">
|
||||
{streakMultiplier ? `${streakMultiplier}x` : '-'}
|
||||
</CardTableTD>
|
||||
</tr>
|
||||
<tr>
|
||||
<CardTableTH>{t('Hoarder reward multiplier')}</CardTableTH>
|
||||
<CardTableTD>
|
||||
<CardTableTD data-testid="hoarder-reward-multiplier-value">
|
||||
{hoarderMultiplier ? `${hoarderMultiplier}x` : '-'}
|
||||
</CardTableTD>
|
||||
</tr>
|
||||
|
@ -319,6 +319,7 @@ export const RewardHistoryTable = ({
|
||||
onClick={() => setIsParty(false)}
|
||||
size="extra-small"
|
||||
minimal={isParty}
|
||||
data-testid="total-distributed-button"
|
||||
>
|
||||
{t('Total distributed')}
|
||||
</TradingButton>
|
||||
@ -327,6 +328,7 @@ export const RewardHistoryTable = ({
|
||||
size="extra-small"
|
||||
disabled={!pubKey}
|
||||
minimal={!isParty}
|
||||
data-testid="earned-by-me-button"
|
||||
>
|
||||
{t('Earned by me')}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
395
apps/trading/e2e/tests/rewards/test_rewards.py
Normal file
395
apps/trading/e2e/tests/rewards/test_rewards.py
Normal file
@ -0,0 +1,395 @@
|
||||
import pytest
|
||||
import logging
|
||||
import vega_sim.proto.vega as vega_protos
|
||||
from typing import Tuple, Any
|
||||
from playwright.sync_api import Page, expect
|
||||
from conftest import init_vega, init_page, auth_setup
|
||||
from fixtures.market import setup_continuous_market, market_exists
|
||||
from actions.utils import next_epoch, change_keys
|
||||
from wallet_config import MM_WALLET, PARTY_A, PARTY_B, PARTY_C, PARTY_D
|
||||
from vega_sim.service import VegaService
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def market_ids():
|
||||
return {
|
||||
"vega_activity_tier_0": "default_id",
|
||||
"vega_hoarder_tier_0": "default_id",
|
||||
"vega_combo_tier_0": "default_id",
|
||||
"vega_activity_tier_1": "default_id",
|
||||
"vega_hoarder_tier_1": "default_id",
|
||||
"vega_combo_tier_1": "default_id",
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_activity_tier_0(request):
|
||||
# with init_vega(request) as vega_activity_tier_0:
|
||||
yield vega_activity_tier_0
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_hoarder_tier_0(request):
|
||||
# with init_vega(request) as vega_hoarder_tier_0:
|
||||
yield vega_hoarder_tier_0
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_combo_tier_0(request):
|
||||
# with init_vega(request) as vega_combo_tier_0:
|
||||
yield vega_combo_tier_0
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_activity_tier_1(request):
|
||||
with init_vega(request) as vega_activity_tier_1:
|
||||
yield vega_activity_tier_1
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_hoarder_tier_1(request):
|
||||
#with init_vega(request) as vega_hoarder_tier_1:
|
||||
yield vega_hoarder_tier_1
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_combo_tier_1(request):
|
||||
# with init_vega(request) as vega_combo_tier_1:
|
||||
yield vega_combo_tier_1
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def auth(vega_instance, page):
|
||||
vega, _, _ = vega_instance
|
||||
return auth_setup(vega, page)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page(vega_instance, browser, request):
|
||||
vega, _, _ = vega_instance
|
||||
with init_page(vega, browser, request) as page_instance:
|
||||
yield page_instance
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def vega_instance(
|
||||
reward_program: str,
|
||||
vega_activity_tier_0: Any,
|
||||
vega_hoarder_tier_0: Any,
|
||||
vega_combo_tier_0: Any,
|
||||
vega_activity_tier_1: Any,
|
||||
vega_hoarder_tier_1: Any,
|
||||
vega_combo_tier_1: Any,
|
||||
market_ids: list,
|
||||
tier: int,
|
||||
) -> Tuple[Any, Any, Any]:
|
||||
"""
|
||||
Create a Vega instance based on the reward program and tier.
|
||||
|
||||
:param reward_program: The reward program type.
|
||||
:param vega_activity_tier_0: The Vega instance for activity tier 0.
|
||||
:param vega_hoarder_tier_0: The Vega instance for hoarder tier 0.
|
||||
:param vega_combo_tier_0: The Vega instance for combo tier 0.
|
||||
:param vega_activity_tier_1: The Vega instance for activity tier 1.
|
||||
:param vega_hoarder_tier_1: The Vega instance for hoarder tier 1.
|
||||
:param vega_combo_tier_1: The Vega instance for combo tier 1.
|
||||
:param market_ids: List of market IDs.
|
||||
:param tier: The tier level.
|
||||
:return: Tuple containing the Vega instance, market ID, and tDAI asset ID.
|
||||
"""
|
||||
|
||||
vega_tiers = {
|
||||
"activity": (vega_activity_tier_0, vega_activity_tier_1),
|
||||
"hoarder": (vega_hoarder_tier_0, vega_hoarder_tier_1),
|
||||
"combo": (vega_combo_tier_0, vega_combo_tier_1),
|
||||
}
|
||||
|
||||
if reward_program not in vega_tiers or tier not in (0, 1):
|
||||
logging.error(f"Invalid reward_program '{reward_program}' or tier '{tier}'")
|
||||
raise ValueError(f"Invalid reward_program '{reward_program}' or tier '{tier}'")
|
||||
|
||||
vega = vega_tiers[reward_program][tier]
|
||||
|
||||
# Set up market with the reward program
|
||||
logging.info("Setting up Vega Instance")
|
||||
market_id, tDAI_asset_id = set_market_reward_program(
|
||||
vega, reward_program, market_ids, tier
|
||||
)
|
||||
|
||||
return vega, market_id, tDAI_asset_id
|
||||
|
||||
|
||||
def setup_market_with_reward_program(vega: VegaService, reward_programs, tier):
|
||||
print("Started setup_market_with_reward_program")
|
||||
tDAI_market = setup_continuous_market(vega)
|
||||
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
|
||||
vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000)
|
||||
vega.mint(key_name=PARTY_C.name, asset=tDAI_asset_id, amount=100000)
|
||||
vega.mint(key_name=PARTY_A.name, asset=tDAI_asset_id, amount=100000)
|
||||
vega.mint(key_name=PARTY_D.name, asset=tDAI_asset_id, amount=100000)
|
||||
next_epoch(vega=vega)
|
||||
if "activity" in reward_programs:
|
||||
vega.update_network_parameter(
|
||||
proposal_key=MM_WALLET.name,
|
||||
parameter="rewards.activityStreak.benefitTiers",
|
||||
new_value=ACTIVITY_STREAKS,
|
||||
)
|
||||
print("update_network_parameter activity done")
|
||||
next_epoch(vega=vega)
|
||||
|
||||
if "hoarder" in reward_programs:
|
||||
vega.update_network_parameter(
|
||||
proposal_key=MM_WALLET.name,
|
||||
parameter="rewards.vesting.benefitTiers",
|
||||
new_value=VESTING,
|
||||
)
|
||||
next_epoch(vega=vega)
|
||||
|
||||
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
|
||||
vega.update_network_parameter(
|
||||
MM_WALLET.name, parameter="reward.asset", new_value=tDAI_asset_id
|
||||
)
|
||||
|
||||
next_epoch(vega=vega)
|
||||
vega.recurring_transfer(
|
||||
from_key_name=PARTY_A.name,
|
||||
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
|
||||
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||
asset=tDAI_asset_id,
|
||||
reference="reward",
|
||||
asset_for_metric=tDAI_asset_id,
|
||||
metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
|
||||
# lock_period= 5,
|
||||
amount=100,
|
||||
factor=1.0,
|
||||
)
|
||||
vega.submit_order(
|
||||
trading_key=PARTY_B.name,
|
||||
market_id=tDAI_market,
|
||||
order_type="TYPE_MARKET",
|
||||
time_in_force="TIME_IN_FORCE_IOC",
|
||||
side="SIDE_BUY",
|
||||
volume=1,
|
||||
)
|
||||
vega.submit_order(
|
||||
trading_key=PARTY_A.name,
|
||||
market_id=tDAI_market,
|
||||
order_type="TYPE_MARKET",
|
||||
time_in_force="TIME_IN_FORCE_IOC",
|
||||
side="SIDE_BUY",
|
||||
volume=1,
|
||||
)
|
||||
|
||||
vega.wait_for_total_catchup()
|
||||
""" if tier == 1:
|
||||
next_epoch(vega=vega)
|
||||
vega.submit_order(
|
||||
trading_key=PARTY_B.name,
|
||||
market_id=tDAI_market,
|
||||
order_type="TYPE_MARKET",
|
||||
time_in_force="TIME_IN_FORCE_IOC",
|
||||
side="SIDE_BUY",
|
||||
volume=1,
|
||||
)
|
||||
vega.submit_order(
|
||||
trading_key=PARTY_D.name,
|
||||
market_id=tDAI_market,
|
||||
order_type="TYPE_MARKET",
|
||||
time_in_force="TIME_IN_FORCE_IOC",
|
||||
side="SIDE_BUY",
|
||||
volume=1,
|
||||
)
|
||||
vega.wait_for_total_catchup() """
|
||||
|
||||
#next_epoch(vega=vega)
|
||||
return tDAI_market, tDAI_asset_id
|
||||
|
||||
|
||||
def set_market_reward_program(vega, reward_program, market_ids, tier):
|
||||
market_id_key = f"vega_{reward_program}"
|
||||
if reward_program == "combo":
|
||||
market_id_key = "combo"
|
||||
|
||||
market_id = market_ids.get(market_id_key, "default_id")
|
||||
|
||||
print(f"Checking if market exists: {market_id}")
|
||||
if not market_exists(vega, market_id):
|
||||
print(f"Market doesn't exist for {reward_program}. Setting up new market.")
|
||||
|
||||
reward_programs = [reward_program]
|
||||
if reward_program == "combo":
|
||||
reward_programs = ["activity", "hoarder"]
|
||||
|
||||
market_id = setup_market_with_reward_program(vega, reward_programs, tier)
|
||||
market_ids[market_id_key] = market_id
|
||||
|
||||
print(f"Using market ID: {market_id}")
|
||||
return market_id
|
||||
|
||||
|
||||
ACTIVITY_STREAKS = """
|
||||
{
|
||||
"tiers": [
|
||||
{
|
||||
"minimum_activity_streak": 1,
|
||||
"reward_multiplier": "2.0",
|
||||
"vesting_multiplier": "1.1"
|
||||
},
|
||||
{
|
||||
"minimum_activity_streak": 5,
|
||||
"reward_multiplier": "3.0",
|
||||
"vesting_multiplier": "1.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
VESTING = """
|
||||
{
|
||||
"tiers": [
|
||||
{
|
||||
"minimum_quantum_balance": "5000000",
|
||||
"reward_multiplier": "2"
|
||||
},
|
||||
{
|
||||
"minimum_quantum_balance": "11666668",
|
||||
"reward_multiplier": "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reward_program, tier, total_rewards",
|
||||
[
|
||||
#("activity", 0, "50.00 tDAI"),
|
||||
#("hoarder", 0, "50.00 tDAI"),
|
||||
#("combo", 0, "50.00 tDAI"),
|
||||
("activity", 1, "110.00 tDAI"),
|
||||
#("hoarder", 1, "116.66666 tDAI"),
|
||||
#("combo", 1, "125.00 tDAI"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.skip("tbd")
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_network_reward_pot(
|
||||
reward_program, vega_instance: VegaService, page: Page, total_rewards, tier
|
||||
):
|
||||
vega, market_id, tDAI_asset_id = vega_instance
|
||||
next_epoch(vega=vega)
|
||||
|
||||
page.goto(f"/#/rewards")
|
||||
if tier == 1:
|
||||
page.pause()
|
||||
next_epoch(vega=vega)
|
||||
next_epoch(vega=vega)
|
||||
vega.submit_order(
|
||||
trading_key=PARTY_B.name,
|
||||
market_id=market_id,
|
||||
order_type="TYPE_MARKET",
|
||||
time_in_force="TIME_IN_FORCE_IOC",
|
||||
side="SIDE_BUY",
|
||||
volume=1,
|
||||
)
|
||||
vega.submit_order(
|
||||
trading_key=PARTY_D.name,
|
||||
market_id=market_id,
|
||||
order_type="TYPE_MARKET",
|
||||
time_in_force="TIME_IN_FORCE_IOC",
|
||||
side="SIDE_BUY",
|
||||
volume=1,
|
||||
)
|
||||
vega.wait_for_total_catchup()
|
||||
page.pause()
|
||||
next_epoch(vega=vega)
|
||||
page.pause()
|
||||
next_epoch(vega=vega)
|
||||
change_keys(page, vega, PARTY_B.name)
|
||||
page.pause()
|
||||
|
||||
expect(page.get_by_test_id("total-rewards")).to_have_text(total_rewards)
|
||||
# TODO Add test ID and Assert for locked,
|
||||
|
||||
|
||||
|
||||
""" @pytest.mark.parametrize(
|
||||
"reward_program",
|
||||
[
|
||||
("activity"),
|
||||
# ("hoarder"),
|
||||
# ("combo"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_vesting(vega_setup, vega: VegaService, page: Page):
|
||||
expect() """
|
||||
|
||||
@pytest.mark.skip("tbd")
|
||||
@pytest.mark.parametrize(
|
||||
"reward_program, tier, reward_multiplier",
|
||||
[
|
||||
("activity", 0, "1x"),
|
||||
("hoarder", 0, "1x"),
|
||||
("combo", 0, "1x"),
|
||||
("activity", 1, "2x"),
|
||||
("hoarder", 1, "2x"),
|
||||
("combo", 1, "4x"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_reward_multiplier(reward_program, vega_instance: VegaService, page: Page, reward_multiplier, tier):
|
||||
vega, market_id, tDAI_asset_id = vega_instance
|
||||
page.goto(f"/#/rewards")
|
||||
change_keys(page, vega, PARTY_B.name)
|
||||
expect(page.get_by_test_id("combined-multipliers")).to_have_text(reward_multiplier)
|
||||
#TODO add test ids and assert for individual multipliers
|
||||
|
||||
"""
|
||||
@pytest.mark.parametrize(
|
||||
"reward_program",
|
||||
[
|
||||
("activity"),
|
||||
# ("hoarder"),
|
||||
# ("combo"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_activity_streak(vega_setup, vega: VegaService, page: Page):
|
||||
expect()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reward_program",
|
||||
[
|
||||
("activity"),
|
||||
# ("hoarder"),
|
||||
# ("combo"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_hoarder_Bonus(vega_setup, vega: VegaService, page: Page):
|
||||
expect()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reward_program",
|
||||
[
|
||||
("activity"),
|
||||
# ("hoarder"),
|
||||
# ("combo"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_Rewards_history(vega_setup, vega: VegaService, page: Page):
|
||||
expect()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_redeem(vega_setup, vega: VegaService, page: Page):
|
||||
expect()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_redeem(vega_setup, vega: VegaService, page: Page):
|
||||
expect()
|
||||
"""
|
@ -11,5 +11,7 @@ GOVERNANCE_WALLET = WalletConfig(
|
||||
"FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs")
|
||||
PARTY_A = WalletConfig("party_a", "party_a")
|
||||
PARTY_B = WalletConfig("party_b", "party_b")
|
||||
PARTY_C = WalletConfig("party_c", "party_c")
|
||||
PARTY_D = WalletConfig("party_d", "party_d")
|
||||
|
||||
wallets = [MM_WALLET, MM_WALLET2, TERMINATE_WALLET, GOVERNANCE_WALLET]
|
||||
wallets = [MM_WALLET, MM_WALLET2, TERMINATE_WALLET, GOVERNANCE_WALLET, PARTY_A, PARTY_B, PARTY_C, PARTY_D]
|
||||
|
@ -13,9 +13,9 @@ export default function Document() {
|
||||
{/* preload fonts */}
|
||||
<link
|
||||
rel="preload"
|
||||
href="/AlphaLyrae.woff2"
|
||||
href="/AlphaLyrae.woff"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
type="font/woff"
|
||||
/>
|
||||
|
||||
{/* icons */}
|
||||
|
BIN
apps/trading/public/AlphaLyrae-Medium.woff
Normal file
BIN
apps/trading/public/AlphaLyrae-Medium.woff
Normal file
Binary file not shown.
@ -26,7 +26,6 @@
|
||||
"Best offer": "Best offer",
|
||||
"Browse": "Browse",
|
||||
"By using the Vega Console, you acknowledge that you have read and understood the <0>Vega Console Disclaimer</0>": "By using the Vega Console, you acknowledge that you have read and understood the <0>Vega Console Disclaimer</0>",
|
||||
"Chart": "Chart",
|
||||
"Change (24h)": "Change (24h)",
|
||||
"Changes have been proposed for this market. <0>View proposals</0>": "Changes have been proposed for this market. <0>View proposals</0>",
|
||||
"Chart": "Chart",
|
||||
@ -351,5 +350,30 @@
|
||||
"Your code has been rejected": "Your code has been rejected",
|
||||
"Your identity is always anonymous on Vega": "Your identity is always anonymous on Vega",
|
||||
"Your referral code": "Your referral code",
|
||||
"Your tier": "Your tier"
|
||||
"Your tier": "Your tier",
|
||||
"Number of epochs after distribution to delay vesting of rewards by": "Number of epochs after distribution to delay vesting of rewards by",
|
||||
"numberEpochs": "{{count}} epochs",
|
||||
"numberEpochs_other": "{{count}} epochs",
|
||||
"numberEpochs_one": "{{count}} epoch",
|
||||
"epochsStreak": "{{count}} epochs streak",
|
||||
"epochStreak_one": "{{count}} epoch streak",
|
||||
"Get rewards for providing liquidity. Get rewards for providing liquidity.": "Get rewards for providing liquidity. Get rewards for providing liquidity.",
|
||||
"Entity scope": "Entity scope",
|
||||
"Staked VEGA": "Staked VEGA",
|
||||
"Average position": "Average position",
|
||||
"Individual": "Individual",
|
||||
"Rewards funded using the pro-rata strategy should be distributed pro-rata by each entity's reward metric scaled by any active multipliers that party has": " Rewards funded using the pro-rata strategy should be distributed pro-rata by each entity's reward metric scaled by any active multipliers that party has",
|
||||
"Tier {{tier}}": "Tier {{tier}}",
|
||||
"Reward {{reward}}x": "Reward {{reward}}x",
|
||||
"Vesting {{vesting}}x": "Vesting {{vesting}}x",
|
||||
"Tier {{userTier}}": "Tier {{userTier}}",
|
||||
"{{reward}}x": "{{reward}}x",
|
||||
"Reward bonus": "Reward bonus",
|
||||
"Activity Streak": "Activity Streak",
|
||||
"userActive": "{{active}} trader: {{count}} epochs so far",
|
||||
"(Tier {{tier}} as of last epoch)": "(Tier {{tier}} as of last epoch)",
|
||||
"Team": "Team",
|
||||
"Ends in": "Ends in",
|
||||
"Assessed over": "Assessed over",
|
||||
"No rows": "No rows"
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ export const NetworkParams = {
|
||||
blockchains_ethereumConfig: 'blockchains_ethereumConfig',
|
||||
reward_asset: 'reward_asset',
|
||||
rewards_activityStreak_benefitTiers: 'rewards_activityStreak_benefitTiers',
|
||||
rewards_vesting_benefitTiers: 'rewards_vesting_benefitTiers',
|
||||
rewards_marketCreationQuantumMultiple:
|
||||
'rewards_marketCreationQuantumMultiple',
|
||||
reward_staking_delegation_payoutDelay:
|
||||
|
@ -558,7 +558,7 @@ const WarningCell = ({
|
||||
<div className="flex items-center justify-end">
|
||||
{showIcon && (
|
||||
<span className="mr-2 text-black dark:text-white">
|
||||
<VegaIcon name={VegaIconNames.EXCLAIMATION_MARK} size={12} />
|
||||
<VegaIcon name={VegaIconNames.EXCLAMATION_MARK} size={12} />
|
||||
</span>
|
||||
)}
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
|
@ -94,7 +94,7 @@ export const ProtocolUpgradeCountdown = ({
|
||||
}
|
||||
)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.EXCLAIMATION_MARK} size={12} />{' '}
|
||||
<VegaIcon name={VegaIconNames.EXCLAMATION_MARK} size={12} />{' '}
|
||||
<span className="flex flex-nowrap gap-1 whitespace-nowrap">
|
||||
<span>{t('Network upgrade in {{countdown}}', { countdown })} </span>
|
||||
</span>
|
||||
|
@ -199,6 +199,12 @@ module.exports = {
|
||||
'AlphaLyrae, "Helvetica Neue", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
||||
{ fontFeatureSettings: '"calt" 0, "liga" 0' },
|
||||
],
|
||||
glitch: [
|
||||
'AlphaLyrae, monospace',
|
||||
{
|
||||
fontFeatureSettings: '"ss02" 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
keyframes: {
|
||||
rotate: {
|
||||
|
@ -1,9 +1,11 @@
|
||||
import type {
|
||||
ConditionOperator,
|
||||
EntityScope,
|
||||
GovernanceTransferKind,
|
||||
GovernanceTransferType,
|
||||
PeggedReference,
|
||||
ProposalChange,
|
||||
TransferStatus,
|
||||
} from './__generated__/types';
|
||||
import type { AccountType } from './__generated__/types';
|
||||
import type {
|
||||
@ -36,32 +38,33 @@ import type { ProductType, ProposalProductType } from './product';
|
||||
export const AccountTypeMapping: {
|
||||
[T in AccountType]: string;
|
||||
} = {
|
||||
ACCOUNT_TYPE_BOND: 'Bond',
|
||||
ACCOUNT_TYPE_EXTERNAL: 'External',
|
||||
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: 'Fees Infrastructure',
|
||||
ACCOUNT_TYPE_FEES_LIQUIDITY: 'Fees Liquidity',
|
||||
ACCOUNT_TYPE_FEES_MAKER: 'Fees Maker',
|
||||
ACCOUNT_TYPE_GENERAL: 'General',
|
||||
ACCOUNT_TYPE_GLOBAL_INSURANCE: 'Global Insurance',
|
||||
ACCOUNT_TYPE_GLOBAL_REWARD: 'Global Reward',
|
||||
ACCOUNT_TYPE_INSURANCE: 'Insurance',
|
||||
ACCOUNT_TYPE_MARGIN: 'Margin',
|
||||
ACCOUNT_TYPE_PENDING_TRANSFERS: 'Pending transfers',
|
||||
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: 'Pending fee referral reward',
|
||||
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: 'Reward LP received fees',
|
||||
ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: 'Reward Maker received fees',
|
||||
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: 'Reward Market Proposers',
|
||||
ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: 'Reward Maker paid fees',
|
||||
ACCOUNT_TYPE_SETTLEMENT: 'Settlement',
|
||||
ACCOUNT_TYPE_HOLDING: 'Holding',
|
||||
ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees',
|
||||
ACCOUNT_TYPE_NETWORK_TREASURY: 'Network Treasury',
|
||||
ACCOUNT_TYPE_REWARD_AVERAGE_POSITION: 'Reward Average Position',
|
||||
ACCOUNT_TYPE_REWARD_RELATIVE_RETURN: 'Reward Relative Return',
|
||||
ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Reward Return Volatility',
|
||||
ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Reward Validator Ranking',
|
||||
ACCOUNT_TYPE_VESTED_REWARDS: 'Vested Rewards',
|
||||
ACCOUNT_TYPE_VESTING_REWARDS: 'Vesting Rewards',
|
||||
ACCOUNT_TYPE_BOND: 'Bond account',
|
||||
ACCOUNT_TYPE_EXTERNAL: 'External account',
|
||||
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: 'Infrastructure fees account',
|
||||
ACCOUNT_TYPE_FEES_LIQUIDITY: 'Liquidity fees account',
|
||||
ACCOUNT_TYPE_FEES_MAKER: 'Maker fees account',
|
||||
ACCOUNT_TYPE_GENERAL: 'General account',
|
||||
ACCOUNT_TYPE_GLOBAL_INSURANCE: 'Global insurance account',
|
||||
ACCOUNT_TYPE_GLOBAL_REWARD: 'Global reward account',
|
||||
ACCOUNT_TYPE_INSURANCE: 'Insurance account',
|
||||
ACCOUNT_TYPE_MARGIN: 'Margin account',
|
||||
ACCOUNT_TYPE_PENDING_TRANSFERS: 'Pending transfers account',
|
||||
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD:
|
||||
'Pending fee referral reward account',
|
||||
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: 'LP received fees reward account',
|
||||
ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: 'Maker received fees reward account',
|
||||
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: 'Market proposers reward account',
|
||||
ACCOUNT_TYPE_REWARD_AVERAGE_POSITION: 'Average position reward account',
|
||||
ACCOUNT_TYPE_REWARD_RELATIVE_RETURN: 'Relative return reward account',
|
||||
ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Volatility return reward account',
|
||||
ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Validator ranking reward account',
|
||||
ACCOUNT_TYPE_VESTED_REWARDS: 'Vested rewards account',
|
||||
ACCOUNT_TYPE_VESTING_REWARDS: 'Vesting rewards account',
|
||||
ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: 'Maker fees paid account',
|
||||
ACCOUNT_TYPE_SETTLEMENT: 'Settlement account',
|
||||
ACCOUNT_TYPE_HOLDING: 'Holding account',
|
||||
ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP liquidity fees account',
|
||||
ACCOUNT_TYPE_NETWORK_TREASURY: 'Network treasury account',
|
||||
};
|
||||
|
||||
/**
|
||||
@ -579,17 +582,36 @@ export const GovernanceTransferKindMapping: GovernanceTransferKindMap = {
|
||||
type DispatchMetricLabel = {
|
||||
[T in DispatchMetric]: string;
|
||||
};
|
||||
|
||||
export const DispatchMetricLabels: DispatchMetricLabel = {
|
||||
DISPATCH_METRIC_LP_FEES_RECEIVED: 'Liquidity Provision fees received',
|
||||
DISPATCH_METRIC_LP_FEES_RECEIVED: 'Liquidity provision fees received',
|
||||
DISPATCH_METRIC_MAKER_FEES_PAID: 'Price maker fees paid',
|
||||
DISPATCH_METRIC_MAKER_FEES_RECEIVED: 'Price maker fees earned',
|
||||
DISPATCH_METRIC_MARKET_VALUE: 'Total market Value',
|
||||
DISPATCH_METRIC_MARKET_VALUE: 'Total market value',
|
||||
DISPATCH_METRIC_AVERAGE_POSITION: 'Average position',
|
||||
DISPATCH_METRIC_RELATIVE_RETURN: 'Relative return',
|
||||
DISPATCH_METRIC_RETURN_VOLATILITY: 'Return volatility',
|
||||
DISPATCH_METRIC_VALIDATOR_RANKING: 'Validator ranking',
|
||||
};
|
||||
|
||||
export const DispatchMetricDescription: DispatchMetricLabel = {
|
||||
DISPATCH_METRIC_LP_FEES_RECEIVED: 'Get rewards for providing liquidity.',
|
||||
DISPATCH_METRIC_MAKER_FEES_PAID:
|
||||
'Get rewards for taking prices off the order book and paying fees.',
|
||||
DISPATCH_METRIC_MAKER_FEES_RECEIVED:
|
||||
'Get rewards for making prices on the order book.',
|
||||
DISPATCH_METRIC_MARKET_VALUE:
|
||||
'Get rewards if a market you proposed attracts a high trading volume.',
|
||||
DISPATCH_METRIC_AVERAGE_POSITION:
|
||||
'Get rewards for having an open position that is consistently larger than that of other traders.',
|
||||
DISPATCH_METRIC_RELATIVE_RETURN:
|
||||
'Get rewards for having a high profit in relation to your position size.',
|
||||
DISPATCH_METRIC_RETURN_VOLATILITY:
|
||||
'Get rewards for having the least amount of variance in your returns while you have a position open during the rewards window.',
|
||||
DISPATCH_METRIC_VALIDATOR_RANKING:
|
||||
'Get rewards if you run a validator node with a high ranking score.',
|
||||
};
|
||||
|
||||
export const PositionStatusMapping: {
|
||||
[T in PositionStatus]: string;
|
||||
} = {
|
||||
@ -599,6 +621,16 @@ export const PositionStatusMapping: {
|
||||
POSITION_STATUS_DISTRESSED: 'Distressed',
|
||||
};
|
||||
|
||||
export const TransferStatusMapping: {
|
||||
[T in TransferStatus]: string;
|
||||
} = {
|
||||
STATUS_DONE: 'Done',
|
||||
STATUS_PENDING: 'Pending',
|
||||
STATUS_REJECTED: 'Rejected',
|
||||
STATUS_CANCELLED: 'Cancelled',
|
||||
STATUS_STOPPED: 'Stopped',
|
||||
};
|
||||
|
||||
export const ConditionOperatorMapping: { [C in ConditionOperator]: string } = {
|
||||
OPERATOR_EQUALS: 'Equals',
|
||||
OPERATOR_GREATER_THAN: 'Greater than',
|
||||
@ -631,6 +663,35 @@ export const ProposalProductTypeMapping: Record<ProposalProductType, string> = {
|
||||
PerpetualProduct: 'Perpetual',
|
||||
};
|
||||
|
||||
export const EntityScopeMapping: { [e in EntityScope]: string } = {
|
||||
/** Rewards must be distributed directly to eligible parties */
|
||||
ENTITY_SCOPE_INDIVIDUALS:
|
||||
'Rewards must be distributed directly to eligible parties',
|
||||
/** Rewards must be distributed directly to eligible teams, and then amongst team members */
|
||||
ENTITY_SCOPE_TEAMS:
|
||||
'Rewards must be distributed directly to eligible teams, and then amongst team members',
|
||||
};
|
||||
|
||||
export const EntityScopeLabelMapping: { [e in EntityScope]: string } = {
|
||||
/** Rewards must be distributed directly to eligible parties */
|
||||
ENTITY_SCOPE_INDIVIDUALS: 'Individual',
|
||||
/** Rewards must be distributed directly to eligible teams, and then amongst team members */
|
||||
ENTITY_SCOPE_TEAMS: 'Team',
|
||||
};
|
||||
|
||||
export enum DistributionStrategyMapping {
|
||||
/** Rewards funded using the pro-rata strategy should be distributed pro-rata by each entity's reward metric scaled by any active multipliers that party has */
|
||||
DISTRIBUTION_STRATEGY_PRO_RATA = 'Pro rata',
|
||||
/** Rewards funded using the rank strategy */
|
||||
DISTRIBUTION_STRATEGY_RANK = 'Strategy rank',
|
||||
}
|
||||
|
||||
export enum DistributionStrategyDescriptionMapping {
|
||||
DISTRIBUTION_STRATEGY_PRO_RATA = `Rewards funded using the pro-rata strategy should be distributed pro-rata by each entity's reward metric scaled by any active multipliers that party has`,
|
||||
/** Rewards funded using the rank strategy */
|
||||
DISTRIBUTION_STRATEGY_RANK = 'Rewards funded using the rank strategy',
|
||||
}
|
||||
|
||||
export const ProposalProductTypeShortName: Record<ProposalProductType, string> =
|
||||
{
|
||||
FutureProduct: 'Futr',
|
||||
|
@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.20755 15.7736H6V4H8.20755V15.7736ZM10.4151 17.8585V15.7736H8.20755V17.9811H10.4151V20.066H12.6226V17.9811H14.8302V15.7736H12.6226V17.8585H10.4151ZM16.7925 13.566H14.5849V4H16.7925V13.566ZM16.7925 13.566H19V15.7736H16.7925V13.566Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 401 B |
@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9946 3C10.2165 3.00106 8.47842 3.52883 6.99987 4.51677C5.51983 5.5057 4.36628 6.91131 3.68509 8.55585C3.0039 10.2004 2.82567 12.01 3.17294 13.7558C3.5202 15.5016 4.37737 17.1053 5.63604 18.364C6.89471 19.6226 8.49836 20.4798 10.2442 20.8271C11.99 21.1743 13.7996 20.9961 15.4442 20.3149C17.0887 19.6337 18.4943 18.4802 19.4832 17.0001C20.4722 15.5201 21 13.78 21 12C21 9.61305 20.0518 7.32386 18.364 5.63604C16.6761 3.94821 14.3869 3 12 3C12.0001 3 11.9999 3 12 3M11.9959 3.936C11.9972 3.936 11.9985 3.936 11.9999 3.936C13.2976 3.93617 14.3772 5.83592 14.9515 8.22998H9.04712C9.62068 5.83664 10.6991 3.93971 11.9959 3.936ZM9.8073 4.24157C9.05208 5.16925 8.44481 6.56185 8.07534 8.22998H4.87438C5.24741 7.52551 5.72602 6.87397 6.3 6.3C7.28288 5.31711 8.49319 4.61388 9.8073 4.24157ZM4.42885 9.22998C4.10667 10.1091 3.93685 11.0458 3.936 12C3.936 12.9499 4.10378 13.8872 4.42669 14.77H7.88922C7.75324 13.8973 7.67969 12.9663 7.67969 12C7.67969 11.0336 7.7527 10.1027 7.8879 9.22998H4.42885ZM4.87153 15.77C5.00006 16.013 5.14133 16.2501 5.29503 16.4801C6.18112 17.8062 7.44054 18.8398 8.91404 19.4502C9.20977 19.5727 9.51146 19.677 9.81744 19.763C9.06048 18.8354 8.44956 17.4409 8.07765 15.77H4.87153ZM14.1834 19.7628C15.5101 19.3896 16.7227 18.6815 17.7021 17.7021C18.2744 17.1298 18.7541 16.4778 19.1285 15.77H15.9224C15.5508 17.4416 14.9402 18.8355 14.1834 19.7628ZM19.5733 14.77C19.7153 14.3819 19.8278 13.9819 19.9091 13.5732C20.1981 12.12 20.0808 10.6174 19.5733 9.22998H16.1106C16.2463 10.1024 16.3197 11.0333 16.3197 12C16.3197 12.9667 16.2463 13.8976 16.1106 14.77H19.5733ZM19.1285 8.22998C18.5047 7.05058 17.596 6.04063 16.4801 5.29503C15.7711 4.82129 14.9955 4.46564 14.1834 4.23723C14.9402 5.16453 15.5508 6.55844 15.9224 8.22998H19.1285ZM8.60129 12C8.60129 11.0806 8.68603 10.1352 8.84194 9.22998H15.1569C15.3132 10.1358 15.3981 11.0814 15.3981 12C15.3981 12.9186 15.314 13.8642 15.1588 14.77H8.84003C8.68519 13.8648 8.60129 12.9194 8.60129 12ZM11.9997 20.064C10.6916 20.064 9.61486 18.1657 9.04394 15.77H14.9547C14.3836 18.1642 13.3072 20.064 11.9997 20.064Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.2 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 4.32353V19.6765C21 20.0275 20.8606 20.3641 20.6123 20.6123C20.3641 20.8606 20.0275 21 19.6765 21H4.32353C3.97251 21 3.63586 20.8606 3.38765 20.6123C3.13944 20.3641 3 20.0275 3 19.6765V4.32353C3 3.97251 3.13944 3.63586 3.38765 3.38765C3.63586 3.13944 3.97251 3 4.32353 3H19.6765C20.0275 3 20.3641 3.13944 20.6123 3.38765C20.8606 3.63586 21 3.97251 21 4.32353V4.32353ZM8.29412 9.88235H5.64706V18.3529H8.29412V9.88235ZM8.53235 6.97059C8.53375 6.77036 8.49569 6.57182 8.42035 6.3863C8.34502 6.20078 8.23387 6.03191 8.09328 5.88935C7.95268 5.74678 7.78537 5.6333 7.60091 5.5554C7.41646 5.47749 7.21846 5.43668 7.01824 5.43529H6.97059C6.5634 5.43529 6.1729 5.59705 5.88497 5.88497C5.59705 6.1729 5.43529 6.5634 5.43529 6.97059C5.43529 7.37777 5.59705 7.76828 5.88497 8.05621C6.1729 8.34413 6.5634 8.50588 6.97059 8.50588V8.50588C7.17083 8.51081 7.37008 8.47623 7.55696 8.40413C7.74383 8.33202 7.91467 8.2238 8.0597 8.08565C8.20474 7.94749 8.32113 7.78212 8.40223 7.59897C8.48333 7.41582 8.52755 7.21848 8.53235 7.01824V6.97059ZM18.3529 13.2071C18.3529 10.6606 16.7329 9.67059 15.1235 9.67059C14.5966 9.6442 14.0719 9.75644 13.6019 9.9961C13.1318 10.2358 12.7328 10.5945 12.4447 11.0365H12.3706V9.88235H9.88235V18.3529H12.5294V13.8476C12.4911 13.3862 12.6365 12.9283 12.9339 12.5735C13.2313 12.2186 13.6567 11.9954 14.1176 11.9524H14.2182C15.06 11.9524 15.6847 12.4818 15.6847 13.8159V18.3529H18.3318L18.3529 13.2071Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,5 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1C4.13 1 1 4.13 1 8C1 11.87 4.13 15 8 15C11.87 15 15 11.87 15 8C15 4.13 11.87 1 8 1ZM8.66 12.44H7.32V11.1H8.66V12.44ZM10.38 6.78C10.29 7.01 10.18 7.2 10.05 7.36C9.92 7.52 9.75 7.7 9.53 7.91C9.3 8.14 9.11 8.34 8.98 8.51C8.85 8.68 8.73 8.89 8.64 9.14C8.55 9.37 8.51 9.65 8.51 9.96V10.04V10.13H7.3V10.04C7.3 9.61 7.36 9.24 7.47 8.92C7.58 8.61 7.71 8.34 7.87 8.13C8.03 7.92 8.22 7.69 8.47 7.43C8.75 7.13 8.96 6.87 9.09 6.66C9.22 6.46 9.28 6.2 9.28 5.88C9.28 5.47 9.16 5.16 8.93 4.93C8.7 4.7 8.38 4.58 7.96 4.58C7.6 4.58 7.28 4.68 7.01 4.89C6.75 5.09 6.56 5.44 6.45 5.96C6.34 6.48 6.43 6.06 6.43 6.06L5.26 5.62L5.28 5.54C5.47 4.87 5.81 4.35 6.29 4.02C6.77 3.69 7.34 3.53 8 3.53C8.75 3.53 9.37 3.75 9.82 4.18C10.28 4.62 10.5 5.22 10.5 5.97C10.5 6.27 10.46 6.53 10.37 6.76L10.38 6.78Z" fill="white"/>
|
||||
<circle cx="8" cy="8" r="7" fill="white"/>
|
||||
<path d="M6.15393 5.69232C6.15393 5.10304 6.24054 4.5075 6.46161 4C4.99179 4.63982 4 6.14089 4 7.84607C4 10.1402 5.85982 12 8.15393 12C9.85911 12 11.3602 11.0082 12 9.53839C11.4925 9.75946 10.8964 9.84607 10.3077 9.84607C8.01357 9.84607 6.15393 7.98643 6.15393 5.69232Z" fill="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1C4.13 1 1 4.13 1 8C1 11.87 4.13 15 8 15C11.87 15 15 11.87 15 8C15 4.13 11.87 1 8 1ZM8.66 12.44H7.32V11.1H8.66V12.44ZM10.38 6.78C10.29 7.01 10.18 7.2 10.05 7.36C9.92 7.52 9.75 7.7 9.53 7.91C9.3 8.14 9.11 8.34 8.98 8.51C8.85 8.68 8.73 8.89 8.64 9.14C8.55 9.37 8.51 9.65 8.51 9.96V10.04V10.13H7.3V10.04C7.3 9.61 7.36 9.24 7.47 8.92C7.58 8.61 7.71 8.34 7.87 8.13C8.03 7.92 8.22 7.69 8.47 7.43C8.75 7.13 8.96 6.87 9.09 6.66C9.22 6.46 9.28 6.2 9.28 5.88C9.28 5.47 9.16 5.16 8.93 4.93C8.7 4.7 8.38 4.58 7.96 4.58C7.6 4.58 7.28 4.68 7.01 4.89C6.75 5.09 6.56 5.44 6.45 5.96L6.43 6.06L5.26 5.62L5.28 5.54C5.47 4.87 5.81 4.35 6.29 4.02C6.77 3.69 7.34 3.53 8 3.53C8.75 3.53 9.37 3.75 9.82 4.18C10.28 4.62 10.5 5.22 10.5 5.97C10.5 6.27 10.46 6.53 10.37 6.76L10.38 6.78Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 929 B |
@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.28743 20.2575C15.8423 20.2575 19.9641 14 19.9641 8.58084C19.9641 8.4012 19.9641 8.22156 19.9541 8.0519C20.7525 7.47305 21.4511 6.74451 22 5.92615C21.2615 6.25549 20.4731 6.47505 19.6447 6.57485C20.493 6.06587 21.1417 5.26747 21.4511 4.2994C20.6627 4.76846 19.7844 5.10778 18.8463 5.29741C18.0978 4.499 17.0299 4 15.8523 4C13.5868 4 11.7505 5.83633 11.7505 8.1018C11.7505 8.42116 11.7904 8.74052 11.8603 9.03992C8.43713 8.86028 5.40319 7.22355 3.38723 4.7485C3.02794 5.34731 2.82834 6.05589 2.82834 6.80439C2.82834 8.23154 3.55689 9.48902 4.65469 10.2176C3.97605 10.2076 3.34731 10.018 2.7984 9.70858C2.7984 9.72854 2.7984 9.73852 2.7984 9.75848C2.7984 11.7445 4.21557 13.4112 6.09182 13.7804C5.74251 13.8703 5.38323 13.9202 5.01397 13.9202C4.75449 13.9202 4.49501 13.8902 4.24551 13.8503C4.76447 15.477 6.28144 16.6647 8.07784 16.7046C6.67066 17.8024 4.90419 18.4611 2.97804 18.4611C2.6487 18.4611 2.31936 18.4411 2 18.4012C3.80639 19.5788 5.96208 20.2575 8.28743 20.2575Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,4 +1,4 @@
|
||||
export const IconExclaimationMark = ({ size = 16 }: { size: number }) => {
|
||||
export const IconExclamationMark = ({ size = 16 }: { size: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||
<path d="M8 0.879997L7.57 1.63L0.130005 14.5H15.87L8 0.879997ZM8.75 12H7.25V10.5H8.75V12ZM7.25 9.5V6H8.75V9.5H7.25Z" />
|
@ -0,0 +1,21 @@
|
||||
export const IconMan = ({ size = 14 }: { size: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M8.00016 7.99996C9.47292 7.99996 10.6668 6.80605 10.6668 5.33329C10.6668 3.86053 9.47292 2.66663 8.00016 2.66663C6.5274 2.66663 5.3335 3.86053 5.3335 5.33329C5.3335 6.80605 6.5274 7.99996 8.00016 7.99996Z"
|
||||
stroke="#DCDEE3"
|
||||
strokeWidth="1.33333"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="stroke-current"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8.00033 9.33333C5.79119 9.33333 4.00033 11.1242 4.00033 13.3333V13.8667C4.00033 13.9403 3.94063 14 3.86699 14H2.80033C2.72669 14 2.66699 13.9403 2.66699 13.8667V13.3333C2.66699 10.3878 5.05481 8 8.00033 8C10.9458 8 13.3337 10.3878 13.3337 13.3333V13.8667C13.3337 13.9403 13.274 14 13.2003 14H12.1337C12.06 14 12.0003 13.9403 12.0003 13.8667V13.3333C12.0003 11.1242 10.2095 9.33333 8.00033 9.33333Z"
|
||||
fill="#DCDEE3"
|
||||
className="fill-current"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -1,7 +1,13 @@
|
||||
export const IconQuestionMark = ({ size = 16 }: { size: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||
<path d="M8 1C4.13 1 1 4.13 1 8C1 11.87 4.13 15 8 15C11.87 15 15 11.87 15 8C15 4.13 11.87 1 8 1ZM8.66 12.44H7.32V11.1H8.66V12.44ZM10.38 6.78C10.29 7.01 10.18 7.2 10.05 7.36C9.92 7.52 9.75 7.7 9.53 7.91C9.3 8.14 9.11 8.34 8.98 8.51C8.85 8.68 8.73 8.89 8.64 9.14C8.55 9.37 8.51 9.65 8.51 9.96V10.04V10.13H7.3V10.04C7.3 9.61 7.36 9.24 7.47 8.92C7.58 8.61 7.71 8.34 7.87 8.13C8.03 7.92 8.22 7.69 8.47 7.43C8.75 7.13 8.96 6.87 9.09 6.66C9.22 6.46 9.28 6.2 9.28 5.88C9.28 5.47 9.16 5.16 8.93 4.93C8.7 4.7 8.38 4.58 7.96 4.58C7.6 4.58 7.28 4.68 7.01 4.89C6.75 5.09 6.56 5.44 6.45 5.96L6.43 6.06L5.26 5.62L5.28 5.54C5.47 4.87 5.81 4.35 6.29 4.02C6.77 3.69 7.34 3.53 8 3.53C8.75 3.53 9.37 3.75 9.82 4.18C10.28 4.62 10.5 5.22 10.5 5.97C10.5 6.27 10.46 6.53 10.37 6.76L10.38 6.78Z" />
|
||||
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8 1C4.13 1 1 4.13 1 8C1 11.87 4.13 15 8 15C11.87 15 15 11.87 15 8C15 4.13 11.87 1 8 1ZM8.66 12.44H7.32V11.1H8.66V12.44ZM10.38 6.78C10.29 7.01 10.18 7.2 10.05 7.36C9.92 7.52 9.75 7.7 9.53 7.91C9.3 8.14 9.11 8.34 8.98 8.51C8.85 8.68 8.73 8.89 8.64 9.14C8.55 9.37 8.51 9.65 8.51 9.96V10.04V10.13H7.3V10.04C7.3 9.61 7.36 9.24 7.47 8.92C7.58 8.61 7.71 8.34 7.87 8.13C8.03 7.92 8.22 7.69 8.47 7.43C8.75 7.13 8.96 6.87 9.09 6.66C9.22 6.46 9.28 6.2 9.28 5.88C9.28 5.47 9.16 5.16 8.93 4.93C8.7 4.7 8.38 4.58 7.96 4.58C7.6 4.58 7.28 4.68 7.01 4.89C6.75 5.09 6.56 5.44 6.45 5.96L6.43 6.06L5.26 5.62L5.28 5.54C5.47 4.87 5.81 4.35 6.29 4.02C6.77 3.69 7.34 3.53 8 3.53C8.75 3.53 9.37 3.75 9.82 4.18C10.28 4.62 10.5 5.22 10.5 5.97C10.5 6.27 10.46 6.53 10.37 6.76L10.38 6.78Z"
|
||||
fill="white"
|
||||
className="fill-current"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,14 @@
|
||||
export const IconStreak = ({ size = 14 }: { size: number }) => {
|
||||
return (
|
||||
<svg width="14" height="14" viewBox="0 0 14 14">
|
||||
<path d="M5.999 4H3.99902V5.99997H5.999V4Z" />
|
||||
<path d="M7.999 2.0001V0.00012207H5.99902V2.0001V4.00007H7.999V2.0001Z" />
|
||||
<path d="M3.99897 6H-0.000976562V7.99997H3.99897V6Z" />
|
||||
<path d="M5.999 7.99988H3.99902V9.99985H5.999V7.99988Z" />
|
||||
<path d="M7.999 9.99994H5.99902V13.9999H7.999V9.99994Z" />
|
||||
<path d="M9.999 7.99988H7.99902V9.99985H9.999V7.99988Z" />
|
||||
<path d="M13.999 6H9.99902V7.99997H13.999V6Z" />
|
||||
<path d="M9.999 4H7.99902V5.99997H9.999V4Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
export const IconTeam = ({ size = 14 }: { size: number }) => {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
id="Vector"
|
||||
d="M13.1282 11.1863C12.7371 10.7949 12.2801 10.4754 11.7782 10.2426C12.486 9.66912 12.9375 8.79412 12.9375 7.81287C12.9375 6.08162 11.4938 4.6613 9.76254 4.68787C8.05785 4.71443 6.68441 6.10349 6.68441 7.81287C6.68441 8.79412 7.13754 9.66912 7.84379 10.2426C7.34177 10.4753 6.88476 10.7947 6.49379 11.1863C5.64066 12.041 5.15629 13.1691 5.12504 14.3722C5.12462 14.3889 5.12755 14.4055 5.13364 14.421C5.13974 14.4366 5.14888 14.4507 5.16053 14.4627C5.17218 14.4746 5.1861 14.4841 5.20147 14.4906C5.21684 14.497 5.23336 14.5004 5.25004 14.5004H6.12504C6.19223 14.5004 6.24848 14.4472 6.25004 14.3801C6.27973 13.4738 6.64691 12.6254 7.29223 11.9816C7.62245 11.6496 8.01523 11.3865 8.44784 11.2073C8.88045 11.0281 9.3443 10.9366 9.81254 10.9379C10.7641 10.9379 11.6594 11.3082 12.3329 11.9816C12.9766 12.6254 13.3438 13.4738 13.375 14.3801C13.3766 14.4472 13.4329 14.5004 13.5 14.5004H14.375C14.3917 14.5004 14.4082 14.497 14.4236 14.4906C14.439 14.4841 14.4529 14.4746 14.4646 14.4627C14.4762 14.4507 14.4853 14.4366 14.4914 14.421C14.4975 14.4055 14.5005 14.3889 14.5 14.3722C14.4688 13.1691 13.9844 12.041 13.1282 11.1863ZM9.81254 9.81287C9.27816 9.81287 8.77504 9.60505 8.39848 9.22693C8.2095 9.03944 8.06022 8.8158 7.95955 8.56937C7.85888 8.32293 7.80888 8.05874 7.81254 7.79255C7.81723 7.28005 8.02191 6.78474 8.37973 6.41755C8.75473 6.03318 9.25629 5.81912 9.79223 5.81287C10.3219 5.80818 10.836 6.01443 11.2141 6.38474C11.6016 6.76443 11.8141 7.27224 11.8141 7.81287C11.8141 8.34724 11.6063 8.8488 11.2282 9.22693C11.0427 9.41333 10.822 9.56109 10.579 9.66167C10.336 9.76224 10.0755 9.81363 9.81254 9.81287ZM5.89848 8.22537C5.88441 8.08943 5.8766 7.95193 5.8766 7.81287C5.8766 7.56443 5.90004 7.32224 5.94379 7.0863C5.95473 7.03005 5.92504 6.97224 5.87348 6.9488C5.66098 6.85349 5.46566 6.72224 5.29691 6.55662C5.09807 6.36382 4.9416 6.13168 4.83748 5.87503C4.73337 5.61837 4.6839 5.34283 4.69223 5.06599C4.70629 4.56443 4.90785 4.08787 5.25941 3.72849C5.64535 3.33318 6.1641 3.11755 6.71566 3.1238C7.2141 3.12849 7.69535 3.32068 8.05941 3.6613C8.18285 3.77693 8.2891 3.90505 8.37817 4.04255C8.40942 4.09099 8.47035 4.1113 8.52348 4.09255C8.79848 3.99724 9.0891 3.93005 9.38754 3.8988C9.47504 3.88943 9.52504 3.79568 9.48598 3.71755C8.97816 2.71287 7.94066 2.01912 6.74066 2.00037C5.00785 1.9738 3.5641 3.39412 3.5641 5.1238C3.5641 6.10505 4.01566 6.98005 4.72348 7.55349C4.2266 7.78318 3.76879 8.10037 3.37191 8.49724C2.51566 9.35193 2.03129 10.4801 2.00004 11.6847C1.99962 11.7014 2.00255 11.718 2.00864 11.7335C2.01474 11.7491 2.02388 11.7632 2.03553 11.7752C2.04718 11.7871 2.0611 11.7966 2.07647 11.8031C2.09184 11.8095 2.10836 11.8129 2.12504 11.8129H3.0016C3.06879 11.8129 3.12504 11.7597 3.1266 11.6926C3.15629 10.7863 3.52348 9.93787 4.16879 9.29412C4.62816 8.83474 5.19066 8.51599 5.80473 8.3613C5.86566 8.34568 5.90629 8.28787 5.89848 8.22537Z"
|
||||
className="fill-current"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -14,7 +14,7 @@ import { IconCopy } from './svg-icons/icon-copy';
|
||||
import { IconCross } from './svg-icons/icon-cross';
|
||||
import { IconDeposit } from './svg-icons/icon-deposit';
|
||||
import { IconEdit } from './svg-icons/icon-edit';
|
||||
import { IconExclaimationMark } from './svg-icons/icon-exclaimation-mark';
|
||||
import { IconExclamationMark } from './svg-icons/icon-exclamation-mark';
|
||||
import { IconEye } from './svg-icons/icon-eye';
|
||||
import { IconEyeOff } from './svg-icons/icon-eye-off';
|
||||
import { IconForum } from './svg-icons/icon-forum';
|
||||
@ -41,6 +41,9 @@ import { IconTwitter } from './svg-icons/icon-twitter';
|
||||
import { IconVote } from './svg-icons/icon-vote';
|
||||
import { IconWarning } from './svg-icons/icon-warning';
|
||||
import { IconWithdraw } from './svg-icons/icon-withdraw';
|
||||
import { IconMan } from './svg-icons/icon-man';
|
||||
import { IconTeam } from './svg-icons/icon-team';
|
||||
import { IconStreak } from './svg-icons/icon-streak';
|
||||
|
||||
export enum VegaIconNames {
|
||||
ARROW_DOWN = 'arrow-down',
|
||||
@ -59,7 +62,7 @@ export enum VegaIconNames {
|
||||
CROSS = 'cross',
|
||||
DEPOSIT = 'deposit',
|
||||
EDIT = 'edit',
|
||||
EXCLAIMATION_MARK = 'exclaimation-mark',
|
||||
EXCLAMATION_MARK = 'exclamation-mark',
|
||||
EYE = 'eye',
|
||||
EYE_OFF = 'eye-off',
|
||||
FORUM = 'forum',
|
||||
@ -76,6 +79,7 @@ export enum VegaIconNames {
|
||||
QUESTION_MARK = 'question-mark',
|
||||
SEARCH = 'search',
|
||||
STAR = 'star',
|
||||
STREAK = 'streak',
|
||||
SUN = 'sun',
|
||||
TICK = 'tick',
|
||||
TICKET = 'ticket',
|
||||
@ -86,6 +90,8 @@ export enum VegaIconNames {
|
||||
VOTE = 'vote',
|
||||
WITHDRAW = 'withdraw',
|
||||
WARNING = 'warning',
|
||||
MAN = 'man',
|
||||
TEAM = 'team',
|
||||
}
|
||||
|
||||
export const VegaIconNameMap: Record<
|
||||
@ -102,7 +108,7 @@ export const VegaIconNameMap: Record<
|
||||
'chevron-right': IconChevronRight,
|
||||
'chevron-up': IconChevronUp,
|
||||
'eye-off': IconEyeOff,
|
||||
'exclaimation-mark': IconExclaimationMark,
|
||||
'exclamation-mark': IconExclamationMark,
|
||||
'open-external': IconOpenExternal,
|
||||
'question-mark': IconQuestionMark,
|
||||
'trend-down': IconTrendDown,
|
||||
@ -135,4 +141,7 @@ export const VegaIconNameMap: Record<
|
||||
vote: IconVote,
|
||||
withdraw: IconWithdraw,
|
||||
warning: IconWarning,
|
||||
man: IconMan,
|
||||
team: IconTeam,
|
||||
streak: IconStreak,
|
||||
};
|
||||
|
@ -3,9 +3,11 @@ import classNames from 'classnames';
|
||||
import type { VegaIconNames } from './vega-icon-record';
|
||||
import { VegaIconNameMap } from './vega-icon-record';
|
||||
|
||||
export type VegaIconSize = 8 | 10 | 12 | 13 | 14 | 16 | 18 | 20 | 24 | 28 | 32;
|
||||
|
||||
export interface VegaIconProps {
|
||||
name: VegaIconNames;
|
||||
size?: 8 | 10 | 12 | 13 | 14 | 16 | 18 | 20 | 24 | 28 | 32;
|
||||
size?: VegaIconSize;
|
||||
}
|
||||
|
||||
export const VegaIcon = ({ size = 16, name }: VegaIconProps) => {
|
||||
|
Loading…
Reference in New Issue
Block a user