Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/chilly-pugs-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@venusprotocol/evm": minor
---

add Yield+ page structure
6 changes: 6 additions & 0 deletions .changeset/fast-radios-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@venusprotocol/ui": minor
"@venusprotocol/evm": minor
---

add Open Yield+ position form
1 change: 1 addition & 0 deletions apps/evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"i18next": "^25.0.0",
"i18next-browser-languagedetector": "^8.2.0",
"immer": "^10.1.1",
"klinecharts": "^10.0.0-beta1",
"lodash": "^4.17.21",
"lodash.merge": "^4.6.2",
"motion": "^12.0.3",
Expand Down
13 changes: 13 additions & 0 deletions apps/evm/src/App/Routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ const Bridge = safeLazyLoad(() => import('pages/Bridge'));
const Skills = safeLazyLoad(() => import('pages/Skills'));
const PrivacyPolicy = safeLazyLoad(() => import('pages/PrivacyPolicy'));
const TermsOfUse = safeLazyLoad(() => import('pages/TermsOfUse'));
const YieldPlus = safeLazyLoad(() => import('pages/YieldPlus'));

const AppRoutes = () => {
const location = useLocation();
const swapRouteEnabled = useIsFeatureEnabled({ name: 'swapRoute' });
const vaiRouteEnabled = useIsFeatureEnabled({ name: 'vaiRoute' });
const bridgeEnabled = useIsFeatureEnabled({ name: 'bridgeRoute' });
const yieldPlusRouteEnabled = useIsFeatureEnabled({ name: 'yieldPlus' });
const primeCalculatorEnabled = useIsFeatureEnabled({
name: 'primeCalculator',
});
Expand Down Expand Up @@ -176,6 +178,17 @@ const AppRoutes = () => {
}
/>

{yieldPlusRouteEnabled && (
<Route
path={Subdirectory.YIELD_PLUS}
element={
<PageSuspense>
<YieldPlus />
</PageSuspense>
}
/>
)}

{swapRouteEnabled && (
<Route
path={Subdirectory.SWAP}
Expand Down
16 changes: 0 additions & 16 deletions apps/evm/src/__mocks__/models/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,6 @@ export const transactions = {
token: usdt,
txType: 'exit_market',
},
{
accountAddress: '0x3d759121234cd36F8124C21aFe1c6852d2bEd848',
amountCents: new BigNumber('0'),
amountTokens: new BigNumber(
'115792089237316195423570985008687907853269984665640564039457584007913129639935',
),
blockNumber: '41114008',
blockTimestamp: new Date('2024-08-02T02:18:00.000Z'),
chainId: 97,
contractAddress: '0xb7526572FFE56AB9D7489838Bf2E18e3323b441A',
hash: '0xb0435b135762a7ca2ad7dccb9aa6c7f50237c6139b16a76348d6c64dfece110e',
poolName: 'Metaverse',
vTokenSymbol: vUsdtCorePool.symbol,
token: vUsdtCorePool,
txType: 'approve',
},
],
count: 7,
};
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 @@ -53,6 +53,30 @@ export const useGetVaiTreasuryPercentage = vi.fn(() =>
}),
);

export const getProportionalCloseTolerancePercentage = vi.fn(() => ({
proportionalCloseTolerancePercentage: 2,
}));
export const useGetProportionalCloseTolerancePercentage = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_PROPORTIONAL_CLOSE_TOLERANCE_PERCENTAGE],
queryFn: getProportionalCloseTolerancePercentage,
}),
);

export const getDsaVTokens = vi.fn(async () => ({
dsaVTokenAddresses: [
poolData[0].assets[0].vToken.address,
poolData[0].assets[1].vToken.address,
poolData[0].assets[2].vToken.address,
],
}));
export const useGetDsaVTokens = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_DSA_V_TOKENS],
queryFn: getDsaVTokens,
}),
);

export const getMarketHistory = vi.fn();
export const useGetMarketHistory = vi.fn(() =>
useQuery({
Expand Down Expand Up @@ -665,6 +689,16 @@ export const useGetProposalCount = vi.fn(() =>
}),
);

export const getYieldPlusPositions = vi.fn(async () => ({
positions: [], // TODO: add mock positions
}));
export const useGetYieldPlusPositions = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_YIELD_PLUS_POSITIONS],
queryFn: getYieldPlusPositions,
}),
);

