From 7c0a28d994523a199182f4d160015f2789f974a8 Mon Sep 17 00:00:00 2001 From: Polybius93 <99192647+Polybius93@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:09:04 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20remove=20mint=20burn=20events=20related?= =?UTF-8?q?=20functions,=20replace=20them=20with=20ap=E2=80=A6=20(#170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: remove mint burn events related functions, replace them with api calls --- .../components/generic-table-header-text.tsx | 4 +- .../components/generic-table-header.tsx | 6 +- .../components/points-table-item.tsx | 8 +- src/app/components/points/points.tsx | 2 +- .../merchant-details/merchant-details.tsx | 59 +----------- .../merchant-details-table-item.tsx | 92 +++++++++---------- .../merchant-table/merchant-details-table.tsx | 9 +- .../proof-of-reserve/proof-of-reserve.tsx | 34 +------ .../protocol-history-table-item.tsx | 59 ++++++++---- .../protocol-history-table.tsx | 4 +- src/app/functions/ethereum.functions.ts | 24 ----- src/app/hooks/use-mint-burn-events.ts | 77 ++++++++++++++++ .../proof-of-reserve-context-provider.tsx | 9 ++ src/shared/constants/api.constants.ts | 1 + src/shared/constants/ethereum.constants.ts | 1 - src/shared/models/ethereum-models.ts | 12 +++ src/shared/models/points.models.ts | 1 + src/shared/utils.ts | 34 +++++++ 18 files changed, 241 insertions(+), 195 deletions(-) delete mode 100644 src/app/functions/ethereum.functions.ts create mode 100644 src/app/hooks/use-mint-burn-events.ts diff --git a/src/app/components/generic-table/components/generic-table-header-text.tsx b/src/app/components/generic-table/components/generic-table-header-text.tsx index d9348426..ec87b8d4 100644 --- a/src/app/components/generic-table/components/generic-table-header-text.tsx +++ b/src/app/components/generic-table/components/generic-table-header-text.tsx @@ -8,7 +8,6 @@ interface GenericTableHeaderTextProps { fontSize?: string; fontWeight?: string; children: React.ReactNode; - pl?: string; } export function GenericTableHeaderText({ @@ -17,10 +16,9 @@ export function GenericTableHeaderText({ fontSize = 'small', children, fontWeight = '600', - pl = '10px', }: GenericTableHeaderTextProps): React.JSX.Element { return ( - + {children} ); diff --git a/src/app/components/generic-table/components/generic-table-header.tsx b/src/app/components/generic-table/components/generic-table-header.tsx index e2cf3122..1ec144e7 100644 --- a/src/app/components/generic-table/components/generic-table-header.tsx +++ b/src/app/components/generic-table/components/generic-table-header.tsx @@ -5,5 +5,9 @@ interface GenericTableHeaderProps { } export function GenericTableHeader({ children }: GenericTableHeaderProps): React.JSX.Element { - return {children}; + return ( + + {children} + + ); } diff --git a/src/app/components/points/components/points-table/components/points-table-item.tsx b/src/app/components/points/components/points-table/components/points-table-item.tsx index 2c6996ee..e544e380 100644 --- a/src/app/components/points/components/points-table/components/points-table-item.tsx +++ b/src/app/components/points/components/points-table/components/points-table-item.tsx @@ -41,9 +41,11 @@ export function PointsTableItem(pointsTableItem: ProtocolRewards): React.JSX.Ele {`(${multiplier}x)`} - - {name} - + + + {name} + + ); } diff --git a/src/app/components/points/points.tsx b/src/app/components/points/points.tsx index 39016a59..c56e37db 100644 --- a/src/app/components/points/points.tsx +++ b/src/app/components/points/points.tsx @@ -55,7 +55,7 @@ export function Points(): React.JSX.Element { p.name == 'Curve')?.points} + totalSupply={userPoints?.useTotal} tokenSuffix={'Use'} /> { - return { - merchant, - dlcBTCAmount: undefined, - }; - }), - ]; - const selectedMerchant = merchantProofOfReserves.find(item => item.merchant.name === name); - - const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); - - const { data: mintBurnEvents } = useQuery({ - queryKey: [`mintBurnEvents${name}`, ethereumNetworkConfiguration.dlcBTCContract.address], - queryFn: fetchMintBurnEventsHandler, - }); + const selectedMerchant = proofOfReserve?.[1].find(item => item.merchant.name === name); + const mintBurnEvents = merchantMintBurnEvents?.find(item => item.name === name)?.mintBurnEvents; if (!name) return Error: No merchant name provided; - async function fetchMintBurnEventsHandler(): Promise { - if (!selectedMerchant || isEmpty(selectedMerchant?.merchant.addresses)) return []; - const detailedEvents: DetailedEvent[] = ( - await Promise.all( - selectedMerchant.merchant.addresses.map(async address => { - return await fetchMintBurnEvents( - ethereumNetworkConfiguration.dlcBTCContract, - ethereumNetworkConfiguration.httpURL, - address - ); - }) - ) - ).flat(); - - return detailedEvents.map((event, index) => { - return { - id: index, - orderBook: selectedMerchant.merchant.addresses - .map(address => address.toLowerCase()) - .includes(event.from.toLowerCase()) - ? 'REDEEM' - : 'MINT', - amount: event.value, - inUSD: 'TODO', //TODO: calculate usd value at the time of mint - txHash: event.txHash, - date: new Date(event.timestamp * 1000).toDateString(), - }; - }); - } - return ( ; - const { orderBook, amount, txHash, date } = merchantFocusTableItem; - - const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); + const { + dlcBTCAmount, + txHash, + date, + isMint, + chain: eventChain, + } = formatEvent(merchantFocusTableItem); - const renderAmount = () => { - const unshiftedValue = unshiftValue(amount); - return orderBook === 'REDEEM' ? -unshiftedValue : unshiftedValue; - }; + const ethereumNetwork = findEthereumNetworkByName(eventChain); return ( - - {orderBook} - - + + + {isMint ? 'MINT' : 'REDEEM'} + + + {'dlc - {renderAmount()} + {unshiftValue(dlcBTCAmount)} {/* add back the USD calculation later and adjus the width accordingly */} {/* {inUSD} */} - - window.open( - `${ethereumNetworkConfiguration.ethereumExplorerAPIURL}/tx/${txHash}`, - '_blank' - ) - } - cursor={'pointer'} - textDecoration={'underline'} - > - {truncateAddress(txHash)} - - - {date} - + + + window.open(`${ethereumNetwork.blockExplorers?.default.url}/tx/${txHash}`, '_blank') + } + cursor={'pointer'} + textDecoration={'underline'} + > + {truncateAddress(txHash)} + + + + + {ethereumNetwork.name} + + + + + {date} + + ); } diff --git a/src/app/components/proof-of-reserve/components/merchant-table/merchant-details-table.tsx b/src/app/components/proof-of-reserve/components/merchant-table/merchant-details-table.tsx index 1ab78b69..0609d692 100644 --- a/src/app/components/proof-of-reserve/components/merchant-table/merchant-details-table.tsx +++ b/src/app/components/proof-of-reserve/components/merchant-table/merchant-details-table.tsx @@ -16,11 +16,12 @@ export function MerchantDetailsTable({ items }: MerchantDetailsTableProps): Reac return ( - Order Book - Amount + Order Book + Amount {/* in USD */} - Transaction - Date + Transaction + Chain + Date {items?.length === 0 && ( diff --git a/src/app/components/proof-of-reserve/proof-of-reserve.tsx b/src/app/components/proof-of-reserve/proof-of-reserve.tsx index 5ef1f002..2abe1358 100644 --- a/src/app/components/proof-of-reserve/proof-of-reserve.tsx +++ b/src/app/components/proof-of-reserve/proof-of-reserve.tsx @@ -1,16 +1,10 @@ import { useContext } from 'react'; import { Divider, HStack, Text } from '@chakra-ui/react'; -import { ProtocolHistoryTableItemProps } from '@components/protocol-history-table/components/protocol-history-table-item'; import { ProtocolHistoryTable } from '@components/protocol-history-table/protocol-history-table'; -import { fetchMintBurnEvents } from '@functions/ethereum.functions'; 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 { BURN_ADDRESS } from '@shared/constants/ethereum.constants'; import { MerchantTableHeader } from './components/merchant-table/components/merchant-table-header'; import { MerchantTableItem } from './components/merchant-table/components/merchant-table-item'; @@ -21,7 +15,8 @@ import { TokenStatsBoardTVL } from './components/token-stats-board/components/to import { TokenStatsBoardLayout } from './components/token-stats-board/token-stats-board.layout'; export function ProofOfReserve(): React.JSX.Element { - const { proofOfReserve, totalSupply, bitcoinPrice } = useContext(ProofOfReserveContext); + const { proofOfReserve, totalSupply, bitcoinPrice, allMintBurnEvents } = + useContext(ProofOfReserveContext); const [proofOfReserveSum, merchantProofOfReserves] = proofOfReserve || [ undefined, @@ -32,31 +27,6 @@ export function ProofOfReserve(): React.JSX.Element { }; }), ]; - const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); - - const { data: allMintBurnEvents } = useQuery({ - queryKey: ['allMintBurnEvents', ethereumNetworkConfiguration.dlcBTCContract.address], - queryFn: fetchMintBurnEventsHandler, - }); - - async function fetchMintBurnEventsHandler(): Promise { - const detailedEvents = await fetchMintBurnEvents( - ethereumNetworkConfiguration.dlcBTCContract, - ethereumNetworkConfiguration.httpURL, - undefined, - 10 - ); - return detailedEvents.map((event, index) => { - const isMint = event.from.toLowerCase() === BURN_ADDRESS.toLowerCase(); - return { - id: index, - dlcBTCAmount: isMint ? event.value : event.value * -1, - merchant: isMint ? event.to : event.from, - txHash: event.txHash, - date: new Date(event.timestamp * 1000).toDateString(), - }; - }); - } return ( diff --git a/src/app/components/protocol-history-table/components/protocol-history-table-item.tsx b/src/app/components/protocol-history-table/components/protocol-history-table-item.tsx index 4516272f..7a81fde4 100644 --- a/src/app/components/protocol-history-table/components/protocol-history-table-item.tsx +++ b/src/app/components/protocol-history-table/components/protocol-history-table-item.tsx @@ -1,33 +1,40 @@ /* eslint-disable */ import { HStack, Image, Text } from '@chakra-ui/react'; import { CustomSkeleton } from '@components/custom-skeleton/custom-skeleton'; +import { DetailedEvent } from '@models/ethereum-models'; import { truncateAddress, unshiftValue } from 'dlc-btc-lib/utilities'; -export interface ProtocolHistoryTableItemProps { - id: number; - merchant: string; - dlcBTCAmount: number; - txHash: string; - date: string; -} +import { findEthereumNetworkByName, formatEvent } from '@shared/utils'; export function ProtocolHistoryTableItem( - protocolHistoryTableItem: ProtocolHistoryTableItemProps + protocolHistoryTableItem: DetailedEvent ): React.JSX.Element { if (!protocolHistoryTableItem) return ; - const { merchant, dlcBTCAmount, txHash, date } = protocolHistoryTableItem; + const { + merchant, + dlcBTCAmount, + txHash, + date, + isMint, + chain: eventChain, + } = formatEvent(protocolHistoryTableItem); + + const ethereumNetwork = findEthereumNetworkByName(eventChain); + + console.log('dlcBTCAmount', dlcBTCAmount); return ( = 0 ? 'table.background.green' : 'table.background.red'} + bg={isMint ? 'table.background.green' : 'table.background.red'} blendMode={'screen'} border={'1px solid'} borderRadius={'md'} borderColor={'border.white.01'} + justifyContent={'space-between'} > {'dlcBTC @@ -35,15 +42,29 @@ export function ProtocolHistoryTableItem( {unshiftValue(dlcBTCAmount)} - - {truncateAddress(merchant)} - - - {truncateAddress(txHash)} - - - {date} - + + + {truncateAddress(merchant)} + + + + + window.open(`${ethereumNetwork.blockExplorers?.default.url}/tx/${txHash}`, '_blank') + } + cursor={'pointer'} + textDecoration={'underline'} + > + {truncateAddress(txHash)} + + + + + {date} + + ); } diff --git a/src/app/components/protocol-history-table/protocol-history-table.tsx b/src/app/components/protocol-history-table/protocol-history-table.tsx index 5af63923..dbbbb505 100644 --- a/src/app/components/protocol-history-table/protocol-history-table.tsx +++ b/src/app/components/protocol-history-table/protocol-history-table.tsx @@ -16,9 +16,9 @@ export function ProtocolHistoryTable({ items }: ProtocolHistoryTableProps): Reac Order Book - Merchant + Merchant Transaction - Date + Date diff --git a/src/app/functions/ethereum.functions.ts b/src/app/functions/ethereum.functions.ts deleted file mode 100644 index e9d17f0a..00000000 --- a/src/app/functions/ethereum.functions.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { DetailedEvent } from '@models/ethereum-models'; -import { Contract } from 'ethers'; - -export async function fetchMintBurnEvents( - dlcBTCContract: Contract, - rpcEndpoint: string, - ethreumAddress?: string, - lastN?: number -): Promise { - const contractAddress = dlcBTCContract.address; - - const req = await fetch( - `/.netlify/functions/fetch-mint-burn-events?providerURL=${rpcEndpoint}&contractAddress=${contractAddress}${ethreumAddress ? `&userAddress=${ethreumAddress}` : ''}&lastN=${lastN}` - ); - - if (!req.ok) { - throw new Error(`HTTP error! status: ${req.status}`); - } - - const events = await req.json(); - const detailedEvents: DetailedEvent[] = events.detailedEvents; - - return detailedEvents; -} diff --git a/src/app/hooks/use-mint-burn-events.ts b/src/app/hooks/use-mint-burn-events.ts new file mode 100644 index 00000000..b9cfdbdb --- /dev/null +++ b/src/app/hooks/use-mint-burn-events.ts @@ -0,0 +1,77 @@ +import { DetailedEvent } from '@models/ethereum-models'; +import { Merchant } from '@models/merchant'; +import { useQuery } from '@tanstack/react-query'; + +import { MINT_BURN_EVENTS_API_URL } from '@shared/constants/api.constants'; + +interface UseMintBurnEventsReturnType { + allMintBurnEvents: DetailedEvent[] | undefined; + merchantMintBurnEvents: { name: string; mintBurnEvents: DetailedEvent[] }[] | undefined; +} + +export function useMintBurnEvents(): UseMintBurnEventsReturnType { + async function fetchMintBurnEvents(ethereumAddress: string): Promise { + try { + const response = await fetch( + `${MINT_BURN_EVENTS_API_URL}/${appConfiguration.appEnvironment}/${ethereumAddress}` + ); + + if (!response.ok) { + throw new Error(`Error fetching mint burn events`); + } + + return await response.json(); + } catch (error) { + console.error(`Error fetching mint burn events`, error); + return []; + } + } + + async function fetchAllMintBurnEvents(): Promise { + try { + const mintBurnEvents = await Promise.all( + appConfiguration.merchants.map(async (merchant: Merchant) => { + const allMerchantMinBurnEvents = await Promise.all( + merchant.addresses.map(async address => { + return await fetchMintBurnEvents(address); + }) + ); + + const sortedAllMerchantMinBurnEvents = allMerchantMinBurnEvents + .flat() + .sort((a: DetailedEvent, b: DetailedEvent) => { + return b.timestamp - a.timestamp; + }); + + return { + name: merchant.name, + mintBurnEvents: sortedAllMerchantMinBurnEvents, + }; + }) + ); + + const allMintBurnEvents = mintBurnEvents + .map(merchant => merchant.mintBurnEvents) + .flat() + .sort((a, b) => b.timestamp - a.timestamp); + + return { + allMintBurnEvents, + merchantMintBurnEvents: mintBurnEvents, + }; + } catch (error) { + console.error(`Error fetching mint burn events`, error); + return undefined; + } + } + + const { data: mintBurnEvents } = useQuery({ + queryKey: ['mintBurnEvents'], + queryFn: fetchAllMintBurnEvents, + }); + + return { + allMintBurnEvents: mintBurnEvents?.allMintBurnEvents, + merchantMintBurnEvents: mintBurnEvents?.merchantMintBurnEvents, + }; +} diff --git a/src/app/providers/proof-of-reserve-context-provider.tsx b/src/app/providers/proof-of-reserve-context-provider.tsx index cb4e0c1c..f1a7e6a4 100644 --- a/src/app/providers/proof-of-reserve-context-provider.tsx +++ b/src/app/providers/proof-of-reserve-context-provider.tsx @@ -1,27 +1,34 @@ import { createContext } from 'react'; import { useBitcoinPrice } from '@hooks/use-bitcoin-price'; +import { useMintBurnEvents } from '@hooks/use-mint-burn-events'; import { useProofOfReserve } from '@hooks/use-proof-of-reserve'; import { useTotalSupply } from '@hooks/use-total-supply'; import { HasChildren } from '@models/has-children'; import { MerchantProofOfReserve } from '@models/merchant'; +import { DetailedEvent } from 'dlc-btc-lib/models'; interface ProofOfReserveContextProviderType { proofOfReserve: [number | undefined, MerchantProofOfReserve[]] | undefined; totalSupply: number | undefined; bitcoinPrice: number | undefined; + allMintBurnEvents: DetailedEvent[] | undefined; + merchantMintBurnEvents: { name: string; mintBurnEvents: DetailedEvent[] }[] | undefined; } export const ProofOfReserveContext = createContext({ proofOfReserve: undefined, totalSupply: undefined, bitcoinPrice: undefined, + allMintBurnEvents: undefined, + merchantMintBurnEvents: undefined, }); export function ProofOfReserveContextProvider({ children }: HasChildren): React.JSX.Element { const { proofOfReserve } = useProofOfReserve(); const { totalSupply } = useTotalSupply(); const { bitcoinPrice } = useBitcoinPrice(); + const { allMintBurnEvents, merchantMintBurnEvents } = useMintBurnEvents(); return ( {children} diff --git a/src/shared/constants/api.constants.ts b/src/shared/constants/api.constants.ts index cda15b30..d07361c8 100644 --- a/src/shared/constants/api.constants.ts +++ b/src/shared/constants/api.constants.ts @@ -1,4 +1,5 @@ const API_URL = 'https://api.dlc.link'; export const POINTS_API_URL = `${API_URL}/points`; export const PROOF_OF_RESERVE_API_URL = `${API_URL}/proof-of-reserve`; +export const MINT_BURN_EVENTS_API_URL = `${API_URL}/mint-burn-events`; export const TOTAL_SUPPLY_API_URL = `${API_URL}/dlcbtc/total-supply`; diff --git a/src/shared/constants/ethereum.constants.ts b/src/shared/constants/ethereum.constants.ts index 2c2e49bf..0ce0779d 100644 --- a/src/shared/constants/ethereum.constants.ts +++ b/src/shared/constants/ethereum.constants.ts @@ -9,7 +9,6 @@ import { sepolia, } from 'viem/chains'; -export const BURN_ADDRESS = '0x0000000000000000000000000000000000000000'; export const SUPPORTED_VIEM_CHAINS: Chain[] = [ mainnet, sepolia, diff --git a/src/shared/models/ethereum-models.ts b/src/shared/models/ethereum-models.ts index 3c0fc565..4c72a60f 100644 --- a/src/shared/models/ethereum-models.ts +++ b/src/shared/models/ethereum-models.ts @@ -27,4 +27,16 @@ export interface DetailedEvent { value: number; timestamp: number; txHash: string; + isCCIP: boolean; + chain: string; + eventType: 'mint' | 'burn' | 'transfer'; +} +export interface FormattedEvent { + merchant: string; + dlcBTCAmount: number; + txHash: string; + date: string; + chain: string; + isMint: boolean; + isCCIP: boolean; } diff --git a/src/shared/models/points.models.ts b/src/shared/models/points.models.ts index ca8811be..083e99d5 100644 --- a/src/shared/models/points.models.ts +++ b/src/shared/models/points.models.ts @@ -7,5 +7,6 @@ export interface ProtocolRewards { export interface PointsData { total: number; + useTotal: number; protocols: ProtocolRewards[]; } diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 44e04a7b..08595695 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1,4 +1,9 @@ +import { DetailedEvent, FormattedEvent } from '@models/ethereum-models'; import Decimal from 'decimal.js'; +import { supportedEthereumNetworks } from 'dlc-btc-lib/constants'; +import { Chain } from 'viem'; + +import { SUPPORTED_VIEM_CHAINS } from './constants/ethereum.constants'; export function formatNumber(value: number): string { if (value < 10000) { @@ -13,3 +18,32 @@ export function formatNumber(value: number): string { return new Decimal(value).dividedBy(1000000000000).toFixed(1).replace(/\.0$/, '') + 'T'; } } + +export function findEthereumNetworkByName(ethereumNetworkName: string): Chain { + const ethereumNetworkID = supportedEthereumNetworks.find( + network => network.name.toLowerCase() === ethereumNetworkName + )?.id; + if (!ethereumNetworkID) { + throw new Error(`Could not find Ethereum network with name ${ethereumNetworkName}`); + } + const chain = SUPPORTED_VIEM_CHAINS.find(chain => chain.id === parseInt(ethereumNetworkID)); + + if (!chain) { + throw new Error(`Could not find chain with id ${ethereumNetworkID}`); + } + + return chain; +} + +export function formatEvent(event: DetailedEvent): FormattedEvent { + const isMint = event.eventType === 'mint'; + return { + dlcBTCAmount: isMint ? event.value : -event.value, + merchant: isMint ? event.to : event.from, + txHash: event.txHash, + date: new Date(event.timestamp * 1000).toDateString(), + isMint, + chain: event.chain, + isCCIP: event.isCCIP, + }; +}