Skip to content
Merged
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/forty-spies-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@venusprotocol/evm": minor
---

add support for Liquidity Hub transactions
18 changes: 18 additions & 0 deletions apps/evm/src/__mocks__/models/transactions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
import BigNumber from 'bignumber.js';
import { usdt } from './tokens';
import { vUsdtCorePool } from './vTokens';
import { vhXvs } from './vhTokens';

export const transactions = {
transactions: [
{
accountAddress: '0x3d759121234cd36F8124C21aFe1c6852d2bEd848',
amounts: [
{
amountCents: 7150,
amountTokens: new BigNumber(10),
token: vhXvs.underlyingToken,
},
],
blockNumber: '41604852',
blockTimestamp: new Date('2024-08-24T04:17:09.000Z'),
chainId: 97,
contractAddress: vhXvs.address,
hash: '0xca13a0f45b10dabd21b863b6b602c6d8776edd4c6b10fe65a0881d491f468f35',
txType: 'supply',
vhToken: vhXvs,
},
{
accountAddress: '0x3d759121234cd36F8124C21aFe1c6852d2bEd848',
amounts: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ChainId } from '@venusprotocol/chains';
import fakeAddress from '__mocks__/models/address';
import { liquidityHubs } from '__mocks__/models/liquidityHubs';
import { poolData } from '__mocks__/models/pools';
import { restService } from 'utilities';
import type { Address } from 'viem';
import { type Mock, vi } from 'vitest';
import { type ApiAccountHistoricalTransaction, getAccountTransactionHistory } from '..';
import { convertToTxType } from '../formatApiTransaction/convertToTxType';
Expand All @@ -12,6 +14,7 @@ const fakeInput = {
accountAddress: fakeAddress,
chainId: ChainId.BSC_TESTNET,
getPoolsData: { pools: poolData },
liquidityHubs,
contractAddress: undefined,
page: 1,
};
Expand Down Expand Up @@ -190,6 +193,64 @@ describe('getAccountTransactionHistory', () => {
]);
});

it('formats Liquidity Hub supply and withdrawal transactions', async () => {
const [liquidityHub] = liquidityHubs;
const commonTransactionFields = {
accountAddress: fakeAddress as Address,
amountUnderlyingMantissa: '1000000000000000000',
amountVTokenMantissa: '1000000000000000000',
blockNumber: '41604853',
chainId: ChainId.BSC_TESTNET,
contractAddress: liquidityHub.vhToken.address,
underlyingAddress: liquidityHub.vhToken.underlyingToken.address,
underlyingTokenPriceMantissa: '7000000000000000000',
...fakeYieldPlusFields,
};

(restService as Mock).mockResolvedValue({
data: {
count: '2',
results: [
{
...commonTransactionFields,
id: 'liquidity-hub-supply',
txHash: '0x3',
txIndex: 3,
txTimestamp: new Date('2024-08-25T04:17:09.000Z'),
txType: 'mint',
},
{
...commonTransactionFields,
id: 'liquidity-hub-withdraw',
txHash: '0x4',
txIndex: 4,
txTimestamp: new Date('2024-08-26T04:17:09.000Z'),
txType: 'redeem',
},
] satisfies ApiAccountHistoricalTransaction[],
},
});

const response = await getAccountTransactionHistory(fakeInput);

expect(response.transactions).toHaveLength(2);
expect(response.transactions).toEqual([
expect.objectContaining({
txType: 'supply',
vhToken: liquidityHub.vhToken,
}),
expect.objectContaining({
txType: 'withdraw',
vhToken: liquidityHub.vhToken,
}),
]);

response.transactions.forEach(transaction => {
expect(transaction.amounts?.[0]?.amountTokens.isEqualTo(1)).toBe(true);
expect(transaction.amounts?.[0]?.amountCents).toBe(700);
});
});

