Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions apps/evm/src/clients/api/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { assetData } from '__mocks__/models/asset';
import { importablePositions } from '__mocks__/models/importablePositions';
import { poolData } from '__mocks__/models/pools';
import { primeEstimationData } from '__mocks__/models/primeEstimation';
import { usdc, xvs } from '__mocks__/models/tokens';
import { tradePositions } from '__mocks__/models/trade';
import { transactions } from '__mocks__/models/transactions';
import { fixedRatedVaults, vaults } from '__mocks__/models/vaults';
Expand Down Expand Up @@ -512,6 +513,191 @@ export const useGetPrimeEstimation = vi.fn(() =>
}),
);

export const getPrimeUserPendingRewards = vi.fn(async () => ({
blockNumber: '1',
isPrimeHolder: true,
rank: 2,
totalPendingUsdCents: '1840000',
rewards: [],
}));
export const useGetPrimeUserPendingRewards = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PRIME_USER_PENDING_REWARDS],
queryFn: getPrimeUserPendingRewards,
}),
);

export const getPrimeEffectiveStake = vi.fn(async () => ({
effectiveStakeMantissa: new BigNumber('542500000').multipliedBy(1e18),
totalStakedMantissa: new BigNumber('5432').multipliedBy(1e18),
}));
export const useGetPrimeEffectiveStake = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PRIME_EFFECTIVE_STAKE],
queryFn: getPrimeEffectiveStake,
}),
);

export const getPrimeMinimumStake = vi.fn(async () => ({
blockNumber: '1',
computedAt: null,
tokenLimit: 500,
totalTokens: 500,
mintThresholdMantissa: null,
minimumStakeMantissa: new BigNumber('10000').multipliedBy(1e18).toFixed(),
reason: 'last_position' as const,
}));
export const useGetPrimeMinimumStake = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PRIME_MINIMUM_STAKE],
queryFn: getPrimeMinimumStake,
}),
);

export const getPrimeCurrentCycle = vi.fn(async () => ({
cycle: {
cycleIndex: 1,
status: 'active',
startsAt: new Date('2026-06-01T00:00:00Z'),
endsAt: new Date('2026-07-01T00:00:00Z'),
anchorBlockNum: null,
mintLimitUsed: 0,
},
pendingPool: {
blockNumber: '1',
computedAt: new Date('2026-06-17T00:00:00Z'),
primeHolderCount: 500,
totalPendingUsdCents: '46230000',
byRewardToken: [
{
rewardTokenAddress: usdc.address,
totalPendingUsdCents: '28040000',
totalPendingMantissa: '0',
},
{
rewardTokenAddress: xvs.address,
totalPendingUsdCents: '17190000',
totalPendingMantissa: '0',
},
],
},
}));
export const useGetPrimeCurrentCycle = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PRIME_CURRENT_CYCLE],
queryFn: getPrimeCurrentCycle,
}),
);

export const getPrimeLeaderboard = vi.fn(async () => ({
blockNumber: '1',
computedAt: new Date('2026-06-17T00:00:00Z'),
page: 1,
limit: 10,
total: 2,
entries: [
{
userAddress: fakeAddress,
rank: 1,
effectiveStakeMantissa: new BigNumber('613500000').multipliedBy(1e18).toFixed(),
totalStakedMantissa: '0',
isPrimeHolder: true,
},
{
userAddress: fakeAddress,
rank: 2,
effectiveStakeMantissa: new BigNumber('542500000').multipliedBy(1e18).toFixed(),
totalStakedMantissa: '0',
isPrimeHolder: true,
},
],
}));
export const useGetPrimeLeaderboard = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PRIME_LEADERBOARD],
queryFn: getPrimeLeaderboard,
}),
);