// Mutations
export const useApproveToken = vi.fn((_variables: never, options?: MutationObserverOptions) =>
useMutation({
Expand Down Expand Up @@ -737,6 +771,14 @@ export const useOpenLeveragedPosition = vi.fn(
}),
);

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

export const useRepayWithCollateral = vi.fn(
(_variables: never, options?: MutationObserverOptions) =>
useMutation({
Expand Down
8 changes: 8 additions & 0 deletions apps/evm/src/clients/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ export * from './mutations/useWithdraw';
export * from './mutations/useImportSupplyPosition';
export * from './mutations/useSetEModeGroup';
export * from './mutations/useOpenLeveragedPosition';
export * from './mutations/useOpenYieldPlusPosition';
export * from './mutations/useRepayWithCollateral';

// Queries
export * from './queries/getVaiTreasuryPercentage';
export * from './queries/getVaiTreasuryPercentage/useGetVaiTreasuryPercentage';
export * from './queries/getProportionalCloseTolerancePercentage';
export * from './queries/getProportionalCloseTolerancePercentage/useGetProportionalCloseTolerancePercentage';
export * from './queries/getDsaVTokens';
export * from './queries/getDsaVTokens/useGetDsaVTokens';

export * from './queries/getVTokenBalance';
export * from './queries/getVTokenBalance/useGetVTokenBalance';
Expand Down Expand Up @@ -240,3 +245,6 @@ export * from './queries/getSwapQuote/useGetSwapQuote';

export * from './queries/getProposalCount';
export * from './queries/getProposalCount/useGetProposalCount';

export * from './queries/getYieldPlusPositions';
export * from './queries/getYieldPlusPositions/useGetYieldPlusPositions';
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`useOpenYieldPlusPosition > calls useSendTransaction with correct parameters 1`] = `
{
"abi": Any<Array>,
"address": "0xfakeRelativePositionManagerContractAddress",
"args": [
"0x170d3b2da05cc2124334240fB34ad1359e34C562",
"0xD5C4C2e2facBEB59D0216D0595d63FcDc6F9A1a7",
1,
100000000n,
2000000000000000000000000000000000000n,
50000000n,
49000000n,
"0x",
],
"functionName": "activateAndOpenPosition",
}
`;

exports[`useOpenYieldPlusPosition > calls useSendTransaction with correct parameters 2`] = `
[
[
{
"queryKey": [
"GET_YIELD_PLUS_POSITIONS",
],
},
],
]
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { exactInSwapQuote as fakeSwapQuote } from '__mocks__/models/swap';
import { vLisUSD, vUsdc } from '__mocks__/models/vTokens';
import { queryClient } from 'clients/api';
import { useGetContractAddress } from 'hooks/useGetContractAddress';
import { useSendTransaction } from 'hooks/useSendTransaction';
import { renderHook } from 'testUtils/render';
import type { Mock } from 'vitest';
import { useOpenYieldPlusPosition } from '..';

vi.mock('libs/contracts');

describe('useOpenYieldPlusPosition', () => {
const fakeInput = {
longVTokenAddress: vLisUSD.address,
shortVTokenAddress: vUsdc.address,
dsaIndex: 1,
initialPrincipalMantissa: 100000000n,
leverageFactor: 2000000000000000000,
shortAmountMantissa: 50000000n,
minLongAmountMantissa: 49000000n,
swapQuote: fakeSwapQuote,
} as const;

it('calls useSendTransaction with correct parameters', async () => {
renderHook(() => useOpenYieldPlusPosition());

expect(useSendTransaction).toHaveBeenCalledWith({
fn: expect.any(Function),
onConfirmed: expect.any(Function),
options: undefined,
});

const { fn } = (useSendTransaction as Mock).mock.calls[0][0];

expect(await fn(fakeInput)).toMatchSnapshot({
abi: expect.any(Array),
});

const { onConfirmed } = (useSendTransaction as Mock).mock.calls[0][0];
await onConfirmed();

expect((queryClient.invalidateQueries as Mock).mock.calls).toMatchSnapshot();
});

it('throws error when RelativePositionManager contract address is not found', async () => {
(useGetContractAddress as Mock).mockImplementation(() => ({ address: undefined }));

renderHook(() => useOpenYieldPlusPosition());

const { fn } = (useSendTransaction as Mock).mock.calls[0][0];

await expect(async () => fn(fakeInput)).rejects.toThrow('somethingWentWrong');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import BigNumber from 'bignumber.js';
import type { Address } from 'viem';

import { queryClient } from 'clients/api';
import { COMPOUND_MANTISSA } from 'constants/compoundMantissa';
import FunctionKey from 'constants/functionKey';
import { useGetContractAddress } from 'hooks/useGetContractAddress';
import { type UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction';
import { relativePositionManagerAbi } from 'libs/contracts';
import { VError } from 'libs/errors';
import type { SwapQuote } from 'types';

export type OpenYieldPlusPositionInput = {
longVTokenAddress: Address;
shortVTokenAddress: Address;
dsaIndex: number;
initialPrincipalMantissa: bigint;
leverageFactor: number;
shortAmountMantissa: bigint;
minLongAmountMantissa: bigint;
swapQuote: SwapQuote;
};

type Options = UseSendTransactionOptions<OpenYieldPlusPositionInput>;

export const useOpenYieldPlusPosition = (options?: Partial<Options>) => {
const { address: relativePositionManagerContractAddress } = useGetContractAddress({
name: 'RelativePositionManager',
});

return useSendTransaction({
fn: ({
longVTokenAddress,
shortVTokenAddress,
dsaIndex,
initialPrincipalMantissa,
leverageFactor,
shortAmountMantissa,
minLongAmountMantissa,
swapQuote,
}: OpenYieldPlusPositionInput) => {
if (!relativePositionManagerContractAddress) {
throw new VError({
type: 'unexpected',
code: 'somethingWentWrong',
});
}

return {
abi: relativePositionManagerAbi,
address: relativePositionManagerContractAddress,
functionName: 'activateAndOpenPosition',
args: [
longVTokenAddress,
shortVTokenAddress,
dsaIndex,
initialPrincipalMantissa,
BigInt(new BigNumber(leverageFactor).multipliedBy(COMPOUND_MANTISSA).toFixed(0)),
shortAmountMantissa,
minLongAmountMantissa,
swapQuote.callData,
],
} as const;
},
onConfirmed: () => {
queryClient.invalidateQueries({
queryKey: [FunctionKey.GET_YIELD_PLUS_POSITIONS],
});
},
options,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,31 @@ const fakeInput = {
accountAddress: fakeAddress,
chainId: ChainId.BSC_TESTNET,
getPoolsData: { pools: poolData },
contractAddress: 'all',
contractAddress: undefined,
page: 1,
};

const fakeYieldPlusFields = {
yieldPlusPositionAccountAddress: null,
yieldPlusLongVTokenAddress: null,
yieldPlusShortVTokenAddress: null,
yieldPlusDsaVTokenAddress: null,
yieldPlusCycleId: null,
yieldPlusEffectiveLeverageRatio: null,
yieldPlusInitialPrincipalMantissa: null,
yieldPlusPrincipalAmountMantissa: null,
yieldPlusNewTotalPrincipalMantissa: null,
yieldPlusRemainingPrincipalMantissa: null,
yieldPlusShortAmountMantissa: null,
yieldPlusAdditionalPrincipalMantissa: null,
yieldPlusCloseFractionBps: null,
yieldPlusAmountRepaidMantissa: null,
yieldPlusAmountRedeemedMantissa: null,
yieldPlusAmountRedeemedDsaMantissa: null,
yieldPlusLongDustRedeemedMantissa: null,
yieldPlusAmountConvertedToProfitMantissa: null,
};

describe('getAccountTransactionHistory', () => {
it('returns transactions formatted', async () => {
(restService as Mock).mockImplementation(() => {
Expand All @@ -34,6 +55,7 @@ describe('getAccountTransactionHistory', () => {
chainId: ChainId.BSC_TESTNET,
underlyingAddress: '0xa11c8d9dc9b66e209ef60f0c8d969d3Cd988782c',
underlyingTokenPriceMantissa: '1000130000000000000',
...fakeYieldPlusFields,
},
{
id: 'b0435b135762a7ca2ad7dccb9aa6c7f50237c6139b16a76348d6c64dfece110e-119-56',
Expand All @@ -49,6 +71,7 @@ describe('getAccountTransactionHistory', () => {
chainId: ChainId.BSC_TESTNET,
underlyingAddress: '0xa11c8d9dc9b66e209ef60f0c8d969d3Cd988782c',
underlyingTokenPriceMantissa: '1000391740000000000',
...fakeYieldPlusFields,
},
];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { VError } from 'libs/errors';
import type { VToken } from 'types';
import { restService } from 'utilities';
import { type Address, isAddress } from 'viem';
import type { Address } from 'viem';
import { formatApiTransaction } from './formatApiTransaction';
import type {
AccountTransactionHistoryApiResponse,
Expand All @@ -15,6 +15,7 @@ export const getAccountTransactionHistory = async ({
chainId,
accountAddress,
contractAddress,
positionAccountAddress,
getPoolsData,
type,
page,
Expand All @@ -25,7 +26,8 @@ export const getAccountTransactionHistory = async ({
params: {
chainId,
type,
contractAddress: isAddress(contractAddress) ? contractAddress : undefined,
contractAddress,
positionAccountAddress,
page,
},
});
Expand Down
Loading
Loading