it('throws on error in payload', async () => {
(restService as Mock).mockResolvedValue({ data: { error: 'Some error' } });

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { LiquidityHubTx, LiquidityHubTxType } from 'types';
import {
convertDollarsToCents,
convertMantissaToTokens,
convertPriceMantissaToDollars,
} from 'utilities';
import type { Address } from 'viem';
import type { ApiAccountHistoricalTransaction, VhTokenMapping } from '../../types';

export const formatToLiquidityHubTransaction = ({
vhTokenMapping,
apiTransaction,
txType,
}: {
vhTokenMapping: VhTokenMapping;
apiTransaction: ApiAccountHistoricalTransaction;
txType: LiquidityHubTxType;
}): LiquidityHubTx | undefined => {
const {
txHash: hash,
txTimestamp: blockTimestamp,
blockNumber,
accountAddress,
contractAddress,
chainId,
amountUnderlyingMantissa,
underlyingTokenPriceMantissa,
} = apiTransaction;

const vhToken = vhTokenMapping[contractAddress.toLowerCase() as Address];
if (!vhToken) {
return undefined;
}

const token = vhToken.underlyingToken;
const amountTokens = amountUnderlyingMantissa
? convertMantissaToTokens({
value: BigInt(amountUnderlyingMantissa),
token,
})
: undefined;

const tokenPriceDollars = underlyingTokenPriceMantissa
? convertPriceMantissaToDollars({
priceMantissa: underlyingTokenPriceMantissa,
decimals: token.decimals,
})
: undefined;

const tokenPriceCents = tokenPriceDollars ? convertDollarsToCents(tokenPriceDollars) : undefined;
const amounts =
amountTokens && tokenPriceCents
? [
{
token,
amountTokens,
amountCents: amountTokens.multipliedBy(tokenPriceCents).toNumber(),
},
]
: undefined;

return {
txType,
hash,
blockTimestamp,
blockNumber,
accountAddress,
contractAddress,
chainId,
vhToken,
amounts,
};
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { TRADE_TX_TYPES } from 'constants/marketTxTypes';
import type { MarketTxType, TradeTxType } from 'types';
import type { ApiAccountHistoricalTransaction, VTokenAssetMapping } from '../types';
import { LIQUIDITY_HUB_TX_TYPES, TRADE_TX_TYPES } from 'constants/marketTxTypes';
import type { LiquidityHubTxType, MarketTxType, TradeTxType } from 'types';
import type { ApiAccountHistoricalTransaction, VTokenAssetMapping, VhTokenMapping } from '../types';
import { convertToTxType } from './convertToTxType';
import { formatToLiquidityHubTransaction } from './formatToLiquidityHubTransaction';
import { formatToMarketTransaction } from './formatToMarketTransaction';
import { formatToTradeTransaction } from './formatToTradeTransaction';

export const formatApiTransaction = ({
vTokenAssetMapping,
vhTokenMapping,
apiTransaction,
}: {
vTokenAssetMapping: VTokenAssetMapping;
vhTokenMapping: VhTokenMapping;
apiTransaction: ApiAccountHistoricalTransaction;
}) => {
const { txType: apiTxType } = apiTransaction;
Expand All @@ -27,6 +30,18 @@ export const formatApiTransaction = ({
});
}

if (LIQUIDITY_HUB_TX_TYPES.some(t => t === txType)) {
const liquidityHubTransaction = formatToLiquidityHubTransaction({
vhTokenMapping,
apiTransaction,
txType: txType as LiquidityHubTxType,
});

if (liquidityHubTransaction) {
return liquidityHubTransaction;
}
}

return formatToMarketTransaction({
vTokenAssetMapping,
apiTransaction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
GetAccountTransactionHistoryInput,
GetAccountTransactionHistoryOutput,
VTokenAssetMapping,
VhTokenMapping,
} from './types';

export * from './types';
Expand All @@ -27,6 +28,7 @@ export const getAccountTransactionHistory = async ({
contractAddress,
positionAccountAddress,
getPoolsData,
liquidityHubs,
type,
page,
}: GetAccountTransactionHistoryInput): Promise<GetAccountTransactionHistoryOutput> => {
Expand Down Expand Up @@ -70,11 +72,18 @@ export const getAccountTransactionHistory = async ({
return acc;
}, {});

const vhTokenMapping = liquidityHubs.reduce<VhTokenMapping>((acc, liquidityHub) => {
acc[liquidityHub.vhToken.address.toLowerCase() as Address] = liquidityHub.vhToken;

return acc;
}, {});

const formattedResponse = txsResponse.data.results.reduce<
GetAccountTransactionHistoryOutput['transactions']
>((acc, apiTransaction) => {
const formattedTransaction = formatApiTransaction({
vTokenAssetMapping,
vhTokenMapping,
apiTransaction,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ChainId } from '@venusprotocol/chains';
import type { Asset, Tx, TxType } from 'types';
import type { Asset, LiquidityHub, Tx, TxType, VhToken } from 'types';
import type { Address } from 'viem';
import type { GetPoolsOutput } from '../useGetPools/types';

Expand Down Expand Up @@ -65,6 +65,7 @@ export interface GetAccountTransactionHistoryInput {
accountAddress: string;
chainId: ChainId;
getPoolsData: GetPoolsOutput | undefined;
liquidityHubs: LiquidityHub[];
positionAccountAddress?: Address;
contractAddress?: Address;
type?: TxType;
Expand All @@ -82,3 +83,5 @@ export type VTokenAssetMapping = Record<
poolName: string;
}
>;

export type VhTokenMapping = Record<Address, VhToken>;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type QueryObserverOptions, useQuery } from '@tanstack/react-query';
import { liquidityHubs } from '__mocks__/models/liquidityHubs';
Comment thread
therealemjy marked this conversation as resolved.

import FunctionKey from 'constants/functionKey';
import { useChainId } from 'libs/wallet';
Expand All @@ -12,7 +13,7 @@ import { useGetPools } from '../useGetPools';

type TrimmedGetAccountTransactionHistoryInput = Omit<
GetAccountTransactionHistoryInput,
'chainId' | 'getPoolsData'
'chainId' | 'getPoolsData' | 'liquidityHubs'
>;

type Options = QueryObserverOptions<
Expand All @@ -37,6 +38,7 @@ export const useGetAccountTransactionHistory = (
const extendedParams = {
...params,
getPoolsData,
liquidityHubs, // TODO: fetch from API
chainId,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { liquidityHubs } from '__mocks__/models/liquidityHubs';
import BigNumber from 'bignumber.js';
import { t } from 'libs/translations';
import type { LiquidityHubTx } from 'types';
import { formatToLiquidityHubTxEvents } from '..';

describe('formatToLiquidityHubTxEvents', () => {
it('formats the underlying token amount and identifies Liquidity Hub as the source', () => {
const [liquidityHub] = liquidityHubs;
const transaction: LiquidityHubTx = {
accountAddress: '0x1000000000000000000000000000000000000000',
amounts: [
{
amountCents: 7150,
amountTokens: new BigNumber(10),
token: liquidityHub.vhToken.underlyingToken,
},
],
blockNumber: '1',
blockTimestamp: new Date('2026-01-01T00:00:00.000Z'),
chainId: liquidityHub.vhToken.chainId,
contractAddress: liquidityHub.vhToken.address,
hash: '0x1',
txType: 'supply',
vhToken: liquidityHub.vhToken,
};

const [event] = formatToLiquidityHubTxEvents({
transaction,
t,
});

expect(event.token).toBe(liquidityHub.vhToken.underlyingToken);
expect(event.title).toBe('10 XVS');
expect(event.description).toBe('$71.5 • XVS • Liquidity Hub');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { TFunction } from 'i18next';
import type { LiquidityHubTx } from 'types';
import { formatCentsToReadableValue, formatTokensToReadableValue } from 'utilities';
import type { EventProps } from '../Event';

export const formatToLiquidityHubTxEvents = ({
transaction,
t,
}: {
transaction: LiquidityHubTx;
t: TFunction;
}) => {
const { amounts, vhToken } = transaction;
const primaryAmount = amounts?.[0];
const token = vhToken.underlyingToken;
const liquidityHubLabel = t('layouts.menu.markets.liquidityHub.label');

const title = formatTokensToReadableValue({
token,
value: primaryAmount?.amountTokens,
});
const description = primaryAmount
? `${formatCentsToReadableValue({
value: primaryAmount.amountCents,
})} • ${token.symbol} • ${liquidityHubLabel}`
: liquidityHubLabel;

const event: EventProps = {
token,
title,
description,
};

return [event];
};
Loading
Loading