export const getPrimeRewardsLeaderboard = vi.fn(async () => ({
blockNumber: '1',
computedAt: new Date('2026-06-17T00:00:00Z'),
page: 1,
limit: 10,
total: 1,
entries: [
{
userAddress: fakeAddress,
totalPendingUsdCents: '50000',
byRewardToken: [
{
rewardTokenAddress: usdc.address,
pendingUsdCents: '4000000',
pendingAmountMantissa: '0',
},
{ rewardTokenAddress: xvs.address, pendingUsdCents: '2236000', pendingAmountMantissa: '0' },
],
},
],
}));
export const useGetPrimeRewardsLeaderboard = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PRIME_REWARDS_LEADERBOARD],
queryFn: getPrimeRewardsLeaderboard,
}),
);

export const getPrimePastCycle = vi.fn(async () => ({
cycle: {
cycleIndex: 1,
status: 'finalized',
startsAt: new Date('2026-05-01T00:00:00Z'),
endsAt: new Date('2026-06-01T00:00:00Z'),
mintLimitUsed: 0,
totalRewardPoolUsdCents: '46230000',
finalizedAt: new Date('2026-06-01T00:00:00Z'),
},
markets: [],
ranking: [
{
userAddress: fakeAddress,
finalRank: 1,
finalEffectiveStakeMantissa: new BigNumber('542500000').multipliedBy(1e18).toFixed(),
finalTotalStakedMantissa: '0',
},
],
}));
export const useGetPrimePastCycle = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PRIME_PAST_CYCLE],
queryFn: getPrimePastCycle,
}),
);

export const getPrimeUserCycleRewards = vi.fn(async () => ({
totalRewardUsdCents: '1840000',
markets: [
{
marketAddress: usdc.address,
rewardTokenAddress: usdc.address,
totalRewardMantissa: '0',
totalRewardUsdCents: '1140000',
},
{
marketAddress: xvs.address,
rewardTokenAddress: xvs.address,
totalRewardMantissa: '0',
totalRewardUsdCents: '700000',
},
],
}));
export const useGetPrimeUserCycleRewards = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PRIME_USER_CYCLE_REWARDS],
queryFn: getPrimeUserCycleRewards,
}),
);

export const getPrimeDistributionForMarket = vi.fn(async () => ({
totalDistributedMantissa: new BigNumber('1230000000000000000000000'),
}));
Expand Down
33 changes: 33 additions & 0 deletions apps/evm/src/clients/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,39 @@ export * from './queries/useGetPrimeEstimation';
export * from './queries/getPrimeDistributionForMarket';
export * from './queries/getPrimeDistributionForMarket/useGetPrimeDistributionForMarket';

export * from './queries/getPrimeLeaderboard';
export * from './queries/getPrimeLeaderboard/useGetPrimeLeaderboard';

export * from './queries/getPrimeRewardsLeaderboard';
export * from './queries/getPrimeRewardsLeaderboard/useGetPrimeRewardsLeaderboard';

export * from './queries/getPrimeMinimumStake';
export * from './queries/getPrimeMinimumStake/useGetPrimeMinimumStake';

export * from './queries/getPrimeCurrentCycle';
export * from './queries/getPrimeCurrentCycle/useGetPrimeCurrentCycle';

export * from './queries/getPrimeUserPendingRewards';
export * from './queries/getPrimeUserPendingRewards/useGetPrimeUserPendingRewards';

export * from './queries/getPrimeCycles';
export * from './queries/getPrimeCycles/useGetPrimeCycles';

export * from './queries/getPrimePastCycle';
export * from './queries/getPrimePastCycle/useGetPrimePastCycle';

export * from './queries/getPrimeUserCycleRewards';
export * from './queries/getPrimeUserCycleRewards/useGetPrimeUserCycleRewards';

export * from './queries/getPrimeEffectiveStake';
export * from './queries/getPrimeEffectiveStake/useGetPrimeEffectiveStake';

export * from './queries/getPrimeMultiplierTiers';
export * from './queries/getPrimeMultiplierTiers/useGetPrimeMultiplierTiers';

export * from './queries/getPrimeOnChainPendingRewards';
export * from './queries/getPrimeOnChainPendingRewards/useGetPrimeOnChainPendingRewards';

