Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V4 Download holders modal #4451

Merged
merged 2 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/packages/v1/components/V1Project/TokensSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Button, Descriptions, Space, Statistic } from 'antd'
import { IssueErc20TokenButton } from 'components/buttons/IssueErc20TokenButton'
import EthereumAddress from 'components/EthereumAddress'
import ManageTokensModal from 'components/modals/ManageTokensModal'
import ParticipantsModal from 'components/modals/ParticipantsModal'
import SectionHeader from 'components/SectionHeader'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import { BigNumber } from 'ethers'
Expand All @@ -19,6 +18,7 @@ import { useV1UnclaimedBalance } from 'packages/v1/hooks/contractReader/useV1Unc
import { useTransferTokensTx } from 'packages/v1/hooks/transactor/useTransferTokensTx'
import { V1OperatorPermission } from 'packages/v1/models/permissions'
import { decodeFundingCycleMetadata } from 'packages/v1/utils/fundingCycle'
import ParticipantsModal from 'packages/v2v3/components/V2V3Project/modals/ParticipantsModal'
import { CSSProperties, useContext, useState } from 'react'
import { isZeroAddress } from 'utils/address'
import { formatPercent, formatWad } from 'utils/format/formatNumber'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ParticipantsModal from 'components/modals/ParticipantsModal'
import ParticipantsModal from 'packages/v2v3/components/V2V3Project/modals/ParticipantsModal'
import { useProjectContext } from 'packages/v2v3/components/V2V3Project/ProjectDashboard/hooks/useProjectContext'

// TODO: This is hacked together - we should consider rebuilding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { PV } from 'models/pv'
import { useState } from 'react'
import { formatPercent } from 'utils/format/formatNumber'
import { tokenSymbolText } from 'utils/tokenSymbolText'
import { DownloadParticipantsModal } from '../DownloadParticipantsModal'
import { DownloadParticipantsModal } from './DownloadParticipantsModal'

interface ParticipantOption {
label: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import { isZeroAddress } from 'utils/address'
import { tokenSymbolText } from 'utils/tokenSymbolText'

import { useQuery } from '@tanstack/react-query'
import TokenDistributionChart from 'components/TokenDistributionChart'
import {
OrderDirection,
Participant_OrderBy,
ParticipantsDocument,
ParticipantsQuery,
QueryParticipantsArgs,
Participant_OrderBy as V1V2V3Participant_OrderBy,
ParticipantsDocument as V1V2V3ParticipantsDocument,
} from 'generated/graphql'
import { client } from 'lib/apollo/client'
import { paginateDepleteQuery } from 'lib/apollo/paginateDepleteQuery'
import TokenDistributionChart from 'packages/v2v3/components/V2V3Project/modals/ParticipantsModal/TokenDistributionChart'
import HoldersList from './HoldersList'

export default function ParticipantsModal({
Expand All @@ -41,20 +41,19 @@ export default function ParticipantsModal({
queryFn: () =>
paginateDepleteQuery<ParticipantsQuery, QueryParticipantsArgs>({
client,
document: ParticipantsDocument,
document: V1V2V3ParticipantsDocument,
variables: {
orderDirection: OrderDirection.desc,
orderBy: Participant_OrderBy.balance,
orderBy: V1V2V3Participant_OrderBy.balance,
where: {
projectId,
pv,
balance_gt: BigNumber.from(0),
wallet_not: constants.AddressZero,
},
},
}
}),
staleTime: 5 * 60 * 1000, // 5 min
enabled: Boolean(projectId && pv && open),
enabled: Boolean(projectId && open),
})

return (
Expand Down
28 changes: 0 additions & 28 deletions src/packages/v4/components/V4TokenHoldersModal.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { t, Trans } from '@lingui/macro'
import { Modal } from 'antd'
import InputAccessoryButton from 'components/buttons/InputAccessoryButton'
import FormattedNumberInput from 'components/inputs/FormattedNumberInput'

import { useBlockNumber } from 'hooks/useBlockNumber'
import { useJBContractContext } from 'juice-sdk-react'
import { ParticipantsDownloadDocument } from 'packages/v4/graphql/client/graphql'
import { useSubgraphQuery } from 'packages/v4/graphql/useSubgraphQuery'
import { useCallback, useEffect, useState } from 'react'
import { downloadCsvFile } from 'utils/csv'
import { fromWad } from 'utils/format/formatNumber'
import { emitErrorNotification } from 'utils/notifications'
import { tokenSymbolText } from 'utils/tokenSymbolText'

export function DownloadTokenHoldersModal({
tokenSymbol,
open,
onCancel,
}: {
tokenSymbol: string | undefined
open: boolean | undefined
onCancel: VoidFunction | undefined
}) {
const { projectId } = useJBContractContext()

const [blockNumber, setBlockNumber] = useState<number>()
const [loading, setLoading] = useState<boolean>()

// Use block number 5 blocks behind chain head to allow for subgraph being a bit behind on indexing.
const { data: latestBlockNumber } = useBlockNumber({ behindChainHeight: 5 })

const { data } = useSubgraphQuery({
document: ParticipantsDownloadDocument,
variables: {
where: {
projectId: Number(projectId),
},
block: {
number: blockNumber
}
},
enabled: Boolean(projectId && open),
})

const participants = data?.participants

useEffect(() => {
setBlockNumber(latestBlockNumber)
}, [latestBlockNumber])

const download = useCallback(async () => {
if (blockNumber === undefined || !projectId) return

const rows = [
[
'Wallet address',
`Total ${tokenSymbolText({ tokenSymbol })} balance`,
'Unclaimed balance',
'Claimed balance',
'Total ETH paid',
'Last paid timestamp',
], // CSV header row
]

setLoading(true)
try {
if (!participants) {
emitErrorNotification(t`Error loading holders`)
throw new Error('No data.')
}

participants.forEach(p => {
let date = new Date((p.lastPaidTimestamp ?? 0) * 1000).toUTCString()

if (date.includes(',')) date = date.split(',')[1]

rows.push([
p.wallet.id ?? '--',
fromWad(p.balance),
fromWad(p.stakedBalance),
fromWad(p.erc20Balance),
fromWad(p.volume),
date,
])
})

downloadCsvFile(
`@v4-project-${projectId}_holders-block${blockNumber}.csv`,
rows,
)

setLoading(false)
} catch (e) {
console.error('Error downloading participants', e)
setLoading(false)
}
}, [blockNumber, projectId, tokenSymbol, participants])

return (
<Modal
open={open}
onCancel={onCancel}
onOk={download}
okText={t`Download CSV`}
okButtonProps={{ type: 'primary' }}
cancelText={t`Close`}
confirmLoading={loading}
centered
>
<div>
<h4>
<Trans>
Download CSV of {tokenSymbolText({ tokenSymbol })} holders
</Trans>
</h4>

<label className="mt-5 mb-1 block">
<Trans>Block number</Trans>
</label>
<FormattedNumberInput
value={blockNumber?.toString()}
onChange={val => setBlockNumber(val ? parseInt(val) : undefined)}
accessory={
<InputAccessoryButton
content={t`Latest`}
onClick={() => setBlockNumber(latestBlockNumber)}
disabled={blockNumber === latestBlockNumber}
/>
}
/>
</div>
</Modal>
)
}
Loading
Loading