From c722a0e358a1eb2c1f4ee799197e7be7e0ae6aab Mon Sep 17 00:00:00 2001 From: Sero <69639595+Seroxdesign@users.noreply.github.com> Date: Mon, 20 May 2024 08:56:01 -0400 Subject: [PATCH 01/19] fix issue --- libs/dm-hooks/src/useAccountingV3.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 9a2dd52b..31cd3a40 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -11,13 +11,13 @@ const transformTokenBalances = (tokenBalanceRes, safeAddress: string) => { return { safeAddress, tokenBalances: tokenBalanceRes, fiatTotal }; }; -const listTokenBalances = async (safeAddress: string) => { +const listTokenBalances = async ({ safeAddress }) => { try { - const res = await fetch(`${API_URL}/safes/${safeAddress}/balances/usd/`); + const res = await fetch(`${API_URL}/safes/${safeAddress}/balances/`); const data = await res.json(); return { data: transformTokenBalances(data, safeAddress) }; } catch (err) { - return { error: 'Error fetching token balances. Please try again.' }; + return { error: `Error fetching token balances. Please try again. ${err}` }; } }; @@ -25,15 +25,15 @@ const useAccountingV3 = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - + console.log(data, 'data') useEffect(() => { const fetchData = async () => { const checksum = getAddress('0x181ebdb03cb4b54f4020622f1b0eacd67a8c63ac'); - const response = await listTokenBalances(checksum); - + const response = await listTokenBalances({ safeAddress: checksum }); if (response.error) { setError(response.error); } else { + console.log(response.data); setData(response.data); } From 50ff4cb7236a263b90a712148091d526e6a13df1 Mon Sep 17 00:00:00 2001 From: Sero <69639595+Seroxdesign@users.noreply.github.com> Date: Thu, 23 May 2024 09:07:43 -0400 Subject: [PATCH 02/19] fetch tx data --- .../src/getTokenBalancesWithPrices.ts | 348 ++++++++++++++++++ libs/dm-hooks/src/useAccountingV3.ts | 15 +- 2 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 libs/dm-hooks/src/getTokenBalancesWithPrices.ts diff --git a/libs/dm-hooks/src/getTokenBalancesWithPrices.ts b/libs/dm-hooks/src/getTokenBalancesWithPrices.ts new file mode 100644 index 00000000..3eac072d --- /dev/null +++ b/libs/dm-hooks/src/getTokenBalancesWithPrices.ts @@ -0,0 +1,348 @@ +// import { +// camelize, +// formatDate, +// formatUnitsAsNumber, +// GUILD_GNOSIS_DAO_ADDRESS, +// } from '@raidguild/dm-utils'; +// import { useState, useEffect } from 'react'; +// import { +// IAccountingRaid, +// ICalculatedTokenBalances, +// IMappedTokenPrice, +// IMolochStatsBalance, +// ISmartEscrow, +// ISmartEscrowWithdrawal, +// ISpoils, +// ITokenBalance, +// ITokenBalanceLineItem, +// ITokenPrice, +// IVaultTransaction, +// } from '@raidguild/dm-types'; +// import _ from 'lodash'; + +// class CalculateTokenBalances { +// calculatedTokenBalances: ICalculatedTokenBalances; + +// constructor() { +// this.calculatedTokenBalances = {}; +// } + +// getBalance(tokenAddress: string) { +// this.initTokenBalance(tokenAddress); +// return this.calculatedTokenBalances[tokenAddress].balance; +// } + +// initTokenBalance(tokenAddress: string) { +// if (!(tokenAddress in this.calculatedTokenBalances)) { +// this.calculatedTokenBalances[tokenAddress] = { +// out: BigInt(0), +// in: BigInt(0), +// balance: BigInt(0), +// }; +// } +// } + +// incrementInflow(tokenAddress: string, inValue: bigint) { +// this.initTokenBalance(tokenAddress); +// const tokenStats = this.calculatedTokenBalances[tokenAddress]; +// this.calculatedTokenBalances[tokenAddress] = { +// ...tokenStats, +// in: tokenStats.in + inValue, +// balance: tokenStats.balance + inValue, +// }; +// } + +// incrementOutflow(tokenAddress: string, outValue: bigint) { +// this.initTokenBalance(tokenAddress); +// const tokenStats = this.calculatedTokenBalances[tokenAddress]; +// this.calculatedTokenBalances[tokenAddress] = { +// ...tokenStats, +// out: tokenStats.out + outValue, +// balance: tokenStats.balance - outValue, +// }; +// } + +// getBalances() { +// return this.calculatedTokenBalances; +// } +// } +// const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; + +// // used to store all the inflow and outflow of each token when iterating over the list of moloch stats +// const calculatedTokenBalances = new CalculateTokenBalances(); +// const formatBalancesAsTransactions = async ( +// balances: Array +// ) => { +// try { +// const mapMolochStatsToTreasuryTransaction = async ( +// molochStatsBalances: Array +// ): Promise> => { +// const treasuryTransactions = await Promise.all( +// molochStatsBalances.map(async (molochStatBalance) => { +// /** +// * molochStatBalance.amount is incorrect because ragequit does not return the correct amount +// * so instead, we track the previous balance of the token in the calculatedTokenBalances class state +// * and subtract from current balance to get the amount. +// */ +// let tokenValue = +// calculatedTokenBalances.getBalance(molochStatBalance.tokenAddress) - +// BigInt(molochStatBalance.balance); + +// // Ensure the value is absolute +// tokenValue = tokenValue >= BigInt(0) ? tokenValue : -tokenValue; + +// const tokenFormattedValue = formatUnitsAsNumber( +// tokenValue, +// molochStatBalance.tokenDecimals +// ); + +// const balances = (() => { +// if ( +// molochStatBalance.payment === false && +// molochStatBalance.tribute === false +// ) { +// const balance = formatUnitsAsNumber( +// calculatedTokenBalances.getBalance( +// molochStatBalance.tokenAddress +// ), +// molochStatBalance.tokenDecimals +// ); + +// return { +// in: 0, +// out: 0, +// net: 0, +// balance, +// }; +// } +// if ( +// molochStatBalance.payment === false && +// molochStatBalance.tribute === true +// ) { +// calculatedTokenBalances.incrementInflow( +// molochStatBalance.tokenAddress, +// tokenValue +// ); + +// const balance = formatUnitsAsNumber( +// calculatedTokenBalances.getBalance( +// molochStatBalance.tokenAddress +// ), +// molochStatBalance.tokenDecimals +// ); + +// return { +// in: tokenFormattedValue, +// out: 0, +// net: tokenFormattedValue, +// balance, +// }; +// } + +// if ( +// molochStatBalance.payment === true && +// molochStatBalance.tribute === false +// ) { +// calculatedTokenBalances.incrementOutflow( +// molochStatBalance.tokenAddress, +// tokenValue +// ); + +// const balance = formatUnitsAsNumber( +// calculatedTokenBalances.getBalance( +// molochStatBalance.tokenAddress +// ), +// molochStatBalance.tokenDecimals +// ); + +// return { +// in: 0, +// out: tokenFormattedValue, +// net: -tokenFormattedValue, +// balance, +// }; +// } + +// const balance = formatUnitsAsNumber( +// calculatedTokenBalances.getBalance( +// molochStatBalance.tokenAddress +// ), +// molochStatBalance.tokenDecimals +// ); + +// return { +// in: 0, +// out: 0, +// net: 0, +// balance, +// }; +// })(); + +// const proposalTitle = (() => { +// try { +// return JSON.parse( +// molochStatBalance.proposalDetail?.details ?? '{}' +// ).title; +// } catch (error) { +// return ''; +// } +// })(); + +// const txExplorerLink = `https://blockscout.com/xdai/mainnet/tx/${molochStatBalance.transactionHash}`; +// const proposalLink = molochStatBalance.proposalDetail +// ? `https://app.daohaus.club/dao/0x64/${GUILD_GNOSIS_DAO_ADDRESS}/proposals/${molochStatBalance.proposalDetail.proposalId}` +// : ''; +// const epochTimeAtIngressMs = +// Number(molochStatBalance.timestamp) * 1000; +// const date = new Date(epochTimeAtIngressMs); +// const elapsedDays = +// balances.net > 0 +// ? Math.floor( +// (Date.now() - epochTimeAtIngressMs) / MILLISECONDS_PER_DAY +// ) +// : undefined; + +// const proposal = molochStatBalance.proposalDetail; + +// return { +// date, +// elapsedDays, +// type: _.startCase(molochStatBalance.action), +// tokenSymbol: molochStatBalance.tokenSymbol, +// tokenDecimals: Number(molochStatBalance.tokenDecimals), +// tokenAddress: molochStatBalance.tokenAddress, +// txExplorerLink, +// counterparty: molochStatBalance.counterpartyAddress, +// proposalId: proposal?.proposalId ?? '', +// proposalLink, +// proposalShares: proposal?.sharesRequested +// ? BigInt(proposal.sharesRequested) +// : undefined, +// proposalLoot: proposal?.lootRequested +// ? BigInt(proposal.lootRequested) +// : undefined, +// proposalApplicant: proposal?.applicant ?? '', +// proposalTitle, +// ...balances, +// }; +// }) +// ); + +// return treasuryTransactions; +// }; + +// const treasuryTransactions = await mapMolochStatsToTreasuryTransaction( +// balances +// ); + +// return { +// transactions: _.orderBy( +// treasuryTransactions, +// 'date', +// 'desc' +// ) as Array, +// }; +// } catch (error) { +// return { +// error: { +// message: (error as Error).message, +// }, +// }; +// } +// }; + +// const mapMolochTokenBalancesToTokenBalanceLineItem = async ( +// molochTokenBalances: ITokenBalance[], +// calculatedTokenBalances: ICalculatedTokenBalances +// ): Promise => { +// const tokenBalanceLineItems = await Promise.all( +// molochTokenBalances.map(async (molochTokenBalance) => { +// const tokenExplorerLink = `https://blockscout.com/xdai/mainnet/address/${molochTokenBalance.token.tokenAddress}`; +// const calculatedTokenBalance = +// calculatedTokenBalances[molochTokenBalance.token.tokenAddress]; + +// return { +// ...molochTokenBalance, +// date: new Date(), +// tokenSymbol: molochTokenBalance.token.symbol, +// tokenExplorerLink, +// inflow: { +// tokenValue: formatUnitsAsNumber( +// calculatedTokenBalance?.in || BigInt(0), +// molochTokenBalance.token.decimals +// ), +// }, +// outflow: { +// tokenValue: formatUnitsAsNumber( +// calculatedTokenBalance?.out || BigInt(0), +// molochTokenBalance.token.decimals +// ), +// }, +// closing: { +// tokenValue: formatUnitsAsNumber( +// molochTokenBalance.tokenBalance, +// molochTokenBalance.token.decimals +// ), +// }, +// }; +// }) +// ); + +// // TODO fix type +// return tokenBalanceLineItems as unknown as any; +// }; + +// export const getTokenBalancesWithPrices = async (safeAddress: string) => { +// const [transactions, setTransactions] = useState>( +// [] +// ); +// const [balances, setBalances] = useState>([]); +// const [tokenPrices, setTokenPrices] = useState({}); + +// // TODO use onSuccess/onError instead of useEffect +// useEffect(() => { +// (async () => { +// if (status === 'success') { +// const formattedData = await formatBalancesAsTransactions( +// data.pages[0].transactions +// ); +// const tokenBalances = +// await mapMolochTokenBalancesToTokenBalanceLineItem( +// data.pages[0].balances, +// calculatedTokenBalances.getBalances() +// ); +// const spoils = await formatSpoils( +// data.pages[0].smartEscrows, +// data.pages[0].raids +// ); +// const { historicalPrices, currentPrices } = data.pages[0]; +// // using any because not sure how to type this +// const mappedPrices: { [key: string]: any } = {}; +// historicalPrices.forEach((price) => { +// if (!mappedPrices[price.symbol]) { +// mappedPrices[price.symbol] = {}; +// mappedPrices[price.symbol][price.date] = price.priceUsd; +// } else { +// mappedPrices[price.symbol][price.date] = price.priceUsd; +// } +// }); +// currentPrices.forEach((price) => { +// const date = new Date(); +// if (!mappedPrices[price.symbol]) { +// mappedPrices[price.symbol] = {}; +// mappedPrices[price.symbol][formatDate(date)] = price.priceUsd; +// } else { +// mappedPrices[price.symbol][formatDate(date)] = price.priceUsd; +// } +// }); +// setTransactions(formattedData.transactions || []); +// setBalances(tokenBalances); +// setSpoils(spoils); +// setTokenPrices(mappedPrices); +// } else if (status === 'error') { +// // eslint-disable-next-line no-console +// console.error('accounting data fetching failed with: ', status); +// } +// })(); +// }, [data, status]); +// }; \ No newline at end of file diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 31cd3a40..60965a46 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -21,6 +21,18 @@ const listTokenBalances = async ({ safeAddress }) => { } }; +const getSafeTransactionProposals = async ({ safeAddress }) => { + try { + const res = await fetch(`${API_URL}/safes/${safeAddress}/all-transactions/`); + const txData = await res.json(); + console.log(txData, 'txData') + return { txData }; + } catch (err) { + return { error: `Error fetching safe transactions. Please try again. ${err}` }; + } + +} + const useAccountingV3 = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); @@ -29,12 +41,13 @@ const useAccountingV3 = () => { useEffect(() => { const fetchData = async () => { const checksum = getAddress('0x181ebdb03cb4b54f4020622f1b0eacd67a8c63ac'); + const txResponse = await getSafeTransactionProposals({ safeAddress: checksum }); const response = await listTokenBalances({ safeAddress: checksum }); if (response.error) { setError(response.error); } else { console.log(response.data); - setData(response.data); + setData({tokens: response.data, transactions: txResponse.txData}); } setLoading(false); From 8e6be72473313b6677ad72e0b10ed1aadd73b740 Mon Sep 17 00:00:00 2001 From: Sero <69639595+Seroxdesign@users.noreply.github.com> Date: Fri, 24 May 2024 09:57:30 -0400 Subject: [PATCH 03/19] proposal data --- libs/dm-hooks/src/useAccountingV3.ts | 50 ++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 60965a46..6238d481 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -1,5 +1,18 @@ import { useEffect, useState } from 'react'; import { getAddress } from 'viem'; +import { NETWORK_CONFIG } from '@raidguild/escrow-utils'; +import { GraphQLClient } from 'graphql-request'; +import _ from 'lodash'; + +const graphUrl = (chainId: number = 4) => + `https://api.thegraph.com/subgraphs/name/${NETWORK_CONFIG[chainId].SUBGRAPH}`; + +export const SUPPORTED_NETWORKS = _.map(_.keys(NETWORK_CONFIG), _.toNumber); + +export const client = (chainId: number) => new GraphQLClient(graphUrl(chainId)); +export const v3client = new GraphQLClient( + 'https://api.thegraph.com/subgraphs/name/hausdao/daohaus-v3-gnosis' +); const API_URL = 'https://safe-transaction-gnosis-chain.safe.global/api/v1'; @@ -43,13 +56,46 @@ const useAccountingV3 = () => { const checksum = getAddress('0x181ebdb03cb4b54f4020622f1b0eacd67a8c63ac'); const txResponse = await getSafeTransactionProposals({ safeAddress: checksum }); const response = await listTokenBalances({ safeAddress: checksum }); + let proposals = [] + txResponse?.txData?.results?.forEach(async (tx) => { + try { + const proposal: { proposals: any[] } = await v3client.request(` + { + proposals(where: { processTxHash: "${tx.transactionHash}"}) { + id + createdAt + createdBy + proposedBy + processTxHash + proposalType + description + } + } + `) + // const members: { members: any[] } = await v3client.request(` + // members(where: { memberAddress: ${proposal?.proposals[0].proposedBy} }) { + // id + // createdAt + // memberAddress + // shares + // loot + // delegatingTo + // delegateShares + // } + // `) + // console.log(members, 'members') + proposals.push({ proposal: proposal?.proposals[0]}) + } catch { + console.log('error') + } + }) if (response.error) { setError(response.error); } else { console.log(response.data); - setData({tokens: response.data, transactions: txResponse.txData}); + setData({tokens: response.data, transactions: txResponse.txData, proposalsInfo: proposals}); } - + setLoading(false); }; From 47b533d668f71941963601d5a9ade4809a3ad08e Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Wed, 5 Jun 2024 12:44:48 +0200 Subject: [PATCH 04/19] wip --- .../frontend/components/TransactionsTable.tsx | 2 +- apps/frontend/pages/accounting.tsx | 107 +++++++++----- libs/dm-hooks/src/index.ts | 1 + libs/dm-hooks/src/useAccountingV3.ts | 86 ++++++----- libs/dm-hooks/src/useFormattedData.ts | 1 + libs/dm-hooks/src/useFormattedDataV3.ts | 135 ++++++++++++++++++ libs/dm-utils/src/table.ts | 4 +- 7 files changed, 262 insertions(+), 74 deletions(-) create mode 100644 libs/dm-hooks/src/useFormattedDataV3.ts diff --git a/apps/frontend/components/TransactionsTable.tsx b/apps/frontend/components/TransactionsTable.tsx index 71a5212d..3d32fc74 100644 --- a/apps/frontend/components/TransactionsTable.tsx +++ b/apps/frontend/components/TransactionsTable.tsx @@ -21,7 +21,7 @@ const columnHelper = createColumnHelper(); const columns = [ columnHelper.accessor('date', { - cell: (info) => info.getValue().toLocaleString(), + cell: (info) => info.getValue()?.toLocaleString(), header: 'Date', meta: { dataType: 'datetime', diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index 70b69996..63b3b29c 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -8,12 +8,14 @@ import { TabPanel, TabPanels, Tabs, + Tr, } from '@raidguild/design-system'; import { useAccountingV3, useAccountingV2, useFormattedData, useMemberList, + useFormattedDataV3, } from '@raidguild/dm-hooks'; import { exportToCsv } from '@raidguild/dm-utils'; import _ from 'lodash'; @@ -37,7 +39,16 @@ export const Accounting = () => { } = useAccountingV2({ token, }); + console.log('dataFromMolochV2', dataFromMolochV2); const { data: dataFromMolochV3 } = useAccountingV3(); + console.log('dataFromMolochV3', dataFromMolochV3); + // const { + // balances: balancesV3, + // spoils: spoilsV3, + // transactions: transactionsV3, + // tokenPrices: tokenPricesV3, + // } = dataFromMolochV3; + const { data: memberData } = useMemberList({ token, limit: 1000, @@ -51,38 +62,58 @@ export const Accounting = () => { transactionsWithPrices, transactionsWithPricesAndMembers, } = useFormattedData(memberData, balances, transactions, tokenPrices); + console.log('balancesWithPrices', balancesWithPrices); + console.log( + 'transactionsWithPricesAndMembers', + transactionsWithPricesAndMembers + ); + + const { + balancesWithPrices: balancesWithPricesV3, + transactionsWithPrices: transactionsWithPricesV3, + transactionsWithPricesAndMembers: transactionsWithPricesAndMembersV3, + } = useFormattedDataV3({ + balances: dataFromMolochV3?.tokens?.tokenBalances || [], + transactions: dataFromMolochV3?.transactions?.results || [], + tokenPrices: {}, + members, + }); + console.log( + 'transactionsWithPricesAndMembersV3', + transactionsWithPricesAndMembersV3 + ); const onExportCsv = useCallback( (type: 'transactions' | 'balances' | 'spoils') => { let csvString = ''; if (type === 'transactions') { const formattedTransactions = transactionsWithPrices.map((t) => ({ - ['Date']: t.date, - ['Tx Explorer Link']: t.txExplorerLink, - ['Elapsed Days']: t.elapsedDays, - ['Type']: t.type, - ['Applicant']: t.proposalApplicant, - ['Applicant Member']: + Date: t.date, + 'Tx Explorer Link': t.txExplorerLink, + 'Elapsed Days': t.elapsedDays, + Type: t.type, + Applicant: t.proposalApplicant, + 'Applicant Member': members[t.proposalApplicant.toLowerCase()]?.name || '-', - ['Shares']: t.proposalShares, - ['Loot']: t.proposalLoot, - ['Title']: t.proposalTitle, - ['Counterparty']: t.counterparty, - ['Counterparty Member']: + Shares: t.proposalShares, + Loot: t.proposalLoot, + Title: t.proposalTitle, + Counterparty: t.counterparty, + 'Counterparty Member': members[t.counterparty.toLowerCase()]?.name || '-', - ['Token Symbol']: t.tokenSymbol, - ['Token Decimals']: t.tokenDecimals, - ['Token Address']: t.tokenAddress, - ['Inflow']: t.in, - ['Inflow USD']: t.priceConversion + 'Token Symbol': t.tokenSymbol, + 'Token Decimals': t.tokenDecimals, + 'Token Address': t.tokenAddress, + Inflow: t.in, + 'Inflow USD': t.priceConversion ? `$${(t.in * t.priceConversion).toLocaleString()}` : '$-', - ['Outflow']: t.out, - ['Outflow USD']: t.priceConversion + Outflow: t.out, + 'Outflow USD': t.priceConversion ? `$${(t.out * t.priceConversion).toLocaleString()}` : '$-', - ['Balance']: t.balance, - ['Balance USD']: t.priceConversion + Balance: t.balance, + 'Balance USD': t.priceConversion ? `$${(t.balance * t.priceConversion).toLocaleString()}` : '$-', })); @@ -90,22 +121,22 @@ export const Accounting = () => { } else if (type === 'balances') { if (type === 'balances') { const formattedBalances = balancesWithPrices.map((b) => ({ - ['Token']: b.tokenSymbol, - ['Tx Explorer Link']: b.tokenExplorerLink, - ['Inflow']: b.inflow.tokenValue, - ['Inflow USD']: b.priceConversion + Token: b.tokenSymbol, + 'Tx Explorer Link': b.tokenExplorerLink, + Inflow: b.inflow.tokenValue, + 'Inflow USD': b.priceConversion ? `$${( Number(b.inflow.tokenValue) * b.priceConversion ).toLocaleString()}` : '$-', - ['Outflow']: b.outflow.tokenValue, - ['Outflow USD']: b.priceConversion + Outflow: b.outflow.tokenValue, + 'Outflow USD': b.priceConversion ? `$${( Number(b.outflow.tokenValue) * b.priceConversion ).toLocaleString()}` : '$-', - ['Balance']: b.closing.tokenValue, - ['Balance USD']: b.priceConversion + Balance: b.closing.tokenValue, + 'Balance USD': b.priceConversion ? `$${( Number(b.closing.tokenValue) * b.priceConversion ).toLocaleString()}` @@ -115,12 +146,12 @@ export const Accounting = () => { } } else if (type === 'spoils') { const formattedSpoils = spoils.map((s) => ({ - ['Date']: s.date, - ['Raid']: s.raidName, + Date: s.date, + Raid: s.raidName, // TODO: Get this dynamically from the subgraph - ['Token Symbol']: 'wxDAI', - ['To DAO Treasury']: `$${s.parentShare.toLocaleString()}`, - ['To Raid Party']: `$${s.childShare.toLocaleString()}`, + 'Token Symbol': 'wxDAI', + 'To DAO Treasury': `$${s.parentShare.toLocaleString()}`, + 'To Raid Party': `$${s.childShare.toLocaleString()}`, })); csvString = Papa.unparse(formattedSpoils); } @@ -189,10 +220,10 @@ export const Accounting = () => { - + -
This is the placeholder for v3 balances data.
+
@@ -229,11 +260,13 @@ export const Accounting = () => { -
This is the placeholder for v3 transactions data.
+
diff --git a/libs/dm-hooks/src/index.ts b/libs/dm-hooks/src/index.ts index 0cf07cb4..2ec97336 100644 --- a/libs/dm-hooks/src/index.ts +++ b/libs/dm-hooks/src/index.ts @@ -17,6 +17,7 @@ export { default as useContactUpdate } from './useContactUpdate'; export { default as useDashboardList } from './useDashboardList'; export { default as useDefaultTitle } from './useDefaultTitle'; export { default as useFormattedData } from './useFormattedData'; +export { default as useFormattedDataV3 } from './useFormattedDataV3'; export * from './useLinks'; export { default as useLinksUpdate } from './useLinksUpdate'; export { default as useMemberCreate } from './useMemberCreate'; diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 6238d481..ecf5d0c2 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -1,8 +1,8 @@ -import { useEffect, useState } from 'react'; -import { getAddress } from 'viem'; import { NETWORK_CONFIG } from '@raidguild/escrow-utils'; import { GraphQLClient } from 'graphql-request'; import _ from 'lodash'; +import { useEffect, useState } from 'react'; +import { getAddress } from 'viem'; const graphUrl = (chainId: number = 4) => `https://api.thegraph.com/subgraphs/name/${NETWORK_CONFIG[chainId].SUBGRAPH}`; @@ -17,8 +17,9 @@ export const v3client = new GraphQLClient( const API_URL = 'https://safe-transaction-gnosis-chain.safe.global/api/v1'; const transformTokenBalances = (tokenBalanceRes, safeAddress: string) => { + console.log('tokenBalanceRes', tokenBalanceRes); const fiatTotal = tokenBalanceRes.reduce( - (sum: number, balance) => sum + Number(balance.fiatBalance), + (sum: number, balance) => sum + Number(balance.balance), 0 ); return { safeAddress, tokenBalances: tokenBalanceRes, fiatTotal }; @@ -28,7 +29,11 @@ const listTokenBalances = async ({ safeAddress }) => { try { const res = await fetch(`${API_URL}/safes/${safeAddress}/balances/`); const data = await res.json(); - return { data: transformTokenBalances(data, safeAddress) }; + const filteredTokenBalanceRes = _.filter(data, 'tokenAddress'); + + return { + data: transformTokenBalances(filteredTokenBalanceRes, safeAddress), + }; } catch (err) { return { error: `Error fetching token balances. Please try again. ${err}` }; } @@ -36,27 +41,34 @@ const listTokenBalances = async ({ safeAddress }) => { const getSafeTransactionProposals = async ({ safeAddress }) => { try { - const res = await fetch(`${API_URL}/safes/${safeAddress}/all-transactions/`); + const res = await fetch( + `${API_URL}/safes/${safeAddress}/all-transactions/` + ); const txData = await res.json(); - console.log(txData, 'txData') return { txData }; } catch (err) { - return { error: `Error fetching safe transactions. Please try again. ${err}` }; + return { + error: `Error fetching safe transactions. Please try again. ${err}`, + }; } - -} +}; const useAccountingV3 = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - console.log(data, 'data') + const [members, setMembers] = useState(null); + useEffect(() => { const fetchData = async () => { const checksum = getAddress('0x181ebdb03cb4b54f4020622f1b0eacd67a8c63ac'); - const txResponse = await getSafeTransactionProposals({ safeAddress: checksum }); - const response = await listTokenBalances({ safeAddress: checksum }); - let proposals = [] + console.log('checksum', checksum); + const txResponse = await getSafeTransactionProposals({ + safeAddress: checksum, + }); + const tokenBalances = await listTokenBalances({ safeAddress: checksum }); + const proposals = []; + console.log('txResponse?.txData?.results', txResponse?.txData?.results); txResponse?.txData?.results?.forEach(async (tx) => { try { const proposal: { proposals: any[] } = await v3client.request(` @@ -71,31 +83,37 @@ const useAccountingV3 = () => { description } } - `) - // const members: { members: any[] } = await v3client.request(` - // members(where: { memberAddress: ${proposal?.proposals[0].proposedBy} }) { - // id - // createdAt - // memberAddress - // shares - // loot - // delegatingTo - // delegateShares - // } - // `) - // console.log(members, 'members') - proposals.push({ proposal: proposal?.proposals[0]}) + `); + const m: { members: any[] } = await v3client.request(` + members(where: { memberAddress: ${proposal?.proposals[0].proposedBy} }) { + id + createdAt + memberAddress + shares + loot + delegatingTo + delegateShares + } + `); + console.log('m', m); + setMembers(m.members[0]); + proposals.push({ proposal: proposal?.proposals[0] }); } catch { - console.log('error') + console.log('error'); } - }) - if (response.error) { - setError(response.error); + }); + if (tokenBalances.error) { + setError(tokenBalances.error); } else { - console.log(response.data); - setData({tokens: response.data, transactions: txResponse.txData, proposalsInfo: proposals}); + console.log(tokenBalances.data); + setData({ + tokens: tokenBalances.data, + transactions: txResponse.txData, + proposalsInfo: proposals, + members, + }); } - + setLoading(false); }; diff --git a/libs/dm-hooks/src/useFormattedData.ts b/libs/dm-hooks/src/useFormattedData.ts index 4fd93780..0134ac5b 100644 --- a/libs/dm-hooks/src/useFormattedData.ts +++ b/libs/dm-hooks/src/useFormattedData.ts @@ -56,6 +56,7 @@ const useFormattedData = ( () => withPrices(transactions), [transactions, withPrices] ); + console.log('transactionsWithPrices', transactionsWithPrices); const transactionsWithPricesAndMembers = useMemo( () => diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts new file mode 100644 index 00000000..cc0a8c88 --- /dev/null +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -0,0 +1,135 @@ +import { + IMappedTokenPrice, + IMember, + ITokenBalanceLineItem, + IVaultTransaction, +} from '@raidguild/dm-types'; +import { formatDate, REGEX_ETH_ADDRESS } from '@raidguild/dm-utils'; +import { useCallback, useMemo } from 'react'; + +const useFormattedDataV3 = ({ + balances, + transactions, + tokenPrices, + members, +}: { + balances: ITokenBalanceLineItem[]; + transactions: IVaultTransaction[]; + tokenPrices: IMappedTokenPrice; + members: Record; +}) => { + console.log('balances', balances); + const withPrices = useCallback( + // fix type + (items: any[]) => + (items || []).map((t) => { + if (!t) return t; + const formattedDate = formatDate(t.date); + const tokenSymbol = t.tokenSymbol?.toLowerCase(); + if ( + tokenPrices[tokenSymbol] && + tokenPrices[tokenSymbol][formattedDate] + ) { + return { + ...t, + priceConversion: tokenPrices[tokenSymbol][formattedDate], + }; + } + if (tokenSymbol?.includes('xdai')) { + return { + ...t, + priceConversion: 1, + }; + } + return t; + }), + [tokenPrices] + ); + + const balancesWithPrices = useMemo( + () => withPrices(balances), + [balances, withPrices] + ); + console.log('balancesWithPrices', balancesWithPrices); + + const transactionsWithPrices = useMemo( + () => + withPrices(transactions).map((t) => { + const transfer = t.transfers[0]; + const tokenSymbol = transfer?.tokenInfo?.symbol || ''; + const tokenDecimals = transfer?.tokenInfo?.decimals || 0; + const tokenAddress = transfer?.tokenAddress || ''; + + const inAmount = + transfer?.type === 'DEPOSIT' ? parseFloat(transfer.value) : 0; + const outAmount = + transfer?.type === 'WITHDRAW' ? parseFloat(transfer.value) : 0; + + return { + balance: 0, // Placeholder, needs to be calculated separately + counterparty: transfer?.to, + date: new Date(t.executionDate), + elapsedDays: undefined, // Placeholder, needs to be calculated separately + in: inAmount, + net: inAmount - outAmount, + out: outAmount, + proposalApplicant: '', // Placeholder + proposalId: '', // Placeholder + proposalLink: '', // Placeholder + proposalLoot: undefined, // Placeholder + proposalShares: undefined, // Placeholder + proposalTitle: '', // Placeholder + tokenAddress, + tokenDecimals, + tokenSymbol, + txExplorerLink: `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`, + type: t.txType, + }; + }), + [transactions, withPrices] + ); + + const transactionsWithPricesAndMembers = useMemo( + () => + transactionsWithPrices.map((t) => { + const ethAddress = t.proposalApplicant?.toLowerCase(); + const m = members[ethAddress]; + const memberLink = m?.ethAddress?.match(REGEX_ETH_ADDRESS) + ? `/members/${ethAddress}` + : undefined; + + return { + balance: t.balance, + counterparty: t.counterparty, + date: t.date, + elapsedDays: t.elapsedDays, + in: t.in, + memberEnsName: m?.ensName, + memberLink, + memberName: m?.name, + net: t.net, + out: t.out, + proposalApplicant: t.proposalApplicant, + proposalId: t.proposalId, + proposalLink: t.proposalLink, + proposalLoot: t.proposalLoot, + proposalShares: t.proposalShares, + proposalTitle: t.proposalTitle, + tokenAddress: t.tokenAddress, + tokenDecimals: t.tokenDecimals, + tokenSymbol: t.tokenSymbol, + txExplorerLink: t.txExplorerLink, + type: t.type, + }; + }), + [transactionsWithPrices, members] + ); + + return { + balancesWithPrices, + transactionsWithPrices, + transactionsWithPricesAndMembers, + }; +}; + +export default useFormattedDataV3; diff --git a/libs/dm-utils/src/table.ts b/libs/dm-utils/src/table.ts index 4b2d7d34..ce3ec507 100644 --- a/libs/dm-utils/src/table.ts +++ b/libs/dm-utils/src/table.ts @@ -16,7 +16,7 @@ export const exportToCsv = (csvString: string, fileName: string) => { link.remove(); }; -export const formatDate = (date: Date) => date.toISOString().split('T')[0]; +export const formatDate = (date: Date) => date?.toISOString().split('T')[0]; export const formatUnitsAsNumber = (value: bigint, decimals: number) => Number(formatUnits(value, decimals)); @@ -90,7 +90,7 @@ export const minMaxDateFilter = ( ) => { const min = Date.parse(columnFilter?.[0]); const max = Date.parse(columnFilter?.[1]) + DAY_MILLISECONDS; - const value = (row.getValue(columnId) as Date).getTime(); + const value = (row.getValue(columnId) as Date)?.getTime(); // Rank the item const itemRank = rankValue(value, min, max); From f495e9458782d747ba68fcc903970eb4c8e35ba6 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Wed, 5 Jun 2024 18:12:17 +0200 Subject: [PATCH 05/19] wip2 --- apps/frontend/pages/accounting.tsx | 21 +++----- libs/dm-hooks/src/useAccountingV2.ts | 5 ++ libs/dm-hooks/src/useAccountingV3.ts | 3 +- libs/dm-hooks/src/useFormattedData.ts | 2 + libs/dm-hooks/src/useFormattedDataV3.ts | 70 ++++++++++++++++++++++++- 5 files changed, 85 insertions(+), 16 deletions(-) diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index 63b3b29c..dd75355b 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -58,15 +58,12 @@ export const Accounting = () => { const { members, - balancesWithPrices, + balancesWithPrices: balancesWithPricesV2, transactionsWithPrices, transactionsWithPricesAndMembers, } = useFormattedData(memberData, balances, transactions, tokenPrices); - console.log('balancesWithPrices', balancesWithPrices); - console.log( - 'transactionsWithPricesAndMembers', - transactionsWithPricesAndMembers - ); + + console.log('balancesWithPricesV2', balancesWithPricesV2); const { balancesWithPrices: balancesWithPricesV3, @@ -78,10 +75,8 @@ export const Accounting = () => { tokenPrices: {}, members, }); - console.log( - 'transactionsWithPricesAndMembersV3', - transactionsWithPricesAndMembersV3 - ); + + console.log('balancesWithPricesV3', balancesWithPricesV3); const onExportCsv = useCallback( (type: 'transactions' | 'balances' | 'spoils') => { @@ -120,7 +115,7 @@ export const Accounting = () => { csvString = Papa.unparse(formattedTransactions); } else if (type === 'balances') { if (type === 'balances') { - const formattedBalances = balancesWithPrices.map((b) => ({ + const formattedBalances = balancesWithPricesV2.map((b) => ({ Token: b.tokenSymbol, 'Tx Explorer Link': b.tokenExplorerLink, Inflow: b.inflow.tokenValue, @@ -157,7 +152,7 @@ export const Accounting = () => { } exportToCsv(csvString, `raidguild-treasury-${type}`); }, - [balancesWithPrices, members, spoils, transactionsWithPrices] + [balancesWithPricesV2, members, spoils, transactionsWithPrices] ); return ( @@ -223,7 +218,7 @@ export const Accounting = () => { - + diff --git a/libs/dm-hooks/src/useAccountingV2.ts b/libs/dm-hooks/src/useAccountingV2.ts index 4e99af1c..4f3cad95 100644 --- a/libs/dm-hooks/src/useAccountingV2.ts +++ b/libs/dm-hooks/src/useAccountingV2.ts @@ -400,6 +400,11 @@ export const useAccountingV2 = ({ token }: { token: string }) => { const formattedData = await formatBalancesAsTransactions( data.pages[0].transactions ); + console.log('data.pages[0].balances', data.pages[0].balances); + console.log( + 'calculatedTokenBalances.getBalances()', + calculatedTokenBalances.getBalances() + ); const tokenBalances = await mapMolochTokenBalancesToTokenBalanceLineItem( data.pages[0].balances, diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index ecf5d0c2..643a59bb 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -41,8 +41,9 @@ const listTokenBalances = async ({ safeAddress }) => { const getSafeTransactionProposals = async ({ safeAddress }) => { try { + // need to fetch all transactions (default limit is 20) const res = await fetch( - `${API_URL}/safes/${safeAddress}/all-transactions/` + `${API_URL}/safes/${safeAddress}/all-transactions/?limit=1000` ); const txData = await res.json(); return { txData }; diff --git a/libs/dm-hooks/src/useFormattedData.ts b/libs/dm-hooks/src/useFormattedData.ts index 0134ac5b..3ec0e905 100644 --- a/libs/dm-hooks/src/useFormattedData.ts +++ b/libs/dm-hooks/src/useFormattedData.ts @@ -22,6 +22,8 @@ const useFormattedData = ( return _.keyBy(memberArray, (m: IMember) => m.ethAddress?.toLowerCase()); }, [memberData]); + console.log('tokenPrices', tokenPrices); + const withPrices = useCallback( (items: T[]) => items.map((t) => { diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index cc0a8c88..221675da 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -1,12 +1,44 @@ +/* eslint-disable no-param-reassign */ import { IMappedTokenPrice, IMember, ITokenBalanceLineItem, IVaultTransaction, } from '@raidguild/dm-types'; -import { formatDate, REGEX_ETH_ADDRESS } from '@raidguild/dm-utils'; +import { + formatDate, + formatUnitsAsNumber, + REGEX_ETH_ADDRESS, +} from '@raidguild/dm-utils'; import { useCallback, useMemo } from 'react'; +function calculateTokenFlows(transactions) { + console.log('transactions', transactions); + return transactions?.reduce((tokenFlows, transaction) => { + const safeAddress = '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC'; + + transaction.transfers.forEach((transfer) => { + const tokenAddress = transfer.tokenInfo?.address; + const amount = parseFloat(transfer.value); + + if (!tokenFlows[tokenAddress]) { + tokenFlows[tokenAddress] = { + inflow: 0, + outflow: 0, + }; + } + + if (transfer.to === safeAddress) { + tokenFlows[tokenAddress].inflow += amount; + } else if (transfer.from === safeAddress) { + tokenFlows[tokenAddress].outflow += amount; + } + }); + + return tokenFlows; + }, {}); +} + const useFormattedDataV3 = ({ balances, transactions, @@ -19,6 +51,15 @@ const useFormattedDataV3 = ({ members: Record; }) => { console.log('balances', balances); + console.log('tokenPrices', tokenPrices); + + const flows = useMemo( + () => calculateTokenFlows(transactions), + [transactions] + ); + + console.log('flows', flows); + const withPrices = useCallback( // fix type (items: any[]) => @@ -26,22 +67,47 @@ const useFormattedDataV3 = ({ if (!t) return t; const formattedDate = formatDate(t.date); const tokenSymbol = t.tokenSymbol?.toLowerCase(); + const balance = { + inflow: { + tokenValue: formatUnitsAsNumber( + flows[t.tokenAddress]?.inflow || BigInt(0), + t.token?.decimals + ), + }, + outflow: { + tokenValue: formatUnitsAsNumber( + flows[t.tokenAddress]?.outflow || BigInt(0), + t.token?.decimals + ), + }, + closing: { + tokenValue: formatUnitsAsNumber( + t.balance || BigInt(0), + t.token?.decimals + ), + }, + }; if ( tokenPrices[tokenSymbol] && tokenPrices[tokenSymbol][formattedDate] ) { return { ...t, + ...balance, priceConversion: tokenPrices[tokenSymbol][formattedDate], }; } if (tokenSymbol?.includes('xdai')) { return { ...t, + ...balance, priceConversion: 1, }; } - return t; + return { + ...t, + ...balance, + }; }), [tokenPrices] ); From f665b3dbf981291a89c142606701795d26caf9c4 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Wed, 5 Jun 2024 18:34:02 +0200 Subject: [PATCH 06/19] fetch all transactions --- apps/frontend/pages/accounting.tsx | 8 +------ libs/dm-hooks/src/useAccountingV3.ts | 33 +++++++++++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index dd75355b..c4040650 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -42,12 +42,6 @@ export const Accounting = () => { console.log('dataFromMolochV2', dataFromMolochV2); const { data: dataFromMolochV3 } = useAccountingV3(); console.log('dataFromMolochV3', dataFromMolochV3); - // const { - // balances: balancesV3, - // spoils: spoilsV3, - // transactions: transactionsV3, - // tokenPrices: tokenPricesV3, - // } = dataFromMolochV3; const { data: memberData } = useMemberList({ token, @@ -71,7 +65,7 @@ export const Accounting = () => { transactionsWithPricesAndMembers: transactionsWithPricesAndMembersV3, } = useFormattedDataV3({ balances: dataFromMolochV3?.tokens?.tokenBalances || [], - transactions: dataFromMolochV3?.transactions?.results || [], + transactions: dataFromMolochV3?.transactions || [], tokenPrices: {}, members, }); diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 643a59bb..d25e8fcc 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-await-in-loop */ import { NETWORK_CONFIG } from '@raidguild/escrow-utils'; import { GraphQLClient } from 'graphql-request'; import _ from 'lodash'; @@ -41,12 +42,27 @@ const listTokenBalances = async ({ safeAddress }) => { const getSafeTransactionProposals = async ({ safeAddress }) => { try { - // need to fetch all transactions (default limit is 20) - const res = await fetch( - `${API_URL}/safes/${safeAddress}/all-transactions/?limit=1000` - ); - const txData = await res.json(); - return { txData }; + const limit = 100; + let offset = 0; + let allTxData = []; + + let hasNext = true; + while (hasNext) { + const res = await fetch( + `${API_URL}/safes/${safeAddress}/all-transactions/?limit=${limit}&offset=${offset}` + ); + const txData = await res.json(); + + allTxData = [...allTxData, ...txData.results]; + + if (txData.next) { + offset += limit; + } else { + hasNext = false; + } + } + + return { txData: allTxData }; } catch (err) { return { error: `Error fetching safe transactions. Please try again. ${err}`, @@ -67,10 +83,11 @@ const useAccountingV3 = () => { const txResponse = await getSafeTransactionProposals({ safeAddress: checksum, }); + console.log('txResponse', txResponse); const tokenBalances = await listTokenBalances({ safeAddress: checksum }); const proposals = []; - console.log('txResponse?.txData?.results', txResponse?.txData?.results); - txResponse?.txData?.results?.forEach(async (tx) => { + console.log('txResponse?.txData?.results', txResponse?.txData); + txResponse?.txData?.forEach(async (tx) => { try { const proposal: { proposals: any[] } = await v3client.request(` { From fd2bbed97560b8f7a16de3d3d5c2af2b84faf446 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Mon, 10 Jun 2024 16:54:10 +0200 Subject: [PATCH 07/19] use bigint for calculating sum of in/out flows --- libs/dm-hooks/src/useFormattedDataV3.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index 221675da..ab15f27b 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -13,18 +13,17 @@ import { import { useCallback, useMemo } from 'react'; function calculateTokenFlows(transactions) { - console.log('transactions', transactions); return transactions?.reduce((tokenFlows, transaction) => { const safeAddress = '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC'; transaction.transfers.forEach((transfer) => { const tokenAddress = transfer.tokenInfo?.address; - const amount = parseFloat(transfer.value); + const amount = BigInt(transfer.value || 0); if (!tokenFlows[tokenAddress]) { tokenFlows[tokenAddress] = { - inflow: 0, - outflow: 0, + inflow: BigInt(0), + outflow: BigInt(0), }; } @@ -50,16 +49,11 @@ const useFormattedDataV3 = ({ tokenPrices: IMappedTokenPrice; members: Record; }) => { - console.log('balances', balances); - console.log('tokenPrices', tokenPrices); - const flows = useMemo( () => calculateTokenFlows(transactions), [transactions] ); - console.log('flows', flows); - const withPrices = useCallback( // fix type (items: any[]) => @@ -116,7 +110,6 @@ const useFormattedDataV3 = ({ () => withPrices(balances), [balances, withPrices] ); - console.log('balancesWithPrices', balancesWithPrices); const transactionsWithPrices = useMemo( () => From a366269fb5195ccd03376867c8157f897875ea0d Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Tue, 11 Jun 2024 16:10:30 +0200 Subject: [PATCH 08/19] transactions --- apps/frontend/pages/accounting.tsx | 14 +- libs/dm-hooks/src/useAccountingV2.ts | 5 - libs/dm-hooks/src/useFormattedData.ts | 2 - libs/dm-hooks/src/useFormattedDataV3.ts | 198 +++++++++++++++--------- 4 files changed, 137 insertions(+), 82 deletions(-) diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index c4040650..8f91d175 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -39,9 +39,7 @@ export const Accounting = () => { } = useAccountingV2({ token, }); - console.log('dataFromMolochV2', dataFromMolochV2); const { data: dataFromMolochV3 } = useAccountingV3(); - console.log('dataFromMolochV3', dataFromMolochV3); const { data: memberData } = useMemberList({ token, @@ -56,8 +54,10 @@ export const Accounting = () => { transactionsWithPrices, transactionsWithPricesAndMembers, } = useFormattedData(memberData, balances, transactions, tokenPrices); - - console.log('balancesWithPricesV2', balancesWithPricesV2); + console.log( + 'transactionsWithPricesAndMembers', + transactionsWithPricesAndMembers + ); const { balancesWithPrices: balancesWithPricesV3, @@ -69,8 +69,10 @@ export const Accounting = () => { tokenPrices: {}, members, }); - - console.log('balancesWithPricesV3', balancesWithPricesV3); + console.log( + 'transactionsWithPricesAndMembersV3', + transactionsWithPricesAndMembersV3 + ); const onExportCsv = useCallback( (type: 'transactions' | 'balances' | 'spoils') => { diff --git a/libs/dm-hooks/src/useAccountingV2.ts b/libs/dm-hooks/src/useAccountingV2.ts index 4f3cad95..4e99af1c 100644 --- a/libs/dm-hooks/src/useAccountingV2.ts +++ b/libs/dm-hooks/src/useAccountingV2.ts @@ -400,11 +400,6 @@ export const useAccountingV2 = ({ token }: { token: string }) => { const formattedData = await formatBalancesAsTransactions( data.pages[0].transactions ); - console.log('data.pages[0].balances', data.pages[0].balances); - console.log( - 'calculatedTokenBalances.getBalances()', - calculatedTokenBalances.getBalances() - ); const tokenBalances = await mapMolochTokenBalancesToTokenBalanceLineItem( data.pages[0].balances, diff --git a/libs/dm-hooks/src/useFormattedData.ts b/libs/dm-hooks/src/useFormattedData.ts index 3ec0e905..0134ac5b 100644 --- a/libs/dm-hooks/src/useFormattedData.ts +++ b/libs/dm-hooks/src/useFormattedData.ts @@ -22,8 +22,6 @@ const useFormattedData = ( return _.keyBy(memberArray, (m: IMember) => m.ethAddress?.toLowerCase()); }, [memberData]); - console.log('tokenPrices', tokenPrices); - const withPrices = useCallback( (items: T[]) => items.map((t) => { diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index ab15f27b..bf187624 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -12,30 +12,83 @@ import { } from '@raidguild/dm-utils'; import { useCallback, useMemo } from 'react'; +class CalculateTokenBalances { + calculatedTokenBalances: Record< + string, + { inflow: bigint; outflow: bigint; balance: bigint } + >; + + constructor() { + this.calculatedTokenBalances = {}; + } + + getBalance(tokenAddress: string) { + this.initTokenBalance(tokenAddress); + return this.calculatedTokenBalances[tokenAddress].balance; + } + + initTokenBalance(tokenAddress: string) { + if (!(tokenAddress in this.calculatedTokenBalances)) { + this.calculatedTokenBalances[tokenAddress] = { + inflow: BigInt(0), + outflow: BigInt(0), + balance: BigInt(0), + }; + } + } + + incrementInflow(tokenAddress: string, inValue: bigint) { + this.initTokenBalance(tokenAddress); + const tokenStats = this.calculatedTokenBalances[tokenAddress]; + this.calculatedTokenBalances[tokenAddress] = { + ...tokenStats, + inflow: tokenStats.inflow + inValue, + balance: tokenStats.balance + inValue, + }; + } + + incrementOutflow(tokenAddress: string, outValue: bigint) { + this.initTokenBalance(tokenAddress); + const tokenStats = this.calculatedTokenBalances[tokenAddress]; + this.calculatedTokenBalances[tokenAddress] = { + ...tokenStats, + outflow: tokenStats.outflow + outValue, + balance: tokenStats.balance - outValue, + }; + } + + getBalances() { + return this.calculatedTokenBalances; + } +} + function calculateTokenFlows(transactions) { - return transactions?.reduce((tokenFlows, transaction) => { + const tokenBalances = new CalculateTokenBalances(); + + // Sort transactions by executionDate in ascending order + const sortedTransactions = transactions.sort((a, b) => + a.executionDate.localeCompare(b.executionDate) + ); + + sortedTransactions.forEach((transaction) => { const safeAddress = '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC'; transaction.transfers.forEach((transfer) => { - const tokenAddress = transfer.tokenInfo?.address; + const tokenAddress = + transfer.type === 'ETHER_TRANSFER' + ? 'ETH' + : transfer.tokenInfo?.address; const amount = BigInt(transfer.value || 0); - if (!tokenFlows[tokenAddress]) { - tokenFlows[tokenAddress] = { - inflow: BigInt(0), - outflow: BigInt(0), - }; - } - if (transfer.to === safeAddress) { - tokenFlows[tokenAddress].inflow += amount; + tokenBalances.incrementInflow(tokenAddress, amount); } else if (transfer.from === safeAddress) { - tokenFlows[tokenAddress].outflow += amount; + tokenBalances.incrementOutflow(tokenAddress, amount); } }); + }); - return tokenFlows; - }, {}); + return tokenBalances.getBalances(); } const useFormattedDataV3 = ({ @@ -55,7 +108,6 @@ const useFormattedDataV3 = ({ ); const withPrices = useCallback( - // fix type (items: any[]) => (items || []).map((t) => { if (!t) return t; @@ -76,7 +128,7 @@ const useFormattedDataV3 = ({ }, closing: { tokenValue: formatUnitsAsNumber( - t.balance || BigInt(0), + flows[t.tokenAddress]?.balance || BigInt(0), t.token?.decimals ), }, @@ -103,7 +155,7 @@ const useFormattedDataV3 = ({ ...balance, }; }), - [tokenPrices] + [tokenPrices, flows] ); const balancesWithPrices = useMemo( @@ -111,42 +163,67 @@ const useFormattedDataV3 = ({ [balances, withPrices] ); - const transactionsWithPrices = useMemo( - () => - withPrices(transactions).map((t) => { - const transfer = t.transfers[0]; - const tokenSymbol = transfer?.tokenInfo?.symbol || ''; - const tokenDecimals = transfer?.tokenInfo?.decimals || 0; - const tokenAddress = transfer?.tokenAddress || ''; + const transactionsWithPrices = useMemo(() => { + const tokenBalances = new CalculateTokenBalances(); - const inAmount = - transfer?.type === 'DEPOSIT' ? parseFloat(transfer.value) : 0; - const outAmount = - transfer?.type === 'WITHDRAW' ? parseFloat(transfer.value) : 0; + return withPrices(transactions) + .sort((a, b) => a.executionDate.localeCompare(b.executionDate)) + .flatMap((t) => + t.transfers.map((transfer) => { + const tokenSymbol = + transfer?.type === 'ETHER_TRANSFER' + ? 'ETH' + : transfer?.tokenInfo?.symbol || ''; + const tokenDecimals = + transfer?.type === 'ETHER_TRANSFER' + ? 18 + : transfer?.tokenInfo?.decimals || 0; + const tokenAddress = + transfer?.type === 'ETHER_TRANSFER' + ? 'ETH' + : transfer?.tokenAddress || ''; - return { - balance: 0, // Placeholder, needs to be calculated separately - counterparty: transfer?.to, - date: new Date(t.executionDate), - elapsedDays: undefined, // Placeholder, needs to be calculated separately - in: inAmount, - net: inAmount - outAmount, - out: outAmount, - proposalApplicant: '', // Placeholder - proposalId: '', // Placeholder - proposalLink: '', // Placeholder - proposalLoot: undefined, // Placeholder - proposalShares: undefined, // Placeholder - proposalTitle: '', // Placeholder - tokenAddress, - tokenDecimals, - tokenSymbol, - txExplorerLink: `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`, - type: t.txType, - }; - }), - [transactions, withPrices] - ); + const inAmount = + transfer?.to === '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC' && + transfer?.value !== null + ? BigInt(transfer.value) + : BigInt(0); + const outAmount = + transfer?.from === '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC' && + transfer?.value !== null + ? BigInt(transfer.value) + : BigInt(0); + + tokenBalances.incrementInflow(tokenAddress, inAmount); + tokenBalances.incrementOutflow(tokenAddress, outAmount); + + return { + balance: formatUnitsAsNumber( + tokenBalances.getBalance(tokenAddress), + tokenDecimals + ), + counterparty: transfer?.to, + date: new Date(t.executionDate), + elapsedDays: undefined, + in: formatUnitsAsNumber(inAmount, tokenDecimals), + net: formatUnitsAsNumber(inAmount - outAmount, tokenDecimals), + out: formatUnitsAsNumber(outAmount, tokenDecimals), + proposalApplicant: '', + proposalId: '', + proposalLink: '', + proposalLoot: undefined, + proposalShares: undefined, + proposalTitle: '', + tokenAddress, + tokenDecimals, + tokenSymbol, + txExplorerLink: `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`, + type: t.txType, + }; + }) + ) + .reverse(); + }, [transactions, withPrices]); const transactionsWithPricesAndMembers = useMemo( () => @@ -158,27 +235,10 @@ const useFormattedDataV3 = ({ : undefined; return { - balance: t.balance, - counterparty: t.counterparty, - date: t.date, - elapsedDays: t.elapsedDays, - in: t.in, - memberEnsName: m?.ensName, + ...t, memberLink, + memberEnsName: m?.ensName, memberName: m?.name, - net: t.net, - out: t.out, - proposalApplicant: t.proposalApplicant, - proposalId: t.proposalId, - proposalLink: t.proposalLink, - proposalLoot: t.proposalLoot, - proposalShares: t.proposalShares, - proposalTitle: t.proposalTitle, - tokenAddress: t.tokenAddress, - tokenDecimals: t.tokenDecimals, - tokenSymbol: t.tokenSymbol, - txExplorerLink: t.txExplorerLink, - type: t.type, }; }), [transactionsWithPrices, members] From d81cf4f730acfd64b5bd1c4866c61016cb10c525 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Wed, 12 Jun 2024 12:25:16 +0200 Subject: [PATCH 09/19] cache results --- libs/dm-hooks/src/useAccountingV3.ts | 123 ++++++++++++++------------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index d25e8fcc..3fecd927 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -1,8 +1,8 @@ /* eslint-disable no-await-in-loop */ import { NETWORK_CONFIG } from '@raidguild/escrow-utils'; +import { useQueries, useQuery } from '@tanstack/react-query'; import { GraphQLClient } from 'graphql-request'; import _ from 'lodash'; -import { useEffect, useState } from 'react'; import { getAddress } from 'viem'; const graphUrl = (chainId: number = 4) => @@ -18,7 +18,6 @@ export const v3client = new GraphQLClient( const API_URL = 'https://safe-transaction-gnosis-chain.safe.global/api/v1'; const transformTokenBalances = (tokenBalanceRes, safeAddress: string) => { - console.log('tokenBalanceRes', tokenBalanceRes); const fiatTotal = tokenBalanceRes.reduce( (sum: number, balance) => sum + Number(balance.balance), 0 @@ -71,74 +70,76 @@ const getSafeTransactionProposals = async ({ safeAddress }) => { }; const useAccountingV3 = () => { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [members, setMembers] = useState(null); - - useEffect(() => { - const fetchData = async () => { - const checksum = getAddress('0x181ebdb03cb4b54f4020622f1b0eacd67a8c63ac'); - console.log('checksum', checksum); - const txResponse = await getSafeTransactionProposals({ - safeAddress: checksum, - }); - console.log('txResponse', txResponse); - const tokenBalances = await listTokenBalances({ safeAddress: checksum }); - const proposals = []; - console.log('txResponse?.txData?.results', txResponse?.txData); - txResponse?.txData?.forEach(async (tx) => { - try { - const proposal: { proposals: any[] } = await v3client.request(` + const checksum = getAddress('0x181ebdb03cb4b54f4020622f1b0eacd67a8c63ac'); + + const { data: tokenBalances, error: tokenBalancesError } = useQuery( + ['tokenBalances', checksum], + () => listTokenBalances({ safeAddress: checksum }) + ); + + const { data: txResponse, error: txResponseError } = useQuery( + ['transactions', checksum], + () => getSafeTransactionProposals({ safeAddress: checksum }) + ); + + const proposalQueries = + txResponse?.txData?.map((tx) => ({ + queryKey: ['proposal', tx.transactionHash], + queryFn: async () => { + const proposal: { proposals: any[] } = await v3client.request(` + { + proposals(where: { processTxHash: "${tx.transactionHash}"}) { + id + createdAt + createdBy + proposedBy + processTxHash + proposalType + description + } + } + `); + return proposal?.proposals[0]; + }, + })) || []; + + const proposalsInfo = useQueries({ queries: proposalQueries }); + + const memberQueries = + proposalsInfo + ?.filter((proposalQuery) => proposalQuery.data) + ?.map((proposalQuery) => ({ + queryKey: ['member', proposalQuery.data?.proposedBy], + queryFn: async () => { + const m: { members: any[] } = await v3client.request(` { - proposals(where: { processTxHash: "${tx.transactionHash}"}) { + members(where: { memberAddress: "${proposalQuery.data?.proposedBy}" }) { id createdAt - createdBy - proposedBy - processTxHash - proposalType - description + memberAddress + shares + loot + delegatingTo + delegateShares } } `); - const m: { members: any[] } = await v3client.request(` - members(where: { memberAddress: ${proposal?.proposals[0].proposedBy} }) { - id - createdAt - memberAddress - shares - loot - delegatingTo - delegateShares - } - `); - console.log('m', m); - setMembers(m.members[0]); - proposals.push({ proposal: proposal?.proposals[0] }); - } catch { - console.log('error'); - } - }); - if (tokenBalances.error) { - setError(tokenBalances.error); - } else { - console.log(tokenBalances.data); - setData({ - tokens: tokenBalances.data, - transactions: txResponse.txData, - proposalsInfo: proposals, - members, - }); - } + return m.members[0]; + }, + })) || []; - setLoading(false); - }; + const members = useQueries({ queries: memberQueries }); + + const error = tokenBalancesError || txResponseError; - fetchData(); - }, []); + const data = { + tokens: tokenBalances?.data, + transactions: txResponse?.txData, + proposalsInfo: proposalsInfo?.map((query) => query.data), + members: members?.map((query) => query.data), + }; - return { data, loading, error }; + return { data, error }; }; export default useAccountingV3; From b6afd7606e941f699044bc6d9473f7bbf19747be Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Thu, 13 Jun 2024 10:29:59 +0200 Subject: [PATCH 10/19] display member & proposal & proposal link --- apps/frontend/pages/accounting.tsx | 9 ++---- libs/dm-hooks/src/useAccountingV3.ts | 19 +++++++++++- libs/dm-hooks/src/useFormattedData.ts | 1 - libs/dm-hooks/src/useFormattedDataV3.ts | 41 +++++++++++++++++++------ 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index 8f91d175..54c232ff 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -8,7 +8,6 @@ import { TabPanel, TabPanels, Tabs, - Tr, } from '@raidguild/design-system'; import { useAccountingV3, @@ -54,11 +53,8 @@ export const Accounting = () => { transactionsWithPrices, transactionsWithPricesAndMembers, } = useFormattedData(memberData, balances, transactions, tokenPrices); - console.log( - 'transactionsWithPricesAndMembers', - transactionsWithPricesAndMembers - ); + console.log('dataFromMolochV3', dataFromMolochV3); const { balancesWithPrices: balancesWithPricesV3, transactionsWithPrices: transactionsWithPricesV3, @@ -67,7 +63,8 @@ export const Accounting = () => { balances: dataFromMolochV3?.tokens?.tokenBalances || [], transactions: dataFromMolochV3?.transactions || [], tokenPrices: {}, - members, + proposalsInfo: dataFromMolochV3?.proposalsInfo || {}, + memberData, }); console.log( 'transactionsWithPricesAndMembersV3', diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 3fecd927..1c2ab949 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -96,6 +96,8 @@ const useAccountingV3 = () => { processTxHash proposalType description + title + txHash } } `); @@ -104,6 +106,7 @@ const useAccountingV3 = () => { })) || []; const proposalsInfo = useQueries({ queries: proposalQueries }); + console.log('proposalsInfo', proposalsInfo); const memberQueries = proposalsInfo @@ -132,10 +135,24 @@ const useAccountingV3 = () => { const error = tokenBalancesError || txResponseError; + const proposalsInfoData = proposalsInfo + ?.filter((query) => query.data !== undefined) + ?.map((query) => query.data); + console.log('proposalsInfoData', proposalsInfoData); + + const transformProposals = (proposalsInfoData || []).reduce( + (acc, proposal) => { + const { txHash, ...rest } = proposal; + acc[txHash] = rest; + return acc; + }, + {} + ); + const data = { tokens: tokenBalances?.data, transactions: txResponse?.txData, - proposalsInfo: proposalsInfo?.map((query) => query.data), + proposalsInfo: transformProposals, members: members?.map((query) => query.data), }; diff --git a/libs/dm-hooks/src/useFormattedData.ts b/libs/dm-hooks/src/useFormattedData.ts index 0134ac5b..4fd93780 100644 --- a/libs/dm-hooks/src/useFormattedData.ts +++ b/libs/dm-hooks/src/useFormattedData.ts @@ -56,7 +56,6 @@ const useFormattedData = ( () => withPrices(transactions), [transactions, withPrices] ); - console.log('transactionsWithPrices', transactionsWithPrices); const transactionsWithPricesAndMembers = useMemo( () => diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index bf187624..8473f167 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -8,8 +8,11 @@ import { import { formatDate, formatUnitsAsNumber, + GUILD_GNOSIS_DAO_ADDRESS, REGEX_ETH_ADDRESS, } from '@raidguild/dm-utils'; +import { InfiniteData } from '@tanstack/react-query'; +import _ from 'lodash'; import { useCallback, useMemo } from 'react'; class CalculateTokenBalances { @@ -95,18 +98,28 @@ const useFormattedDataV3 = ({ balances, transactions, tokenPrices, - members, + memberData, + proposalsInfo, }: { balances: ITokenBalanceLineItem[]; transactions: IVaultTransaction[]; tokenPrices: IMappedTokenPrice; - members: Record; + memberData: InfiniteData; + proposalsInfo: any; }) => { const flows = useMemo( () => calculateTokenFlows(transactions), [transactions] ); + const members = useMemo(() => { + const memberArray = _.flatten( + _.get(memberData, 'pages') + ) as unknown as IMember[]; + return _.keyBy(memberArray, (m: IMember) => m.ethAddress?.toLowerCase()); + }, [memberData]); + console.log('members', members); + const withPrices = useCallback( (items: any[]) => (items || []).map((t) => { @@ -162,6 +175,7 @@ const useFormattedDataV3 = ({ () => withPrices(balances), [balances, withPrices] ); + console.log('proposalsInfo', proposalsInfo); const transactionsWithPrices = useMemo(() => { const tokenBalances = new CalculateTokenBalances(); @@ -197,27 +211,36 @@ const useFormattedDataV3 = ({ tokenBalances.incrementInflow(tokenAddress, inAmount); tokenBalances.incrementOutflow(tokenAddress, outAmount); + const proposal = proposalsInfo[t.txHash]; + const txExplorerLink = `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`; + const proposalLink = proposal + ? `https://admin.daohaus.club/#/molochV3/0x64/${proposal.id.replace( + /-/g, + '/' + )}` + : ''; + return { balance: formatUnitsAsNumber( tokenBalances.getBalance(tokenAddress), tokenDecimals ), - counterparty: transfer?.to, + counterparty: transfer?.to, // gotta check date: new Date(t.executionDate), elapsedDays: undefined, in: formatUnitsAsNumber(inAmount, tokenDecimals), net: formatUnitsAsNumber(inAmount - outAmount, tokenDecimals), out: formatUnitsAsNumber(outAmount, tokenDecimals), - proposalApplicant: '', - proposalId: '', - proposalLink: '', + proposalApplicant: proposal?.proposedBy, + proposalId: proposal?.id, + txExplorerLink, + proposalLink, proposalLoot: undefined, proposalShares: undefined, - proposalTitle: '', + proposalTitle: proposal?.title, tokenAddress, tokenDecimals, tokenSymbol, - txExplorerLink: `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`, type: t.txType, }; }) @@ -228,7 +251,7 @@ const useFormattedDataV3 = ({ const transactionsWithPricesAndMembers = useMemo( () => transactionsWithPrices.map((t) => { - const ethAddress = t.proposalApplicant?.toLowerCase(); + const ethAddress = t.counterparty?.toLowerCase(); const m = members[ethAddress]; const memberLink = m?.ethAddress?.match(REGEX_ETH_ADDRESS) ? `/members/${ethAddress}` From 690c0e48de2af0736c8a19fb422876780616b9ba Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Thu, 13 Jun 2024 16:17:52 +0200 Subject: [PATCH 11/19] fix proposals --- libs/dm-hooks/src/useAccountingV3.ts | 31 +++++++++++++++---------- libs/dm-hooks/src/useFormattedDataV3.ts | 10 ++++---- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 1c2ab949..241519ae 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -82,13 +82,16 @@ const useAccountingV3 = () => { () => getSafeTransactionProposals({ safeAddress: checksum }) ); + console.log('txResponse?.txData', txResponse?.txData); const proposalQueries = - txResponse?.txData?.map((tx) => ({ - queryKey: ['proposal', tx.transactionHash], - queryFn: async () => { - const proposal: { proposals: any[] } = await v3client.request(` + txResponse?.txData?.map((tx) => { + const txHash = tx.transactionHash || tx.txHash; + return { + queryKey: ['proposal', txHash], + queryFn: async () => { + const proposal: { proposals: any[] } = await v3client.request(` { - proposals(where: { processTxHash: "${tx.transactionHash}"}) { + proposals(where: { processTxHash: "${txHash}"}) { id createdAt createdBy @@ -101,12 +104,17 @@ const useAccountingV3 = () => { } } `); - return proposal?.proposals[0]; - }, - })) || []; + return proposal?.proposals[0]; + }, + }; + }) || []; const proposalsInfo = useQueries({ queries: proposalQueries }); - console.log('proposalsInfo', proposalsInfo); + + console.log( + 'proposalsInfo', + proposalsInfo.map((query) => query.data) + ); const memberQueries = proposalsInfo @@ -138,12 +146,11 @@ const useAccountingV3 = () => { const proposalsInfoData = proposalsInfo ?.filter((query) => query.data !== undefined) ?.map((query) => query.data); - console.log('proposalsInfoData', proposalsInfoData); const transformProposals = (proposalsInfoData || []).reduce( (acc, proposal) => { - const { txHash, ...rest } = proposal; - acc[txHash] = rest; + const { processTxHash, ...rest } = proposal; + acc[processTxHash] = rest; return acc; }, {} diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index 8473f167..10185f8b 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -175,7 +175,6 @@ const useFormattedDataV3 = ({ () => withPrices(balances), [balances, withPrices] ); - console.log('proposalsInfo', proposalsInfo); const transactionsWithPrices = useMemo(() => { const tokenBalances = new CalculateTokenBalances(); @@ -183,10 +182,10 @@ const useFormattedDataV3 = ({ return withPrices(transactions) .sort((a, b) => a.executionDate.localeCompare(b.executionDate)) .flatMap((t) => - t.transfers.map((transfer) => { + t.transfers?.map((transfer) => { const tokenSymbol = transfer?.type === 'ETHER_TRANSFER' - ? 'ETH' + ? 'XDAI' : transfer?.tokenInfo?.symbol || ''; const tokenDecimals = transfer?.type === 'ETHER_TRANSFER' @@ -194,7 +193,7 @@ const useFormattedDataV3 = ({ : transfer?.tokenInfo?.decimals || 0; const tokenAddress = transfer?.type === 'ETHER_TRANSFER' - ? 'ETH' + ? 'XDAI' : transfer?.tokenAddress || ''; const inAmount = @@ -211,7 +210,8 @@ const useFormattedDataV3 = ({ tokenBalances.incrementInflow(tokenAddress, inAmount); tokenBalances.incrementOutflow(tokenAddress, outAmount); - const proposal = proposalsInfo[t.txHash]; + const txHash = t.transactionHash || t.txHash; + const proposal = proposalsInfo[txHash]; const txExplorerLink = `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`; const proposalLink = proposal ? `https://admin.daohaus.club/#/molochV3/0x64/${proposal.id.replace( From 704c1662bad180c002742044beca91d034569c57 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Fri, 14 Jun 2024 16:56:04 +0200 Subject: [PATCH 12/19] fix type --- libs/dm-hooks/src/useFormattedDataV3.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index 10185f8b..5718589c 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -8,7 +8,6 @@ import { import { formatDate, formatUnitsAsNumber, - GUILD_GNOSIS_DAO_ADDRESS, REGEX_ETH_ADDRESS, } from '@raidguild/dm-utils'; import { InfiniteData } from '@tanstack/react-query'; @@ -118,7 +117,6 @@ const useFormattedDataV3 = ({ ) as unknown as IMember[]; return _.keyBy(memberArray, (m: IMember) => m.ethAddress?.toLowerCase()); }, [memberData]); - console.log('members', members); const withPrices = useCallback( (items: any[]) => @@ -220,6 +218,14 @@ const useFormattedDataV3 = ({ )}` : ''; + let txType = 'proposal'; + if (!proposal) { + txType = + transfer.to === '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC' + ? 'spoils' + : 'ragequit'; + } + return { balance: formatUnitsAsNumber( tokenBalances.getBalance(tokenAddress), @@ -241,12 +247,12 @@ const useFormattedDataV3 = ({ tokenAddress, tokenDecimals, tokenSymbol, - type: t.txType, + type: txType, }; }) ) .reverse(); - }, [transactions, withPrices]); + }, [transactions, withPrices, proposalsInfo]); const transactionsWithPricesAndMembers = useMemo( () => From c1d31f77527afd50de38299de4e610874f2bc38f Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Fri, 14 Jun 2024 19:52:25 +0200 Subject: [PATCH 13/19] cleanup --- .../frontend/components/TransactionsTable.tsx | 4 +- apps/frontend/pages/accounting.tsx | 7 +- .../src/getTokenBalancesWithPrices.ts | 348 ------------------ libs/dm-hooks/src/useAccountingV3.ts | 121 +++--- libs/dm-hooks/src/useFormattedDataV3.ts | 181 ++++----- libs/dm-types/src/accounting.ts | 55 +++ libs/dm-utils/src/constants.ts | 2 + 7 files changed, 193 insertions(+), 525 deletions(-) delete mode 100644 libs/dm-hooks/src/getTokenBalancesWithPrices.ts diff --git a/apps/frontend/components/TransactionsTable.tsx b/apps/frontend/components/TransactionsTable.tsx index 3d32fc74..a011b62b 100644 --- a/apps/frontend/components/TransactionsTable.tsx +++ b/apps/frontend/components/TransactionsTable.tsx @@ -1,5 +1,5 @@ import { Link, Tooltip } from '@raidguild/design-system'; -import { IVaultTransaction } from '@raidguild/dm-types'; +import { IVaultTransaction, IVaultTransactionV2 } from '@raidguild/dm-types'; import { formatNumber, minMaxDateFilter, @@ -14,7 +14,7 @@ import DataTable from './DataTable'; import TokenWithUsdValue from './TokenWithUsdValue'; interface TransactionsTableProps { - data: IVaultTransaction[]; + data: IVaultTransaction[] | IVaultTransactionV2[]; } const columnHelper = createColumnHelper(); diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index 54c232ff..8e41c109 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -54,10 +54,9 @@ export const Accounting = () => { transactionsWithPricesAndMembers, } = useFormattedData(memberData, balances, transactions, tokenPrices); - console.log('dataFromMolochV3', dataFromMolochV3); const { balancesWithPrices: balancesWithPricesV3, - transactionsWithPrices: transactionsWithPricesV3, + transactionsWithPrices: transactionsWithPricesV3, // used for export transactionsWithPricesAndMembers: transactionsWithPricesAndMembersV3, } = useFormattedDataV3({ balances: dataFromMolochV3?.tokens?.tokenBalances || [], @@ -66,10 +65,6 @@ export const Accounting = () => { proposalsInfo: dataFromMolochV3?.proposalsInfo || {}, memberData, }); - console.log( - 'transactionsWithPricesAndMembersV3', - transactionsWithPricesAndMembersV3 - ); const onExportCsv = useCallback( (type: 'transactions' | 'balances' | 'spoils') => { diff --git a/libs/dm-hooks/src/getTokenBalancesWithPrices.ts b/libs/dm-hooks/src/getTokenBalancesWithPrices.ts deleted file mode 100644 index 3eac072d..00000000 --- a/libs/dm-hooks/src/getTokenBalancesWithPrices.ts +++ /dev/null @@ -1,348 +0,0 @@ -// import { -// camelize, -// formatDate, -// formatUnitsAsNumber, -// GUILD_GNOSIS_DAO_ADDRESS, -// } from '@raidguild/dm-utils'; -// import { useState, useEffect } from 'react'; -// import { -// IAccountingRaid, -// ICalculatedTokenBalances, -// IMappedTokenPrice, -// IMolochStatsBalance, -// ISmartEscrow, -// ISmartEscrowWithdrawal, -// ISpoils, -// ITokenBalance, -// ITokenBalanceLineItem, -// ITokenPrice, -// IVaultTransaction, -// } from '@raidguild/dm-types'; -// import _ from 'lodash'; - -// class CalculateTokenBalances { -// calculatedTokenBalances: ICalculatedTokenBalances; - -// constructor() { -// this.calculatedTokenBalances = {}; -// } - -// getBalance(tokenAddress: string) { -// this.initTokenBalance(tokenAddress); -// return this.calculatedTokenBalances[tokenAddress].balance; -// } - -// initTokenBalance(tokenAddress: string) { -// if (!(tokenAddress in this.calculatedTokenBalances)) { -// this.calculatedTokenBalances[tokenAddress] = { -// out: BigInt(0), -// in: BigInt(0), -// balance: BigInt(0), -// }; -// } -// } - -// incrementInflow(tokenAddress: string, inValue: bigint) { -// this.initTokenBalance(tokenAddress); -// const tokenStats = this.calculatedTokenBalances[tokenAddress]; -// this.calculatedTokenBalances[tokenAddress] = { -// ...tokenStats, -// in: tokenStats.in + inValue, -// balance: tokenStats.balance + inValue, -// }; -// } - -// incrementOutflow(tokenAddress: string, outValue: bigint) { -// this.initTokenBalance(tokenAddress); -// const tokenStats = this.calculatedTokenBalances[tokenAddress]; -// this.calculatedTokenBalances[tokenAddress] = { -// ...tokenStats, -// out: tokenStats.out + outValue, -// balance: tokenStats.balance - outValue, -// }; -// } - -// getBalances() { -// return this.calculatedTokenBalances; -// } -// } -// const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; - -// // used to store all the inflow and outflow of each token when iterating over the list of moloch stats -// const calculatedTokenBalances = new CalculateTokenBalances(); -// const formatBalancesAsTransactions = async ( -// balances: Array -// ) => { -// try { -// const mapMolochStatsToTreasuryTransaction = async ( -// molochStatsBalances: Array -// ): Promise> => { -// const treasuryTransactions = await Promise.all( -// molochStatsBalances.map(async (molochStatBalance) => { -// /** -// * molochStatBalance.amount is incorrect because ragequit does not return the correct amount -// * so instead, we track the previous balance of the token in the calculatedTokenBalances class state -// * and subtract from current balance to get the amount. -// */ -// let tokenValue = -// calculatedTokenBalances.getBalance(molochStatBalance.tokenAddress) - -// BigInt(molochStatBalance.balance); - -// // Ensure the value is absolute -// tokenValue = tokenValue >= BigInt(0) ? tokenValue : -tokenValue; - -// const tokenFormattedValue = formatUnitsAsNumber( -// tokenValue, -// molochStatBalance.tokenDecimals -// ); - -// const balances = (() => { -// if ( -// molochStatBalance.payment === false && -// molochStatBalance.tribute === false -// ) { -// const balance = formatUnitsAsNumber( -// calculatedTokenBalances.getBalance( -// molochStatBalance.tokenAddress -// ), -// molochStatBalance.tokenDecimals -// ); - -// return { -// in: 0, -// out: 0, -// net: 0, -// balance, -// }; -// } -// if ( -// molochStatBalance.payment === false && -// molochStatBalance.tribute === true -// ) { -// calculatedTokenBalances.incrementInflow( -// molochStatBalance.tokenAddress, -// tokenValue -// ); - -// const balance = formatUnitsAsNumber( -// calculatedTokenBalances.getBalance( -// molochStatBalance.tokenAddress -// ), -// molochStatBalance.tokenDecimals -// ); - -// return { -// in: tokenFormattedValue, -// out: 0, -// net: tokenFormattedValue, -// balance, -// }; -// } - -// if ( -// molochStatBalance.payment === true && -// molochStatBalance.tribute === false -// ) { -// calculatedTokenBalances.incrementOutflow( -// molochStatBalance.tokenAddress, -// tokenValue -// ); - -// const balance = formatUnitsAsNumber( -// calculatedTokenBalances.getBalance( -// molochStatBalance.tokenAddress -// ), -// molochStatBalance.tokenDecimals -// ); - -// return { -// in: 0, -// out: tokenFormattedValue, -// net: -tokenFormattedValue, -// balance, -// }; -// } - -// const balance = formatUnitsAsNumber( -// calculatedTokenBalances.getBalance( -// molochStatBalance.tokenAddress -// ), -// molochStatBalance.tokenDecimals -// ); - -// return { -// in: 0, -// out: 0, -// net: 0, -// balance, -// }; -// })(); - -// const proposalTitle = (() => { -// try { -// return JSON.parse( -// molochStatBalance.proposalDetail?.details ?? '{}' -// ).title; -// } catch (error) { -// return ''; -// } -// })(); - -// const txExplorerLink = `https://blockscout.com/xdai/mainnet/tx/${molochStatBalance.transactionHash}`; -// const proposalLink = molochStatBalance.proposalDetail -// ? `https://app.daohaus.club/dao/0x64/${GUILD_GNOSIS_DAO_ADDRESS}/proposals/${molochStatBalance.proposalDetail.proposalId}` -// : ''; -// const epochTimeAtIngressMs = -// Number(molochStatBalance.timestamp) * 1000; -// const date = new Date(epochTimeAtIngressMs); -// const elapsedDays = -// balances.net > 0 -// ? Math.floor( -// (Date.now() - epochTimeAtIngressMs) / MILLISECONDS_PER_DAY -// ) -// : undefined; - -// const proposal = molochStatBalance.proposalDetail; - -// return { -// date, -// elapsedDays, -// type: _.startCase(molochStatBalance.action), -// tokenSymbol: molochStatBalance.tokenSymbol, -// tokenDecimals: Number(molochStatBalance.tokenDecimals), -// tokenAddress: molochStatBalance.tokenAddress, -// txExplorerLink, -// counterparty: molochStatBalance.counterpartyAddress, -// proposalId: proposal?.proposalId ?? '', -// proposalLink, -// proposalShares: proposal?.sharesRequested -// ? BigInt(proposal.sharesRequested) -// : undefined, -// proposalLoot: proposal?.lootRequested -// ? BigInt(proposal.lootRequested) -// : undefined, -// proposalApplicant: proposal?.applicant ?? '', -// proposalTitle, -// ...balances, -// }; -// }) -// ); - -// return treasuryTransactions; -// }; - -// const treasuryTransactions = await mapMolochStatsToTreasuryTransaction( -// balances -// ); - -// return { -// transactions: _.orderBy( -// treasuryTransactions, -// 'date', -// 'desc' -// ) as Array, -// }; -// } catch (error) { -// return { -// error: { -// message: (error as Error).message, -// }, -// }; -// } -// }; - -// const mapMolochTokenBalancesToTokenBalanceLineItem = async ( -// molochTokenBalances: ITokenBalance[], -// calculatedTokenBalances: ICalculatedTokenBalances -// ): Promise => { -// const tokenBalanceLineItems = await Promise.all( -// molochTokenBalances.map(async (molochTokenBalance) => { -// const tokenExplorerLink = `https://blockscout.com/xdai/mainnet/address/${molochTokenBalance.token.tokenAddress}`; -// const calculatedTokenBalance = -// calculatedTokenBalances[molochTokenBalance.token.tokenAddress]; - -// return { -// ...molochTokenBalance, -// date: new Date(), -// tokenSymbol: molochTokenBalance.token.symbol, -// tokenExplorerLink, -// inflow: { -// tokenValue: formatUnitsAsNumber( -// calculatedTokenBalance?.in || BigInt(0), -// molochTokenBalance.token.decimals -// ), -// }, -// outflow: { -// tokenValue: formatUnitsAsNumber( -// calculatedTokenBalance?.out || BigInt(0), -// molochTokenBalance.token.decimals -// ), -// }, -// closing: { -// tokenValue: formatUnitsAsNumber( -// molochTokenBalance.tokenBalance, -// molochTokenBalance.token.decimals -// ), -// }, -// }; -// }) -// ); - -// // TODO fix type -// return tokenBalanceLineItems as unknown as any; -// }; - -// export const getTokenBalancesWithPrices = async (safeAddress: string) => { -// const [transactions, setTransactions] = useState>( -// [] -// ); -// const [balances, setBalances] = useState>([]); -// const [tokenPrices, setTokenPrices] = useState({}); - -// // TODO use onSuccess/onError instead of useEffect -// useEffect(() => { -// (async () => { -// if (status === 'success') { -// const formattedData = await formatBalancesAsTransactions( -// data.pages[0].transactions -// ); -// const tokenBalances = -// await mapMolochTokenBalancesToTokenBalanceLineItem( -// data.pages[0].balances, -// calculatedTokenBalances.getBalances() -// ); -// const spoils = await formatSpoils( -// data.pages[0].smartEscrows, -// data.pages[0].raids -// ); -// const { historicalPrices, currentPrices } = data.pages[0]; -// // using any because not sure how to type this -// const mappedPrices: { [key: string]: any } = {}; -// historicalPrices.forEach((price) => { -// if (!mappedPrices[price.symbol]) { -// mappedPrices[price.symbol] = {}; -// mappedPrices[price.symbol][price.date] = price.priceUsd; -// } else { -// mappedPrices[price.symbol][price.date] = price.priceUsd; -// } -// }); -// currentPrices.forEach((price) => { -// const date = new Date(); -// if (!mappedPrices[price.symbol]) { -// mappedPrices[price.symbol] = {}; -// mappedPrices[price.symbol][formatDate(date)] = price.priceUsd; -// } else { -// mappedPrices[price.symbol][formatDate(date)] = price.priceUsd; -// } -// }); -// setTransactions(formattedData.transactions || []); -// setBalances(tokenBalances); -// setSpoils(spoils); -// setTokenPrices(mappedPrices); -// } else if (status === 'error') { -// // eslint-disable-next-line no-console -// console.error('accounting data fetching failed with: ', status); -// } -// })(); -// }, [data, status]); -// }; \ No newline at end of file diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 241519ae..0a07e55a 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -1,4 +1,5 @@ /* eslint-disable no-await-in-loop */ +import { GNOSIS_SAFE_ADDRESS } from '@raidguild/dm-utils'; import { NETWORK_CONFIG } from '@raidguild/escrow-utils'; import { useQueries, useQuery } from '@tanstack/react-query'; import { GraphQLClient } from 'graphql-request'; @@ -6,7 +7,10 @@ import _ from 'lodash'; import { getAddress } from 'viem'; const graphUrl = (chainId: number = 4) => - `https://api.thegraph.com/subgraphs/name/${NETWORK_CONFIG[chainId].SUBGRAPH}`; + `https://api.thegraph.com/subgraphs/name/${_.get(NETWORK_CONFIG, [ + chainId, + 'SUBGRAPH', + ])}`; export const SUPPORTED_NETWORKS = _.map(_.keys(NETWORK_CONFIG), _.toNumber); @@ -18,14 +22,15 @@ export const v3client = new GraphQLClient( const API_URL = 'https://safe-transaction-gnosis-chain.safe.global/api/v1'; const transformTokenBalances = (tokenBalanceRes, safeAddress: string) => { - const fiatTotal = tokenBalanceRes.reduce( + const fiatTotal = _.reduce( + tokenBalanceRes, (sum: number, balance) => sum + Number(balance.balance), 0 ); return { safeAddress, tokenBalances: tokenBalanceRes, fiatTotal }; }; -const listTokenBalances = async ({ safeAddress }) => { +const listTokenBalances = async ({ safeAddress }: { safeAddress: string }) => { try { const res = await fetch(`${API_URL}/safes/${safeAddress}/balances/`); const data = await res.json(); @@ -39,7 +44,11 @@ const listTokenBalances = async ({ safeAddress }) => { } }; -const getSafeTransactionProposals = async ({ safeAddress }) => { +const getSafeTransactionProposals = async ({ + safeAddress, +}: { + safeAddress: string; +}) => { try { const limit = 100; let offset = 0; @@ -51,14 +60,10 @@ const getSafeTransactionProposals = async ({ safeAddress }) => { `${API_URL}/safes/${safeAddress}/all-transactions/?limit=${limit}&offset=${offset}` ); const txData = await res.json(); + allTxData = _.concat(allTxData, txData.results); - allTxData = [...allTxData, ...txData.results]; - - if (txData.next) { - offset += limit; - } else { - hasNext = false; - } + hasNext = !!txData.next; + if (hasNext) offset += limit; } return { txData: allTxData }; @@ -70,7 +75,7 @@ const getSafeTransactionProposals = async ({ safeAddress }) => { }; const useAccountingV3 = () => { - const checksum = getAddress('0x181ebdb03cb4b54f4020622f1b0eacd67a8c63ac'); + const checksum = getAddress(GNOSIS_SAFE_ADDRESS); const { data: tokenBalances, error: tokenBalancesError } = useQuery( ['tokenBalances', checksum], @@ -82,85 +87,63 @@ const useAccountingV3 = () => { () => getSafeTransactionProposals({ safeAddress: checksum }) ); - console.log('txResponse?.txData', txResponse?.txData); const proposalQueries = - txResponse?.txData?.map((tx) => { + _.map(txResponse?.txData, (tx) => { const txHash = tx.transactionHash || tx.txHash; return { queryKey: ['proposal', txHash], queryFn: async () => { - const proposal: { proposals: any[] } = await v3client.request(` - { - proposals(where: { processTxHash: "${txHash}"}) { - id - createdAt - createdBy - proposedBy - processTxHash - proposalType - description - title - txHash + try { + const proposal: { proposals: any[] } = await v3client.request(` + { + proposals(where: { processTxHash: "${txHash}"}) { + id + createdAt + createdBy + proposedBy + processTxHash + proposalType + description + title + txHash + } + } + `); + if (!_.size(proposal.proposals)) { + return null; + } + return _.first(proposal.proposals) || null; + } catch (error) { + return null; } - } - `); - return proposal?.proposals[0]; + }, + onError: (error) => { + console.error( + `Error in query proposal with txHash: ${txHash}`, + error + ); }, }; }) || []; const proposalsInfo = useQueries({ queries: proposalQueries }); - console.log( - 'proposalsInfo', - proposalsInfo.map((query) => query.data) - ); - - const memberQueries = - proposalsInfo - ?.filter((proposalQuery) => proposalQuery.data) - ?.map((proposalQuery) => ({ - queryKey: ['member', proposalQuery.data?.proposedBy], - queryFn: async () => { - const m: { members: any[] } = await v3client.request(` - { - members(where: { memberAddress: "${proposalQuery.data?.proposedBy}" }) { - id - createdAt - memberAddress - shares - loot - delegatingTo - delegateShares - } - } - `); - return m.members[0]; - }, - })) || []; - - const members = useQueries({ queries: memberQueries }); - const error = tokenBalancesError || txResponseError; - const proposalsInfoData = proposalsInfo - ?.filter((query) => query.data !== undefined) - ?.map((query) => query.data); - - const transformProposals = (proposalsInfoData || []).reduce( - (acc, proposal) => { + const transformProposals = _.chain(proposalsInfo) + .filter((query) => query.data) + .map((query) => query.data) + .reduce((acc, proposal) => { const { processTxHash, ...rest } = proposal; acc[processTxHash] = rest; return acc; - }, - {} - ); + }, {}) + .value(); const data = { tokens: tokenBalances?.data, transactions: txResponse?.txData, proposalsInfo: transformProposals, - members: members?.map((query) => query.data), }; return { data, error }; diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index 5718589c..27b8c21e 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -3,33 +3,27 @@ import { IMappedTokenPrice, IMember, ITokenBalanceLineItem, - IVaultTransaction, + IVaultTransactionV2, } from '@raidguild/dm-types'; import { formatDate, formatUnitsAsNumber, + GNOSIS_SAFE_ADDRESS, REGEX_ETH_ADDRESS, } from '@raidguild/dm-utils'; import { InfiniteData } from '@tanstack/react-query'; import _ from 'lodash'; import { useCallback, useMemo } from 'react'; +const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; + class CalculateTokenBalances { calculatedTokenBalances: Record< string, { inflow: bigint; outflow: bigint; balance: bigint } - >; - - constructor() { - this.calculatedTokenBalances = {}; - } + > = {}; - getBalance(tokenAddress: string) { - this.initTokenBalance(tokenAddress); - return this.calculatedTokenBalances[tokenAddress].balance; - } - - initTokenBalance(tokenAddress: string) { + private initTokenBalance(tokenAddress: string) { if (!(tokenAddress in this.calculatedTokenBalances)) { this.calculatedTokenBalances[tokenAddress] = { inflow: BigInt(0), @@ -42,21 +36,20 @@ class CalculateTokenBalances { incrementInflow(tokenAddress: string, inValue: bigint) { this.initTokenBalance(tokenAddress); const tokenStats = this.calculatedTokenBalances[tokenAddress]; - this.calculatedTokenBalances[tokenAddress] = { - ...tokenStats, - inflow: tokenStats.inflow + inValue, - balance: tokenStats.balance + inValue, - }; + tokenStats.inflow += inValue; + tokenStats.balance += inValue; } incrementOutflow(tokenAddress: string, outValue: bigint) { this.initTokenBalance(tokenAddress); const tokenStats = this.calculatedTokenBalances[tokenAddress]; - this.calculatedTokenBalances[tokenAddress] = { - ...tokenStats, - outflow: tokenStats.outflow + outValue, - balance: tokenStats.balance - outValue, - }; + tokenStats.outflow += outValue; + tokenStats.balance -= outValue; + } + + getBalance(tokenAddress: string) { + this.initTokenBalance(tokenAddress); + return this.calculatedTokenBalances[tokenAddress].balance; } getBalances() { @@ -64,27 +57,20 @@ class CalculateTokenBalances { } } -function calculateTokenFlows(transactions) { +function calculateTokenFlows(transactions: IVaultTransactionV2[]) { const tokenBalances = new CalculateTokenBalances(); - // Sort transactions by executionDate in ascending order - const sortedTransactions = transactions.sort((a, b) => - a.executionDate.localeCompare(b.executionDate) - ); - - sortedTransactions.forEach((transaction) => { - const safeAddress = '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC'; - - transaction.transfers.forEach((transfer) => { + _.sortBy(transactions, 'executionDate').forEach((transaction) => { + _.forEach(transaction?.transfers, (transfer) => { const tokenAddress = transfer.type === 'ETHER_TRANSFER' ? 'ETH' - : transfer.tokenInfo?.address; + : transfer.tokenInfo?.address || ''; const amount = BigInt(transfer.value || 0); - if (transfer.to === safeAddress) { + if (transfer.to === GNOSIS_SAFE_ADDRESS) { tokenBalances.incrementInflow(tokenAddress, amount); - } else if (transfer.from === safeAddress) { + } else if (transfer.from === GNOSIS_SAFE_ADDRESS) { tokenBalances.incrementOutflow(tokenAddress, amount); } }); @@ -101,10 +87,10 @@ const useFormattedDataV3 = ({ proposalsInfo, }: { balances: ITokenBalanceLineItem[]; - transactions: IVaultTransaction[]; + transactions: IVaultTransactionV2[]; tokenPrices: IMappedTokenPrice; memberData: InfiniteData; - proposalsInfo: any; + proposalsInfo: Record; }) => { const flows = useMemo( () => calculateTokenFlows(transactions), @@ -120,50 +106,38 @@ const useFormattedDataV3 = ({ const withPrices = useCallback( (items: any[]) => - (items || []).map((t) => { + _.map(items, (t) => { if (!t) return t; const formattedDate = formatDate(t.date); - const tokenSymbol = t.tokenSymbol?.toLowerCase(); + const tokenSymbol = _.lowerCase(t.tokenSymbol); const balance = { inflow: { tokenValue: formatUnitsAsNumber( - flows[t.tokenAddress]?.inflow || BigInt(0), - t.token?.decimals + _.get(flows, [t.tokenAddress, 'inflow'], BigInt(0)), + _.get(t, ['token', 'decimals']) ), }, outflow: { tokenValue: formatUnitsAsNumber( - flows[t.tokenAddress]?.outflow || BigInt(0), - t.token?.decimals + _.get(flows, [t.tokenAddress, 'outflow'], BigInt(0)), + _.get(t, ['token', 'decimals']) ), }, closing: { tokenValue: formatUnitsAsNumber( - flows[t.tokenAddress]?.balance || BigInt(0), - t.token?.decimals + _.get(flows, [t.tokenAddress, 'balance'], BigInt(0)), + _.get(t, ['token', 'decimals']) ), }, }; - if ( - tokenPrices[tokenSymbol] && - tokenPrices[tokenSymbol][formattedDate] - ) { - return { - ...t, - ...balance, - priceConversion: tokenPrices[tokenSymbol][formattedDate], - }; - } - if (tokenSymbol?.includes('xdai')) { - return { - ...t, - ...balance, - priceConversion: 1, - }; - } + + const priceConversion = + _.get(tokenPrices, [tokenSymbol, formattedDate]) || + (_.includes(tokenSymbol, 'xdai') ? 1 : undefined); return { ...t, ...balance, + priceConversion, }; }), [tokenPrices, flows] @@ -177,73 +151,79 @@ const useFormattedDataV3 = ({ const transactionsWithPrices = useMemo(() => { const tokenBalances = new CalculateTokenBalances(); - return withPrices(transactions) - .sort((a, b) => a.executionDate.localeCompare(b.executionDate)) + return _.chain(withPrices(transactions)) + .sortBy('executionDate') .flatMap((t) => - t.transfers?.map((transfer) => { + _.map(t.transfers, (transfer) => { const tokenSymbol = - transfer?.type === 'ETHER_TRANSFER' + transfer.type === 'ETHER_TRANSFER' ? 'XDAI' - : transfer?.tokenInfo?.symbol || ''; + : _.get(transfer, 'tokenInfo.symbol', ''); const tokenDecimals = - transfer?.type === 'ETHER_TRANSFER' + transfer.type === 'ETHER_TRANSFER' ? 18 - : transfer?.tokenInfo?.decimals || 0; + : _.get(transfer, 'tokenInfo.decimals', 0); const tokenAddress = - transfer?.type === 'ETHER_TRANSFER' + transfer.type === 'ETHER_TRANSFER' ? 'XDAI' - : transfer?.tokenAddress || ''; + : _.get(transfer, 'tokenAddress', ''); const inAmount = - transfer?.to === '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC' && - transfer?.value !== null + transfer.to === GNOSIS_SAFE_ADDRESS && transfer.value !== null ? BigInt(transfer.value) : BigInt(0); const outAmount = - transfer?.from === '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC' && - transfer?.value !== null + transfer.from === GNOSIS_SAFE_ADDRESS && transfer.value !== null ? BigInt(transfer.value) : BigInt(0); tokenBalances.incrementInflow(tokenAddress, inAmount); tokenBalances.incrementOutflow(tokenAddress, outAmount); - const txHash = t.transactionHash || t.txHash; + const txHash = _.get(t, 'transactionHash', t.txHash); const proposal = proposalsInfo[txHash]; const txExplorerLink = `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`; const proposalLink = proposal - ? `https://admin.daohaus.club/#/molochV3/0x64/${proposal.id.replace( + ? `https://admin.daohaus.club/#/molochV3/0x64/${_.replace( + proposal.id, /-/g, '/' )}` : ''; - - let txType = 'proposal'; - if (!proposal) { - txType = - transfer.to === '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC' - ? 'spoils' - : 'ragequit'; + let txType; + if (proposal) { + txType = 'proposal'; + } else if (transfer.to === GNOSIS_SAFE_ADDRESS) { + txType = 'spoils'; + } else { + txType = 'ragequit'; } + const net = formatUnitsAsNumber(inAmount - outAmount, tokenDecimals); + + const elapsedDays = + net > 0 + ? Math.floor( + (Date.now() - t.executionDate) / MILLISECONDS_PER_DAY + ) + : undefined; + return { + elapsedDays, + date: new Date(t.executionDate), balance: formatUnitsAsNumber( tokenBalances.getBalance(tokenAddress), tokenDecimals ), - counterparty: transfer?.to, // gotta check - date: new Date(t.executionDate), - elapsedDays: undefined, + counterparty: transfer.to, in: formatUnitsAsNumber(inAmount, tokenDecimals), - net: formatUnitsAsNumber(inAmount - outAmount, tokenDecimals), out: formatUnitsAsNumber(outAmount, tokenDecimals), - proposalApplicant: proposal?.proposedBy, - proposalId: proposal?.id, + net, + proposalApplicant: _.get(proposal, 'proposedBy'), + proposalId: _.get(proposal, 'id'), txExplorerLink, proposalLink, - proposalLoot: undefined, - proposalShares: undefined, - proposalTitle: proposal?.title, + proposalTitle: _.get(proposal, 'title'), tokenAddress, tokenDecimals, tokenSymbol, @@ -251,23 +231,24 @@ const useFormattedDataV3 = ({ }; }) ) - .reverse(); + .reverse() + .value(); }, [transactions, withPrices, proposalsInfo]); const transactionsWithPricesAndMembers = useMemo( () => - transactionsWithPrices.map((t) => { - const ethAddress = t.counterparty?.toLowerCase(); - const m = members[ethAddress]; - const memberLink = m?.ethAddress?.match(REGEX_ETH_ADDRESS) + _.map(transactionsWithPrices, (t) => { + const ethAddress = _.get(t, 'counterparty').toLowerCase(); + const member = _.get(members, ethAddress); + const memberLink = _.get(member, 'ethAddress')?.match(REGEX_ETH_ADDRESS) ? `/members/${ethAddress}` : undefined; return { ...t, memberLink, - memberEnsName: m?.ensName, - memberName: m?.name, + memberEnsName: _.get(member, 'ensName'), + memberName: _.get(member, 'name'), }; }), [transactionsWithPrices, members] diff --git a/libs/dm-types/src/accounting.ts b/libs/dm-types/src/accounting.ts index b545a45a..7babc287 100644 --- a/libs/dm-types/src/accounting.ts +++ b/libs/dm-types/src/accounting.ts @@ -46,6 +46,61 @@ export type IVaultTransaction = { escrowLink?: string; }; +interface Transfer { + blockNumber: number; + executionDate: string; + from: string; + to: string; + tokenAddress: string; + tokenId: string | null; + tokenInfo: { + address: string; + decimals: number; + logoUri: string; + name: string; + symbol: string; + trusted: boolean; + type: string; + }; + transactionHash: string; + transferId: string; + type: string; + value: string; +} + +interface EthereumTransaction { + blockNumber: number; + data: string; + executionDate: string; + from: string; + to: string; + transfers: Transfer[]; + txHash: string; + txType: 'ETHEREUM_TRANSACTION'; +} + +interface ModuleTransaction { + blockNumber: number; + created: string; + data: string; + dataDecoded: { + method: string; + parameters: Array<{ name: string; type: string; value: string }>; + }; + executionDate: string; + isSuccessful: boolean; + module: string; + moduleTransactionId: string; + operation: number; + safe: string; + to: string; + transactionHash: string; + transfers: Transfer[]; + txType: 'MODULE_TRANSACTION'; +} + +export type IVaultTransactionV2 = EthereumTransaction | ModuleTransaction; + export type ICalculatedTokenBalances = { [tokenAddress: string]: { in: bigint; diff --git a/libs/dm-utils/src/constants.ts b/libs/dm-utils/src/constants.ts index 7cf0a0c4..11931932 100644 --- a/libs/dm-utils/src/constants.ts +++ b/libs/dm-utils/src/constants.ts @@ -293,6 +293,8 @@ export const GUILD_CLASS_DISPLAY = { export const GUILD_GNOSIS_DAO_ADDRESS = '0xfe1084bc16427e5eb7f13fc19bcd4e641f7d571f'; +export const GNOSIS_SAFE_ADDRESS = '0x181eBDB03cb4b54F4020622F1B0EAcd67A8C63aC'; + export const SIDEBAR_ACTION_STATES = { none: 'NONE', select: 'SELECT', From 213347f894901e2519600935d86e365a07ba0e54 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Sat, 15 Jun 2024 13:06:16 +0200 Subject: [PATCH 14/19] set balancesWithPricesV3 data for csv export fix --- apps/frontend/pages/accounting.tsx | 31 +++++++++++++++++++++---- libs/dm-hooks/src/useFormattedDataV3.ts | 12 +++++++--- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index 8e41c109..115428c9 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -21,7 +21,7 @@ import _ from 'lodash'; import { useSession } from 'next-auth/react'; import { NextSeo } from 'next-seo'; import Papa from 'papaparse'; -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import BalancesTable from '../components/BalancesTable'; import SiteLayout from '../components/SiteLayout'; @@ -30,6 +30,9 @@ import TransactionsTable from '../components/TransactionsTable'; export const Accounting = () => { const { data: session } = useSession(); + const [isV3, setIsV3] = useState(true); + console.log('isV3', isV3); + const token = _.get(session, 'token'); const { data: dataFromMolochV2, @@ -70,7 +73,9 @@ export const Accounting = () => { (type: 'transactions' | 'balances' | 'spoils') => { let csvString = ''; if (type === 'transactions') { - const formattedTransactions = transactionsWithPrices.map((t) => ({ + const formattedTransactions = ( + isV3 ? transactionsWithPricesV3 : transactionsWithPrices + ).map((t) => ({ Date: t.date, 'Tx Explorer Link': t.txExplorerLink, 'Elapsed Days': t.elapsedDays, @@ -103,7 +108,9 @@ export const Accounting = () => { csvString = Papa.unparse(formattedTransactions); } else if (type === 'balances') { if (type === 'balances') { - const formattedBalances = balancesWithPricesV2.map((b) => ({ + const formattedBalances = ( + isV3 ? balancesWithPricesV3 : balancesWithPricesV2 + ).map((b) => ({ Token: b.tokenSymbol, 'Tx Explorer Link': b.tokenExplorerLink, Inflow: b.inflow.tokenValue, @@ -140,7 +147,15 @@ export const Accounting = () => { } exportToCsv(csvString, `raidguild-treasury-${type}`); }, - [balancesWithPricesV2, members, spoils, transactionsWithPrices] + [ + balancesWithPricesV2, + balancesWithPricesV3, + isV3, + members, + spoils, + transactionsWithPrices, + transactionsWithPricesV3, + ] ); return ( @@ -178,6 +193,10 @@ export const Accounting = () => { colorScheme='whiteAlpha' variant='unstyled' defaultIndex={0} + onChange={(index) => { + if (index === 0) setIsV3(true); + else setIsV3(false); + }} > { colorScheme='whiteAlpha' variant='unstyled' defaultIndex={0} + onChange={(index) => { + if (index === 0) setIsV3(true); + else setIsV3(false); + }} > { if (!t) return t; const formattedDate = formatDate(t.date); - const tokenSymbol = _.lowerCase(t.tokenSymbol); + const tokenSymbol = _.lowerCase(t.token?.symbol); const balance = { inflow: { tokenValue: formatUnitsAsNumber( @@ -134,10 +134,14 @@ const useFormattedDataV3 = ({ const priceConversion = _.get(tokenPrices, [tokenSymbol, formattedDate]) || (_.includes(tokenSymbol, 'xdai') ? 1 : undefined); + return { ...t, ...balance, + tokenSymbol, priceConversion, + date: new Date(), + tokenExplorerLink: `https://blockscout.com/xdai/mainnet/address/${t.tokenAddress}`, }; }), [tokenPrices, flows] @@ -180,9 +184,9 @@ const useFormattedDataV3 = ({ tokenBalances.incrementInflow(tokenAddress, inAmount); tokenBalances.incrementOutflow(tokenAddress, outAmount); + const txExplorerLink = `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`; const txHash = _.get(t, 'transactionHash', t.txHash); const proposal = proposalsInfo[txHash]; - const txExplorerLink = `https://blockscout.com/xdai/mainnet/tx/${t.txHash}`; const proposalLink = proposal ? `https://admin.daohaus.club/#/molochV3/0x64/${_.replace( proposal.id, @@ -219,11 +223,13 @@ const useFormattedDataV3 = ({ in: formatUnitsAsNumber(inAmount, tokenDecimals), out: formatUnitsAsNumber(outAmount, tokenDecimals), net, - proposalApplicant: _.get(proposal, 'proposedBy'), + proposalApplicant: _.get(proposal, 'createdBy', ''), proposalId: _.get(proposal, 'id'), txExplorerLink, proposalLink, proposalTitle: _.get(proposal, 'title'), + proposalShares: undefined, // ?? + proposalLoot: undefined, // ?? tokenAddress, tokenDecimals, tokenSymbol, From d1d859b8ac0493ab477aab5bc58f2c507464dbbd Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Tue, 18 Jun 2024 11:53:42 +0200 Subject: [PATCH 15/19] add elapsed days and ragequit shares --- apps/frontend/.env.example | 3 ++ apps/frontend/pages/accounting.tsx | 13 +------ libs/dm-hooks/src/useAccountingV3.ts | 32 ++++++++++++++-- libs/dm-hooks/src/useFormattedDataV3.ts | 51 +++++++++++-------------- libs/dm-utils/src/constants.ts | 2 + libs/dm-utils/src/table.ts | 2 +- 6 files changed, 58 insertions(+), 45 deletions(-) diff --git a/apps/frontend/.env.example b/apps/frontend/.env.example index 996329a9..47e5bf72 100644 --- a/apps/frontend/.env.example +++ b/apps/frontend/.env.example @@ -21,3 +21,6 @@ NEXT_PUBLIC_WC_PROJECT_ID= # https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens GITHUB_API_TOKEN= GITHUB_API_URL='https://api.github.com/graphql' + +# create one for prod here: https://thegraph.com/studio/ +NEXT_PUBLIC_THE_GRAPH_API_KEY= \ No newline at end of file diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index 115428c9..940823bf 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -10,7 +10,6 @@ import { Tabs, } from '@raidguild/design-system'; import { - useAccountingV3, useAccountingV2, useFormattedData, useMemberList, @@ -31,7 +30,6 @@ import TransactionsTable from '../components/TransactionsTable'; export const Accounting = () => { const { data: session } = useSession(); const [isV3, setIsV3] = useState(true); - console.log('isV3', isV3); const token = _.get(session, 'token'); const { @@ -41,7 +39,6 @@ export const Accounting = () => { } = useAccountingV2({ token, }); - const { data: dataFromMolochV3 } = useAccountingV3(); const { data: memberData } = useMemberList({ token, @@ -59,15 +56,9 @@ export const Accounting = () => { const { balancesWithPrices: balancesWithPricesV3, - transactionsWithPrices: transactionsWithPricesV3, // used for export + transactionsWithPrices: transactionsWithPricesV3, transactionsWithPricesAndMembers: transactionsWithPricesAndMembersV3, - } = useFormattedDataV3({ - balances: dataFromMolochV3?.tokens?.tokenBalances || [], - transactions: dataFromMolochV3?.transactions || [], - tokenPrices: {}, - proposalsInfo: dataFromMolochV3?.proposalsInfo || {}, - memberData, - }); + } = useFormattedDataV3(memberData); const onExportCsv = useCallback( (type: 'transactions' | 'balances' | 'spoils') => { diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index 0a07e55a..f80b4ff0 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -15,9 +15,6 @@ const graphUrl = (chainId: number = 4) => export const SUPPORTED_NETWORKS = _.map(_.keys(NETWORK_CONFIG), _.toNumber); export const client = (chainId: number) => new GraphQLClient(graphUrl(chainId)); -export const v3client = new GraphQLClient( - 'https://api.thegraph.com/subgraphs/name/hausdao/daohaus-v3-gnosis' -); const API_URL = 'https://safe-transaction-gnosis-chain.safe.global/api/v1'; @@ -74,9 +71,30 @@ const getSafeTransactionProposals = async ({ } }; +const getRageQuits = async (v3client) => { + try { + const rageQuits: { rageQuits: any[] } = await v3client.request(` + { + rageQuits(where: { dao: "${GNOSIS_SAFE_ADDRESS}" }) { + shares + txHash + } + } + `); + + return rageQuits.rageQuits; + } catch (error) { + return null; + } +}; + const useAccountingV3 = () => { const checksum = getAddress(GNOSIS_SAFE_ADDRESS); + const v3client = new GraphQLClient( + `https://gateway-arbitrum.network.thegraph.com/api/${process.env.NEXT_PUBLIC_THE_GRAPH_API_KEY}/subgraphs/id/6x9FK3iuhVFaH9sZ39m8bKB5eckax8sjxooBPNKWWK8r` + ); + const { data: tokenBalances, error: tokenBalancesError } = useQuery( ['tokenBalances', checksum], () => listTokenBalances({ safeAddress: checksum }) @@ -87,6 +105,11 @@ const useAccountingV3 = () => { () => getSafeTransactionProposals({ safeAddress: checksum }) ); + const { data: rageQuitsData, error: rageQuitsError } = useQuery( + ['rageQuits'], + () => getRageQuits(v3client) + ); + const proposalQueries = _.map(txResponse?.txData, (tx) => { const txHash = tx.transactionHash || tx.txHash; @@ -128,7 +151,7 @@ const useAccountingV3 = () => { const proposalsInfo = useQueries({ queries: proposalQueries }); - const error = tokenBalancesError || txResponseError; + const error = tokenBalancesError || txResponseError || rageQuitsError; const transformProposals = _.chain(proposalsInfo) .filter((query) => query.data) @@ -143,6 +166,7 @@ const useAccountingV3 = () => { const data = { tokens: tokenBalances?.data, transactions: txResponse?.txData, + rageQuits: rageQuitsData || [], proposalsInfo: transformProposals, }; diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index 9df521ba..553c682f 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -1,12 +1,7 @@ /* eslint-disable no-param-reassign */ +import { IMember, IVaultTransactionV2 } from '@raidguild/dm-types'; import { - IMappedTokenPrice, - IMember, - ITokenBalanceLineItem, - IVaultTransactionV2, -} from '@raidguild/dm-types'; -import { - formatDate, + DAY_MILLISECONDS, formatUnitsAsNumber, GNOSIS_SAFE_ADDRESS, REGEX_ETH_ADDRESS, @@ -15,7 +10,7 @@ import { InfiniteData } from '@tanstack/react-query'; import _ from 'lodash'; import { useCallback, useMemo } from 'react'; -const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; +import useAccountingV3 from './useAccountingV3'; class CalculateTokenBalances { calculatedTokenBalances: Record< @@ -79,19 +74,13 @@ function calculateTokenFlows(transactions: IVaultTransactionV2[]) { return tokenBalances.getBalances(); } -const useFormattedDataV3 = ({ - balances, - transactions, - tokenPrices, - memberData, - proposalsInfo, -}: { - balances: ITokenBalanceLineItem[]; - transactions: IVaultTransactionV2[]; - tokenPrices: IMappedTokenPrice; - memberData: InfiniteData; - proposalsInfo: Record; -}) => { +const useFormattedDataV3 = (memberData: InfiniteData) => { + const { data: dataFromMolochV3 } = useAccountingV3(); + const balances = dataFromMolochV3?.tokens?.tokenBalances || []; + const transactions = dataFromMolochV3?.transactions || []; + const proposalsInfo = dataFromMolochV3?.proposalsInfo || {}; + const rageQuits = dataFromMolochV3?.rageQuits || []; + const flows = useMemo( () => calculateTokenFlows(transactions), [transactions] @@ -108,7 +97,6 @@ const useFormattedDataV3 = ({ (items: any[]) => _.map(items, (t) => { if (!t) return t; - const formattedDate = formatDate(t.date); const tokenSymbol = _.lowerCase(t.token?.symbol); const balance = { inflow: { @@ -131,9 +119,7 @@ const useFormattedDataV3 = ({ }, }; - const priceConversion = - _.get(tokenPrices, [tokenSymbol, formattedDate]) || - (_.includes(tokenSymbol, 'xdai') ? 1 : undefined); + const priceConversion = _.includes(tokenSymbol, 'xdai') ? 1 : undefined; return { ...t, @@ -144,7 +130,7 @@ const useFormattedDataV3 = ({ tokenExplorerLink: `https://blockscout.com/xdai/mainnet/address/${t.tokenAddress}`, }; }), - [tokenPrices, flows] + [flows] ); const balancesWithPrices = useMemo( @@ -154,6 +140,7 @@ const useFormattedDataV3 = ({ const transactionsWithPrices = useMemo(() => { const tokenBalances = new CalculateTokenBalances(); + const rageQuitsMap = _.keyBy(rageQuits, 'txHash'); return _.chain(withPrices(transactions)) .sortBy('executionDate') @@ -208,10 +195,16 @@ const useFormattedDataV3 = ({ const elapsedDays = net > 0 ? Math.floor( - (Date.now() - t.executionDate) / MILLISECONDS_PER_DAY + (Date.now() - new Date(t.executionDate).getTime()) / + DAY_MILLISECONDS ) : undefined; + const proposalShares = + transfer.from === GNOSIS_SAFE_ADDRESS + ? _.get(rageQuitsMap, [t.txHash, 'shares'], undefined) + : undefined; + return { elapsedDays, date: new Date(t.executionDate), @@ -228,8 +221,8 @@ const useFormattedDataV3 = ({ txExplorerLink, proposalLink, proposalTitle: _.get(proposal, 'title'), - proposalShares: undefined, // ?? - proposalLoot: undefined, // ?? + proposalShares, + proposalLoot: undefined, tokenAddress, tokenDecimals, tokenSymbol, diff --git a/libs/dm-utils/src/constants.ts b/libs/dm-utils/src/constants.ts index 11931932..0e676c9d 100644 --- a/libs/dm-utils/src/constants.ts +++ b/libs/dm-utils/src/constants.ts @@ -303,3 +303,5 @@ export const SIDEBAR_ACTION_STATES = { cleric: 'CLERIC', hunter: 'HUNTER', }; + +export const DAY_MILLISECONDS = 1000 * 60 * 60 * 24; diff --git a/libs/dm-utils/src/table.ts b/libs/dm-utils/src/table.ts index ce3ec507..e92380bc 100644 --- a/libs/dm-utils/src/table.ts +++ b/libs/dm-utils/src/table.ts @@ -4,7 +4,7 @@ import { RankingInfo, rankings } from '@tanstack/match-sorter-utils'; import { CellContext } from '@tanstack/react-table'; import { formatUnits } from 'viem'; -const DAY_MILLISECONDS = 1000 * 60 * 60 * 24; +import { DAY_MILLISECONDS } from './constants'; export const exportToCsv = (csvString: string, fileName: string) => { const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' }); From 045a83bee44eb3e5e1179b59e8037d38259805c4 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Tue, 18 Jun 2024 13:14:49 +0200 Subject: [PATCH 16/19] add in/out/balance USD --- libs/dm-hooks/src/useFormattedDataV3.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index 553c682f..e1e593f7 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -120,6 +120,7 @@ const useFormattedDataV3 = (memberData: InfiniteData) => { }; const priceConversion = _.includes(tokenSymbol, 'xdai') ? 1 : undefined; + console.log('priceConversion', priceConversion); return { ...t, @@ -159,6 +160,10 @@ const useFormattedDataV3 = (memberData: InfiniteData) => { ? 'XDAI' : _.get(transfer, 'tokenAddress', ''); + const priceConversion = _.includes(tokenSymbol.toLowerCase(), 'xdai') + ? 1 + : undefined; + const inAmount = transfer.to === GNOSIS_SAFE_ADDRESS && transfer.value !== null ? BigInt(transfer.value) @@ -226,6 +231,7 @@ const useFormattedDataV3 = (memberData: InfiniteData) => { tokenAddress, tokenDecimals, tokenSymbol, + priceConversion, type: txType, }; }) From 924f54e27ff7089eb144cb54e0983e6a40628988 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Tue, 18 Jun 2024 13:25:32 +0200 Subject: [PATCH 17/19] rename --- apps/frontend/pages/accounting.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index 940823bf..fd1e6f77 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -50,8 +50,8 @@ export const Accounting = () => { const { members, balancesWithPrices: balancesWithPricesV2, - transactionsWithPrices, - transactionsWithPricesAndMembers, + transactionsWithPrices: transactionsWithPricesV2, + transactionsWithPricesAndMembers: transactionsWithPricesAndMembersV2, } = useFormattedData(memberData, balances, transactions, tokenPrices); const { @@ -65,7 +65,7 @@ export const Accounting = () => { let csvString = ''; if (type === 'transactions') { const formattedTransactions = ( - isV3 ? transactionsWithPricesV3 : transactionsWithPrices + isV3 ? transactionsWithPricesV3 : transactionsWithPricesV2 ).map((t) => ({ Date: t.date, 'Tx Explorer Link': t.txExplorerLink, @@ -144,7 +144,7 @@ export const Accounting = () => { isV3, members, spoils, - transactionsWithPrices, + transactionsWithPricesV2, transactionsWithPricesV3, ] ); @@ -156,7 +156,8 @@ export const Accounting = () => { { From ae331a504b0750a4b0a90d3c0e6a7e8e370ee55e Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Tue, 25 Jun 2024 16:53:23 +0200 Subject: [PATCH 18/19] fix types --- apps/frontend/components/BalancesTable.tsx | 11 +++- apps/frontend/pages/accounting.tsx | 4 +- libs/dm-hooks/src/index.ts | 2 +- libs/dm-hooks/src/useAccountingV3.ts | 21 +++--- ...FormattedData.ts => useFormattedDataV2.ts} | 4 +- libs/dm-hooks/src/useFormattedDataV3.ts | 17 +++-- libs/dm-types/src/accounting.ts | 65 +++++++++++++++++++ 7 files changed, 100 insertions(+), 24 deletions(-) rename libs/dm-hooks/src/{useFormattedData.ts => useFormattedDataV2.ts} (97%) diff --git a/apps/frontend/components/BalancesTable.tsx b/apps/frontend/components/BalancesTable.tsx index 9bc6435e..f3fd360a 100644 --- a/apps/frontend/components/BalancesTable.tsx +++ b/apps/frontend/components/BalancesTable.tsx @@ -1,5 +1,8 @@ import { Link, Tooltip } from '@raidguild/design-system'; -import { ITokenBalanceLineItem } from '@raidguild/dm-types'; +import { + ITokenBalanceLineItem, + ITokenBalanceLineItemV3, +} from '@raidguild/dm-types'; import { minMaxNumberFilter, sortNumeric } from '@raidguild/dm-utils'; // @ts-expect-error - no types from RT import { createColumnHelper } from '@tanstack/react-table'; @@ -8,10 +11,12 @@ import DataTable from './DataTable'; import TokenWithUsdValue from './TokenWithUsdValue'; interface BalancesTableProps { - data: ITokenBalanceLineItem[]; + data: ITokenBalanceLineItem[] | ITokenBalanceLineItemV3[]; } -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper< + ITokenBalanceLineItem | ITokenBalanceLineItemV3 +>(); const columns = [ columnHelper.accessor('tokenExplorerLink', { diff --git a/apps/frontend/pages/accounting.tsx b/apps/frontend/pages/accounting.tsx index fd1e6f77..9014a8a0 100644 --- a/apps/frontend/pages/accounting.tsx +++ b/apps/frontend/pages/accounting.tsx @@ -11,7 +11,7 @@ import { } from '@raidguild/design-system'; import { useAccountingV2, - useFormattedData, + useFormattedDataV2, useMemberList, useFormattedDataV3, } from '@raidguild/dm-hooks'; @@ -52,7 +52,7 @@ export const Accounting = () => { balancesWithPrices: balancesWithPricesV2, transactionsWithPrices: transactionsWithPricesV2, transactionsWithPricesAndMembers: transactionsWithPricesAndMembersV2, - } = useFormattedData(memberData, balances, transactions, tokenPrices); + } = useFormattedDataV2(memberData, balances, transactions, tokenPrices); const { balancesWithPrices: balancesWithPricesV3, diff --git a/libs/dm-hooks/src/index.ts b/libs/dm-hooks/src/index.ts index 2ec97336..09054a58 100644 --- a/libs/dm-hooks/src/index.ts +++ b/libs/dm-hooks/src/index.ts @@ -16,7 +16,7 @@ export { useContacts } from './useContacts'; export { default as useContactUpdate } from './useContactUpdate'; export { default as useDashboardList } from './useDashboardList'; export { default as useDefaultTitle } from './useDefaultTitle'; -export { default as useFormattedData } from './useFormattedData'; +export { default as useFormattedDataV2 } from './useFormattedDataV2'; export { default as useFormattedDataV3 } from './useFormattedDataV3'; export * from './useLinks'; export { default as useLinksUpdate } from './useLinksUpdate'; diff --git a/libs/dm-hooks/src/useAccountingV3.ts b/libs/dm-hooks/src/useAccountingV3.ts index f80b4ff0..c6e8dc00 100644 --- a/libs/dm-hooks/src/useAccountingV3.ts +++ b/libs/dm-hooks/src/useAccountingV3.ts @@ -1,4 +1,5 @@ /* eslint-disable no-await-in-loop */ +import { Proposal, RageQuit } from '@raidguild/dm-types'; import { GNOSIS_SAFE_ADDRESS } from '@raidguild/dm-utils'; import { NETWORK_CONFIG } from '@raidguild/escrow-utils'; import { useQueries, useQuery } from '@tanstack/react-query'; @@ -71,9 +72,9 @@ const getSafeTransactionProposals = async ({ } }; -const getRageQuits = async (v3client) => { +const getRageQuits = async (v3client: GraphQLClient): Promise => { try { - const rageQuits: { rageQuits: any[] } = await v3client.request(` + const rageQuits: { rageQuits: RageQuit[] } = await v3client.request(` { rageQuits(where: { dao: "${GNOSIS_SAFE_ADDRESS}" }) { shares @@ -84,7 +85,7 @@ const getRageQuits = async (v3client) => { return rageQuits.rageQuits; } catch (error) { - return null; + return []; } }; @@ -115,9 +116,9 @@ const useAccountingV3 = () => { const txHash = tx.transactionHash || tx.txHash; return { queryKey: ['proposal', txHash], - queryFn: async () => { + queryFn: async (): Promise => { try { - const proposal: { proposals: any[] } = await v3client.request(` + const proposal: { proposals: Proposal[] } = await v3client.request(` { proposals(where: { processTxHash: "${txHash}"}) { id @@ -140,7 +141,7 @@ const useAccountingV3 = () => { return null; } }, - onError: (error) => { + onError: (error: Error) => { console.error( `Error in query proposal with txHash: ${txHash}`, error @@ -152,16 +153,14 @@ const useAccountingV3 = () => { const proposalsInfo = useQueries({ queries: proposalQueries }); const error = tokenBalancesError || txResponseError || rageQuitsError; - - const transformProposals = _.chain(proposalsInfo) + const transformProposals = proposalsInfo .filter((query) => query.data) - .map((query) => query.data) + .map((query) => query.data as Proposal) .reduce((acc, proposal) => { const { processTxHash, ...rest } = proposal; acc[processTxHash] = rest; return acc; - }, {}) - .value(); + }, {} as Record>); const data = { tokens: tokenBalances?.data, diff --git a/libs/dm-hooks/src/useFormattedData.ts b/libs/dm-hooks/src/useFormattedDataV2.ts similarity index 97% rename from libs/dm-hooks/src/useFormattedData.ts rename to libs/dm-hooks/src/useFormattedDataV2.ts index 4fd93780..994ac452 100644 --- a/libs/dm-hooks/src/useFormattedData.ts +++ b/libs/dm-hooks/src/useFormattedDataV2.ts @@ -9,7 +9,7 @@ import { InfiniteData } from '@tanstack/react-query'; import _ from 'lodash'; import { useCallback, useMemo } from 'react'; -const useFormattedData = ( +const useFormattedDataV2 = ( memberData: InfiniteData, balances: ITokenBalanceLineItem[], transactions: IVaultTransaction[], @@ -84,4 +84,4 @@ const useFormattedData = ( }; }; -export default useFormattedData; +export default useFormattedDataV2; diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index e1e593f7..ad88592c 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -1,5 +1,9 @@ /* eslint-disable no-param-reassign */ -import { IMember, IVaultTransactionV2 } from '@raidguild/dm-types'; +import { + IMember, + ITokenBalanceLineItemV3, + IVaultTransactionV2, +} from '@raidguild/dm-types'; import { DAY_MILLISECONDS, formatUnitsAsNumber, @@ -94,10 +98,13 @@ const useFormattedDataV3 = (memberData: InfiniteData) => { }, [memberData]); const withPrices = useCallback( - (items: any[]) => + (items: T[]) => _.map(items, (t) => { if (!t) return t; - const tokenSymbol = _.lowerCase(t.token?.symbol); + + const tokenSymbol = _.lowerCase( + 'token' in t ? t.token.symbol : t.tokenInfo?.symbol + ); const balance = { inflow: { tokenValue: formatUnitsAsNumber( @@ -119,13 +126,13 @@ const useFormattedDataV3 = (memberData: InfiniteData) => { }, }; + // todo get priceConversion for non-stable tokens const priceConversion = _.includes(tokenSymbol, 'xdai') ? 1 : undefined; - console.log('priceConversion', priceConversion); return { ...t, ...balance, - tokenSymbol, + tokenSymbol: 'lalsdasd', priceConversion, date: new Date(), tokenExplorerLink: `https://blockscout.com/xdai/mainnet/address/${t.tokenAddress}`, diff --git a/libs/dm-types/src/accounting.ts b/libs/dm-types/src/accounting.ts index 7babc287..49c25e0f 100644 --- a/libs/dm-types/src/accounting.ts +++ b/libs/dm-types/src/accounting.ts @@ -179,3 +179,68 @@ export type IMappedTokenPrice = { [key: string]: number; }; }; + +export type ITokenBalanceV3 = { + balance: string; + token: { + decimals: number; + logoUri: string; + name: string; + symbol: string; + }; + tokenAddress: string; +}; + +export type TransferV3 = { + blockNumber: number; + executionDate: string; + from: string; + to: string; + tokenAddress: string; + tokenId: null | string; + tokenInfo: { + address: string; + decimals: number; + logoUri: string; + name: string; + symbol: string; + trusted: boolean; + type: string; + }; + transactionHash: string; + transferId: string; + type: string; + value: string; +}; + +export type ITokenBalanceLineItemV3 = (ITokenBalanceV3 | TransferV3) & { + id: string; + tokenExplorerLink: string; + inflow: { + tokenValue: bigint; + }; + outflow: { + tokenValue: bigint; + }; + closing: { + tokenValue: bigint; + }; + priceConversion?: number; +}; + +export type Proposal = { + id: string; + createdAt: string; + createdBy: string; + proposedBy: string; + processTxHash: string; + proposalType: string; + description: string; + title: string; + txHash: string; +}; + +export interface RageQuit { + shares: string; + txHash: string; +} From e24045b22d4473c8a2538baf8d4ed1fe9e2f7643 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Wed, 26 Jun 2024 14:38:38 +0200 Subject: [PATCH 19/19] token symbol value --- libs/dm-hooks/src/useFormattedDataV3.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/dm-hooks/src/useFormattedDataV3.ts b/libs/dm-hooks/src/useFormattedDataV3.ts index ad88592c..88ab8c66 100644 --- a/libs/dm-hooks/src/useFormattedDataV3.ts +++ b/libs/dm-hooks/src/useFormattedDataV3.ts @@ -132,7 +132,7 @@ const useFormattedDataV3 = (memberData: InfiniteData) => { return { ...t, ...balance, - tokenSymbol: 'lalsdasd', + tokenSymbol, priceConversion, date: new Date(), tokenExplorerLink: `https://blockscout.com/xdai/mainnet/address/${t.tokenAddress}`,