export * from './queries/getVaiVaultPaused';
export * from './queries/getVaiVaultPaused/useGetVaiVaultPaused';

Expand Down
98 changes: 98 additions & 0 deletions apps/evm/src/clients/api/queries/getPrimeCurrentCycle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { VError } from 'libs/errors';
import type { ChainId } from 'types';
import { restService } from 'utilities';
import type { Address } from 'viem';

export interface PrimeCurrentCycle {
cycleIndex: number;
status: string;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an enum so we can list all the possible statuses.

startsAt: Date;
endsAt: Date;
anchorBlockNum: string | null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No null

mintLimitUsed: number;
}

export interface PrimePendingRewardTokenTotal {
rewardTokenAddress: Address;
totalPendingUsdCents: string;
totalPendingMantissa: string;
}

export interface PrimePendingRewardPool {
blockNumber: string;
computedAt: Date;
primeHolderCount: number;
totalPendingUsdCents: string;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to add "Usd" into the name (the repo considers that dollars are the the only on-crypto currency used).

byRewardToken: PrimePendingRewardTokenTotal[];
}

export interface GetPrimeCurrentCycleInput {
chainId: ChainId;
}

export interface GetPrimeCurrentCycleOutput {
cycle: PrimeCurrentCycle | null;
pendingPool: PrimePendingRewardPool | null;
}

interface PrimeCurrentCycleResponse {
cycleIndex: number;
status: string;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here, let's use an enum.

startsAt: string;
endsAt: string;
anchorBlockNum: string | null;
mintLimitUsed: number;
}

interface PrimePendingRewardPoolResponse {
blockNumber: string;
computedAt: string;
primeHolderCount: number;
totalPendingUsdCents: string;
byRewardToken: PrimePendingRewardTokenTotal[];
}

interface GetPrimeCurrentCycleResponse {
cycle: PrimeCurrentCycleResponse | null;
pendingPool: PrimePendingRewardPoolResponse | null;
}

export const getPrimeCurrentCycle = async ({
chainId,
}: GetPrimeCurrentCycleInput): Promise<GetPrimeCurrentCycleOutput> => {
const response = await restService<GetPrimeCurrentCycleResponse>({
endpoint: '/prime/cycle/current',
method: 'GET',
params: { chainId },
});

const payload = response.data;

if (payload && 'error' in payload) {
throw new VError({
type: 'unexpected',
code: 'somethingWentWrong',
data: { exception: payload.error },
});
}

if (!payload) {
throw new VError({ type: 'unexpected', code: 'somethingWentWrong' });
}

return {
cycle: payload.cycle
? {
...payload.cycle,
startsAt: new Date(payload.cycle.startsAt),
endsAt: new Date(payload.cycle.endsAt),
}
: null,
pendingPool: payload.pendingPool
? {
...payload.pendingPool,
computedAt: new Date(payload.pendingPool.computedAt),
}
: null,
Comment on lines +84 to +96

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not use null in the frontend repo, but rather undefined. null has many many issues: https://craftbettersoftware.com/p/stop-using-null-its-a-bad-practice

};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type QueryObserverOptions, useQuery } from '@tanstack/react-query';

import FunctionKey from 'constants/functionKey';
import { useChainId } from 'libs/wallet';

import {
type GetPrimeCurrentCycleInput,
type GetPrimeCurrentCycleOutput,
getPrimeCurrentCycle,
} from '.';

type Options = QueryObserverOptions<
GetPrimeCurrentCycleOutput,
Error,
GetPrimeCurrentCycleOutput,
GetPrimeCurrentCycleOutput,
[FunctionKey.GET_PRIME_CURRENT_CYCLE, GetPrimeCurrentCycleInput]
>;

export const useGetPrimeCurrentCycle = (options?: Partial<Options>) => {
const { chainId } = useChainId();

return useQuery({
queryKey: [FunctionKey.GET_PRIME_CURRENT_CYCLE, { chainId }],
queryFn: () => getPrimeCurrentCycle({ chainId }),
...options,
});
};
Loading
Loading