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
6 changes: 6 additions & 0 deletions .changeset/puny-lamps-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@venusprotocol/chains": minor
"@venusprotocol/evm": minor
---

add institutional vaults
148 changes: 139 additions & 9 deletions apps/evm/src/__mocks__/models/vaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import BigNumber from 'bignumber.js';
import type { GetFixedRatedVaultsOutput } from 'clients/api/queries/getFixedRatedVaults';

import {
type InstitutionalVault,
type LockedDeposit,
VaultCategory,
VaultManager,
VaultStatus,
VaultType,
type VenusVault,
} from 'types';

import type { Address } from 'viem';
import { vai, xvs } from './tokens';
import { usdc, vai, xvs } from './tokens';

export const vaults: VenusVault[] = [
{
Expand All @@ -22,10 +24,11 @@ export const vaults: VenusVault[] = [
rewardTokenPriceCents: new BigNumber(100),
dailyEmissionMantissa: new BigNumber('144000000000000000000'),
dailyEmissionCents: 14400,
totalStakedMantissa: new BigNumber('415000000000000000000'),
totalStakedCents: 41500,
stakingAprPercentage: 12665.060240963856,
stakeBalanceMantissa: new BigNumber('415000000000000000000'),
stakeBalanceCents: 41500,
stakeAprPercentage: 12665.060240963856,
category: VaultCategory.STABLECOINS,
vaultType: VaultType.Venus,
manager: VaultManager.Venus,
managerIcon: 'logoMobile',
status: VaultStatus.Active,
Expand All @@ -40,12 +43,13 @@ export const vaults: VenusVault[] = [
rewardTokenPriceCents: new BigNumber(100),
dailyEmissionMantissa: new BigNumber('144000000000000000000'),
dailyEmissionCents: 14400,
totalStakedMantissa: new BigNumber('400000000000000000000000000'),
totalStakedCents: 40000000000,
stakingAprPercentage: 12.92281835063781,
userStakedMantissa: new BigNumber('233000000000000000000'),
userStakedCents: 23300,
stakeBalanceMantissa: new BigNumber('400000000000000000000000000'),
stakeBalanceCents: 40000000000,
stakeAprPercentage: 12.92281835063781,
userStakeBalanceMantissa: new BigNumber('233000000000000000000'),
userStakeBalanceCents: 23300,
category: VaultCategory.GOVERNANCE,
vaultType: VaultType.Venus,
manager: VaultManager.Venus,
managerIcon: 'logoMobile',
status: VaultStatus.Active,
Expand Down Expand Up @@ -98,11 +102,137 @@ export const fixedRatedVaults: GetFixedRatedVaultsOutput = [
maturityDate: '2026-06-25T00:00:00.000Z',
createdAt: '2026-01-21T20:14:15.000Z',
updatedAt: '2026-01-21T20:14:15.000Z',
tokenPrices: [
{
id: 'fake-price-pendle',
tokenAddress: '0xe052823b4aefc6e230FAf46231A57d0905E30AE0',
tokenWrappedAddress: null,
chainId: '56',
priceMantissa: '682687557196753800000000000000000000000',
priceSource: 'oracle',
priceOracleAddress: '0x0000000000000000000000000000000000000001',
mainOracleAddress: '0x0000000000000000000000000000000000000001',
mainOracleName: 'ResilientOracle',
isPriceInvalid: false,
hasErrorFetchingPrice: false,
createdAt: '2026-01-21T20:14:15.000Z',
updatedAt: '2026-01-21T20:14:15.000Z',
},
],
},
],
},
{
id: '97-institutional-0x5263D68786AaCfad74B9aa385A004c272548e8B7',
chainId: '97',
protocol: 'institutional-vault',
vaultAddress: '0x5263D68786AaCfad74B9aa385A004c272548e8B7',
underlyingAssetAddress: '0x312e39c7641cE64BEccDe53613f07952258fa810',
fixedApyDecimal: '0.08',
maturityDate: '2026-09-01T00:00:00.000Z',
protocolData: {
collateralAssetAddress: '0xCC3933141a64E26C9317b19CE4BbB4ec2c333bc6',
institutionOperatorAddress: '0x1111111111111111111111111111111111111111',
latePenaltyRateMantissa: '0',
lockDurationSeconds: 2592000,
openDurationSeconds: 604800,
settlementWindowSeconds: 259200,
},
createdAt: '2026-04-01T00:00:00.000Z',
updatedAt: '2026-04-01T00:00:00.000Z',
loanVaultDetail: {
chainId: '97',
collateralAssetAddress: '0xCC3933141a64E26C9317b19CE4BbB4ec2c333bc6',
collateralValueCents: '0',
createdAt: '2026-04-01T00:00:00.000Z',
debtValueCents: '0',
fixedRateVaultId: '97-institutional-0x5263D68786AaCfad74B9aa385A004c272548e8B7',
id: 'loan-vault-detail-1',
institutionAddress: '0x1111111111111111111111111111111111111111',
latePenaltyRateMantissa: '0',
liquidationIncentiveMantissa: '0',
liquidationThresholdMantissa: '0',
liquidityMantissa: '500000000000',
lockEndTime: '2026-08-29T00:00:00.000Z',
maxBorrowCapMantissa: '1000000000000',
minBorrowCapMantissa: '100000000000',
minSupplierDepositMantissa: '10000000',
openEndTime: '2026-04-08T00:00:00.000Z',
outstandingDebtMantissa: '0',
reserveFactorMantissa: '0',
settlementDeadline: '2026-09-01T00:00:00.000Z',
shortfallMantissa: '0',
supplyAssetAddress: '0x312e39c7641cE64BEccDe53613f07952258fa810',
totalOwedMantissa: '0',
totalRaisedMantissa: '500000000000',
updatedAt: '2026-04-01T00:00:00.000Z',
vaultState: 2,
},
underlyingToken: [
{
address: '0x312e39c7641cE64BEccDe53613f07952258fa810',
chainId: '97',
name: 'Mock USDC',
symbol: 'MOCK_USDC',
decimals: 6,
maturityDate: '2026-09-01T00:00:00.000Z',
createdAt: '2026-04-01T00:00:00.000Z',
updatedAt: '2026-04-01T00:00:00.000Z',
tokenPrices: [
{
id: 'fake-price-institutional',
tokenAddress: '0x312e39c7641cE64BEccDe53613f07952258fa810',
tokenWrappedAddress: null,
chainId: '97',
priceMantissa: '1000000000000000000000000000000',
priceSource: 'oracle',
priceOracleAddress: '0x0000000000000000000000000000000000000001',
mainOracleAddress: '0x0000000000000000000000000000000000000001',
mainOracleName: 'ResilientOracle',
isPriceInvalid: false,
hasErrorFetchingPrice: false,
createdAt: '2026-04-01T00:00:00.000Z',
updatedAt: '2026-04-01T00:00:00.000Z',
},
],
},
],
},
];

export const institutionalVault: InstitutionalVault = {
vaultType: VaultType.Institutional,
category: VaultCategory.STABLECOINS,
manager: VaultManager.Ceffu,
managerIcon: 'ceefu',
managerAddress: '0x1111111111111111111111111111111111111111',
managerLink: 'https://www.ceffu.com',
status: VaultStatus.Pending,
key: '97-institutional-0x5263D68786AaCfad74B9aa385A004c272548e8B7',
stakedToken: usdc,
rewardToken: usdc,
stakedTokenPriceCents: new BigNumber(100),
rewardTokenPriceCents: new BigNumber(100),
stakeAprPercentage: 8,
stakeBalanceMantissa: new BigNumber('500000000000'),
stakeBalanceCents: 50000000,
userStakeBalanceMantissa: new BigNumber('100000000'),
userStakeBalanceCents: 10000,
vaultAddress: '0x5263D68786AaCfad74B9aa385A004c272548e8B7',
reserveFactor: 0,
vaultDeploymentDate: new Date('2026-04-01T00:00:00.000Z'),
openEndDate: new Date('2026-04-08T00:00:00.000Z'),
lockEndDate: new Date('2026-08-29T00:00:00.000Z'),
maturityDate: new Date('2026-09-01T00:00:00.000Z'),
settlementDate: new Date('2026-09-01T00:00:00.000Z'),
stakeLimitMantissa: new BigNumber('1000000000000'),
stakeMinMantissa: new BigNumber('100000000000'),
userMinIndividualStakeMantissa: new BigNumber('10000000'),
userRedeemLimitMantissa: new BigNumber(0),
userWithdrawLimitMantissa: new BigNumber(0),
lockingPeriodMs: 2592000 * 1000,
};

export const lockedDeposits: LockedDeposit[] = [
{
amountMantissa: new BigNumber('1000000000000000000'),
Expand Down
42 changes: 42 additions & 0 deletions apps/evm/src/clients/api/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1073,3 +1073,45 @@ export const useGetFixedRatedVaults = vi.fn(() => ({
}));

export const getFixedRatedVaults = vi.fn(async () => fixedRatedVaults);

export const useGetFixedRatedVaultUserStakedTokens = vi.fn(() => ({
data: [],
isLoading: false,
}));

export const getFixedRatedVaultUserStakedTokens = vi.fn(async () => []);

export const useGetInstitutionalVaultUserMetrics = vi.fn(() => ({
data: [],
isLoading: false,
}));

export const getInstitutionalVaultUserMetrics = vi.fn(async () => []);

export const useGetInstitutionalVaultUserData = vi.fn(() => ({
data: [],
isLoading: false,
}));

export const getInstitutionalVaultUserData = vi.fn(async () => []);

export const useStakeIntoInstitutionalVault = vi.fn((options?: MutationObserverOptions) =>
useMutation({
mutationFn: vi.fn(),
...options,
}),
);

export const useRedeemFromInstitutionalVault = vi.fn((options?: MutationObserverOptions) =>
useMutation({
mutationFn: vi.fn(),
...options,
}),
);

export const useWithdrawFromInstitutionalVault = vi.fn((options?: MutationObserverOptions) =>
useMutation({
mutationFn: vi.fn(),
...options,
}),
);
12 changes: 12 additions & 0 deletions apps/evm/src/clients/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export * from './mutations/useWithdrawTradePositionCollateral';
export * from './mutations/useRepayWithCollateral';
export * from './mutations/useStakeInPendleVault';
export * from './mutations/useWithdrawFromPendleVault';
export * from './mutations/useStakeIntoInstitutionalVault';
export * from './mutations/useRedeemFromInstitutionalVault';
export * from './mutations/useWithdrawFromInstitutionalVault';
// Queries
export * from './queries/getVaiTreasuryPercentage';
export * from './queries/getVaiTreasuryPercentage/useGetVaiTreasuryPercentage';
Expand Down Expand Up @@ -259,6 +262,15 @@ export * from './queries/getProposalCount/useGetProposalCount';
export * from './queries/getFixedRatedVaults';
export * from './queries/getFixedRatedVaults/useGetFixedRatedVaults';

export * from './queries/getInstitutionalVaultUserData';
export * from './queries/getInstitutionalVaultUserData/useGetInstitutionalVaultUserData';

export * from './queries/getFixedRatedVaultUserStakedTokens';
export * from './queries/getFixedRatedVaultUserStakedTokens/useGetFixedRatedVaultUserStakedTokens';

export * from './queries/getInstitutionalVaultUserMetrics';
export * from './queries/getInstitutionalVaultUserMetrics/useGetInstitutionalVaultUserMetrics';

export * from './queries/getPendleSwapQuote';
export * from './queries/getPendleSwapQuote/useGetPendleSwapQuote';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { queryClient } from 'clients/api/queryClient';
import FunctionKey from 'constants/functionKey';
import type { Mock } from 'vitest';

import { invalidateInstitutionalVaultQueries } from '..';

describe('clients/api/mutations/invalidateInstitutionalVaultQueries', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('invalidates all institutional vault queries including token balances', () => {
invalidateInstitutionalVaultQueries();

expect(queryClient.invalidateQueries).toHaveBeenCalledTimes(4);
expect((queryClient.invalidateQueries as Mock).mock.calls).toEqual([
[{ queryKey: [FunctionKey.GET_BALANCE_OF] }],
[{ queryKey: [FunctionKey.GET_FIXED_RATED_VAULTS] }],
[{ queryKey: [FunctionKey.GET_TOKEN_BALANCES] }],
[{ queryKey: [FunctionKey.GET_INSTITUTIONAL_VAULT_USER_DATA] }],
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { queryClient } from 'clients/api/queryClient';
import FunctionKey from 'constants/functionKey';

export const invalidateInstitutionalVaultQueries = () => {
queryClient.invalidateQueries({ queryKey: [FunctionKey.GET_BALANCE_OF] });
queryClient.invalidateQueries({ queryKey: [FunctionKey.GET_FIXED_RATED_VAULTS] });
queryClient.invalidateQueries({
queryKey: [FunctionKey.GET_TOKEN_BALANCES],
});
queryClient.invalidateQueries({
queryKey: [FunctionKey.GET_INSTITUTIONAL_VAULT_USER_DATA],
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type BigNumber from 'bignumber.js';
import { type UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction';
import { useAnalytics } from 'libs/analytics';
import { institutionalVaultAbi } from 'libs/contracts/abis/institutionalVaultAbi';
import { VError } from 'libs/errors';
import { useAccountAddress } from 'libs/wallet';
import type { Address } from 'viem';
import { invalidateInstitutionalVaultQueries } from '../invalidateInstitutionalVaultQueries';

type RedeemFromInstitutionalVaultInput = {
amountMantissa: BigNumber;
};

type Options = UseSendTransactionOptions<RedeemFromInstitutionalVaultInput>;

export const useRedeemFromInstitutionalVault = (
{ vaultAddress }: { vaultAddress: Address },
options?: Partial<Options>,
) => {
const { accountAddress } = useAccountAddress();
const { captureAnalyticEvent } = useAnalytics();

return useSendTransaction({
fn: ({ amountMantissa }: RedeemFromInstitutionalVaultInput) => {
if (!accountAddress) {
throw new VError({
type: 'unexpected',
code: 'somethingWentWrong',
});
}

return {
abi: institutionalVaultAbi,
address: vaultAddress,
functionName: 'redeem' as const,
args: [BigInt(amountMantissa.toFixed()), accountAddress, accountAddress] as const,
};
},
onConfirmed: () => {
captureAnalyticEvent('Institutional vault redeem', {
vaultAddress,
accountAddress,
});

invalidateInstitutionalVaultQueries();
},
options,
});
};
Loading
Loading