Skip to content

Commit

Permalink
feat: use api (#168)
Browse files Browse the repository at this point in the history
* feat: modify points, por, total supply to use api

* feat: remove unit test github action since there is no unit test currently
  • Loading branch information
Polybius93 authored Sep 9, 2024
1 parent 669f8ce commit a252b43
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 391 deletions.
9 changes: 0 additions & 9 deletions .github/workflows/code-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,3 @@ jobs:

- name: Typecheck
run: yarn typecheck

test-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/provision

- name: Test
run: yarn test:unit
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { HStack, Image, Text } from '@chakra-ui/react';
import { CustomSkeleton } from '@components/custom-skeleton/custom-skeleton';
import { ProtocolRewards } from '@models/ethereum-models';
import { ProtocolRewards } from '@models/points.models';
import { unshiftValue } from 'dlc-btc-lib/utilities';

import { formatNumber } from '@shared/utils';

export function PointsTableItem(pointsTableItem: ProtocolRewards): React.JSX.Element {
if (!pointsTableItem) return <CustomSkeleton height={'35px'} />;

const { name, points, currentDLCBTC, multiplier } = pointsTableItem;
const { name, points, currentTokens, multiplier } = pointsTableItem;

return (
<HStack
Expand All @@ -25,7 +25,7 @@ export function PointsTableItem(pointsTableItem: ProtocolRewards): React.JSX.Ele
<HStack w={'25%'}>
<Image src={'/images/logos/dlc-btc-logo.svg'} alt={'dlc BTC logo'} boxSize={'25px'} />
<Text color={'white'} fontSize={'sm'} fontWeight={800}>
{unshiftValue(currentDLCBTC)}
{unshiftValue(currentTokens)}
</Text>
</HStack>
<HStack w={'50%'}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { useNavigate, useParams } from 'react-router-dom';

import { Divider, HStack, Icon, Image, Link, Text } from '@chakra-ui/react';
import { fetchMintBurnEvents } from '@functions/ethereum.functions';
import { DetailedEvent } from '@models/ethereum-models';
import { Merchant } from '@models/merchant';
import { bitcoin, dlcBTC } from '@models/token';
import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider';
import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider';
import { useQuery } from '@tanstack/react-query';
import { DetailedEvent } from 'dlc-btc-lib/models';
import { isEmpty } from 'ramda';

import { MerchantDetailsTableItemProps } from '../merchant-table/components/merchant-details-table-item';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';

import { Button, HStack, Image, Skeleton, Text } from '@chakra-ui/react';
import { Merchant } from '@models/merchant';
import { unshiftValue } from 'dlc-btc-lib/utilities';

interface MerchantTableItemProps {
merchant: Merchant;
Expand Down Expand Up @@ -33,7 +34,7 @@ export function MerchantTableItem({
<Image src={'/images/logos/dlc-btc-logo.svg'} alt={'dlcBTC Logo'} boxSize={'25px'} />
<Skeleton isLoaded={dlcBTCAmount !== undefined} h={'auto'} w={'150px'}>
<Text color={'white'} fontSize={'2xl'} fontWeight={800} h={'35px'}>
{Number(dlcBTCAmount?.toFixed(4))}
{dlcBTCAmount && unshiftValue(dlcBTCAmount)}
</Text>
</Skeleton>
</HStack>
Expand Down
6 changes: 0 additions & 6 deletions src/app/functions/configuration.functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ export function getWagmiConfiguration(ethereumNetworkIDs: EthereumNetworkID[]):
});
}

export const isEnabledEthereumNetwork = (chain: Chain): boolean => {
return appConfiguration.enabledEthereumNetworkIDs.includes(
chain.id.toString() as EthereumNetworkID
);
};

function clientToSigner(client: Client<Transport, Chain, Account>): providers.JsonRpcSigner {
const { account, chain, transport } = client;

Expand Down
2 changes: 1 addition & 1 deletion src/app/functions/ethereum.functions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DetailedEvent } from 'dlc-btc-lib/models';
import { DetailedEvent } from '@models/ethereum-models';
import { Contract } from 'ethers';

export async function fetchMintBurnEvents(
Expand Down
225 changes: 23 additions & 202 deletions src/app/hooks/use-points.ts
Original file line number Diff line number Diff line change
@@ -1,219 +1,40 @@
import { useContext, useEffect, useState } from 'react';

import {
getEthereumNetworkDeploymentPlans,
isEnabledEthereumNetwork,
} from '@functions/configuration.functions';
import {
DetailedEvent,
PointsData,
ProtocolRewards,
TimeStampedEvent,
} from '@models/ethereum-models';
import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider';
import Decimal from 'decimal.js';
import { Chain } from 'viem';
import { PointsData } from '@models/points.models';
import { useQuery } from '@tanstack/react-query';
import { useAccount } from 'wagmi';

import { POINTS_API_URL } from '@shared/constants/api.constants';

interface UsePointsReturnType {
userPoints: PointsData | undefined;
}

// The reason why this same function works for the Curve Gauge as well as dlcBTC:
// The gauge is minting gaugeTokens to the user on Deposit, that can be transferred.
// So to track the user's TVL, we can track the user's gaugeToken balance.
export function calculateRollingTVL(
events: DetailedEvent[],
userAddress: string
): TimeStampedEvent[] {
return events.reduce<TimeStampedEvent[]>((rollingTVL, { from, value, timestamp }) => {
const currentTotalValueLocked =
rollingTVL.length > 0 ? rollingTVL[rollingTVL.length - 1].totalValueLocked : 0;
const amount = from === userAddress ? -value : value;

rollingTVL.push({
timestamp,
amount,
totalValueLocked: currentTotalValueLocked + amount,
});

return rollingTVL;
}, []);
}

export function calculateRewardBetweenEvents(
previousEvent: TimeStampedEvent,
currentEvent: TimeStampedEvent,
rewardsRate: number
): number {
const rewardsPerTime = new Decimal(previousEvent.totalValueLocked).times(rewardsRate);
const rewards = rewardsPerTime.times(
new Decimal(currentEvent.timestamp).minus(previousEvent.timestamp)
);
return rewards.toNumber();
}

export function calculatePoints(
userTimeStampedEvents: TimeStampedEvent[],
rewardsRate: number
): number {
if (userTimeStampedEvents.length === 0) {
return 0;
}
export function usePoints(): UsePointsReturnType {
const { address } = useAccount();

const currentTimestamp = Math.floor(Date.now() / 1000);
const totalRewards = userTimeStampedEvents.reduce((acc, currentEvent, index, array) => {
if (array.length === 1) {
return calculateRewardBetweenEvents(
currentEvent,
{
timestamp: currentTimestamp,
totalValueLocked: currentEvent.totalValueLocked,
amount: 0,
},
rewardsRate
);
}
if (index === 0) {
return acc;
}
const previousEvent = array[index - 1];
acc += calculateRewardBetweenEvents(previousEvent, currentEvent, rewardsRate);
if (index === array.length - 1) {
acc += calculateRewardBetweenEvents(
currentEvent,
{
timestamp: currentTimestamp,
totalValueLocked: currentEvent.totalValueLocked,
amount: 0,
},
rewardsRate
async function fetchUserPoints(): Promise<PointsData | undefined> {
try {
const response = await fetch(
`${POINTS_API_URL}/${appConfiguration.appEnvironment}/${address}`
);
}
return acc;
}, 0);

return totalRewards;
}
if (!response.ok) {
throw new Error(`Error fetching user: ${address} points`);
}

async function fetchTransfersForUser(
ethereumNetwork: Chain,
providerURL: string,
userAddress: string,
contractAddress?: string
): Promise<DetailedEvent[]> {
if (!contractAddress) {
const dlcBTCContract = getEthereumNetworkDeploymentPlans(ethereumNetwork).find(
plan => plan.contract.name === 'DLCBTC'
);
const responseData = await response.json();

if (!dlcBTCContract) {
throw new Error('DLCBTC Contract not found in Deployment Plans');
return responseData.points;
} catch (error) {
console.error(`Error fetching user: ${address} points`, error);

Check warning on line 28 in src/app/hooks/use-points.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

Unexpected console statement
return undefined;
}
contractAddress = dlcBTCContract.contract.address;
}

const req = await fetch(
`/.netlify/functions/fetch-transfer-events?providerURL=${providerURL}&contractAddress=${contractAddress}&userAddress=${userAddress}`
);

if (!req.ok) {
// throw new Error(`HTTP error! status: ${req.status}`);
return [];
}

const events = await req.json();
const detailedEvents: DetailedEvent[] = events.detailedEvents;

return detailedEvents;
}

export function usePoints(): UsePointsReturnType {
const { address, chain } = useAccount();

const [userPoints, setUserPoints] = useState<PointsData | undefined>(undefined);
const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext);

const protocolRewardDefinitions = [
{
description: 'Holding dlcBTC in user wallet',
name: 'dlcBTC',
multiplier: 1,
getRollingTVL: async (
userAddress: string,
ethereumNetwork: Chain
): Promise<TimeStampedEvent[]> => {
const events = await fetchTransfersForUser(
ethereumNetwork,
ethereumNetworkConfiguration.httpURL,
userAddress
);
events.sort((a, b) => a.timestamp - b.timestamp);
const rollingTVL = calculateRollingTVL(events, userAddress);
return rollingTVL;
},
},
{
description: 'Staking LP tokens in DLCBTC/WBTC gauge',
name: 'Curve',
multiplier: 5,
getRollingTVL: async (
userAddress: string,
ethereumNetwork: Chain
): Promise<TimeStampedEvent[]> => {
const gaugeAddress = appConfiguration.protocols.find(p => p.name === 'Curve')?.gaugeAddress;
if (!gaugeAddress) {
return [];
}
const events = await fetchTransfersForUser(
ethereumNetwork,
ethereumNetworkConfiguration.httpURL,
userAddress,
gaugeAddress
);
events.sort((a, b) => a.timestamp - b.timestamp);
const rollingTVL = calculateRollingTVL(events, userAddress);
return rollingTVL;
},
},
];

useEffect(() => {
const fetchUserPoints = async (currentUserAddress: string, currentEthereumNetwork: Chain) => {
void fetchPoints(currentUserAddress, currentEthereumNetwork);
};
if (address && chain && isEnabledEthereumNetwork(chain)) {
void fetchUserPoints(address, chain);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [address, chain]);

async function fetchPoints(
currentUserAddress: string,
currentEthereumNetwork: Chain
): Promise<void> {
// This is the default 1x reward rate of 10000 points/day/BTC
const rewardsRate = import.meta.env.VITE_REWARDS_RATE;
if (!rewardsRate) {
throw new Error('Rewards Rate not set');
}

let totalPoints = 0;
const protocolRewards: ProtocolRewards[] = [];
for (const protocol of protocolRewardDefinitions) {
const rollingTVL = await protocol.getRollingTVL(currentUserAddress, currentEthereumNetwork);
const points = calculatePoints(rollingTVL, rewardsRate * protocol.multiplier);
totalPoints += points;
protocolRewards.push({
name: protocol.name,
points: points,
currentDLCBTC: rollingTVL.length ? rollingTVL[rollingTVL.length - 1].totalValueLocked : 0,
multiplier: protocol.multiplier,
});
}

setUserPoints({ total: totalPoints, protocols: protocolRewards });
}
const { data: userPoints } = useQuery({
queryKey: ['userPoints', address],
queryFn: fetchUserPoints,
enabled: !!address,
});

return {
userPoints,
Expand Down
Loading

0 comments on commit a252b43

Please sign in to comment.