diff --git a/packages/state/query/queries/contract.ts b/packages/state/query/queries/contract.ts index 9125aa5c0..851150d2f 100644 --- a/packages/state/query/queries/contract.ts +++ b/packages/state/query/queries/contract.ts @@ -3,6 +3,10 @@ import { fromUtf8, toUtf8 } from '@cosmjs/encoding' import { QueryClient, queryOptions, skipToken } from '@tanstack/react-query' import { InfoResponse } from '@dao-dao/types' +import { + ArrayOfVestingContract, + VestingContract, +} from '@dao-dao/types/contracts/CwPayrollFactory' import { CodeInfoResponse } from '@dao-dao/types/protobuf/codegen/cosmwasm/wasm/v1/query' import { AccessType } from '@dao-dao/types/protobuf/codegen/cosmwasm/wasm/v1/types' import { @@ -19,6 +23,7 @@ import { } from '@dao-dao/utils' import { chainQueries } from './chain' +import { cwVestingQueries } from './contracts' import { indexerQueries } from './indexer' /** @@ -311,6 +316,54 @@ export const generateInstantiate2Address = async ( ) } +/** + * List all vesting contracts owned by a given account. + */ +export const listVestingContractsOwnedByAccount = async ( + queryClient: QueryClient, + { + chainId, + address, + }: { + chainId: string + address: string + } +): Promise<{ + chainId: string + contracts: ArrayOfVestingContract +}> => { + const vestingContracts = await queryClient.fetchQuery( + contractQueries.listContractsOwnedByAccount(queryClient, { + chainId, + address, + key: 'cw-vesting', + }) + ) + + const contracts = await Promise.all( + vestingContracts.map( + async (contract): Promise => ({ + contract, + recipient: ( + await queryClient.fetchQuery( + cwVestingQueries.info(queryClient, { + chainId, + contractAddress: contract, + }) + ) + ).recipient, + // Ignore. + instantiator: '', + }) + ) + ) + + return { + chainId, + contracts, + } +} + export const contractQueries = { /** * Fetch contract info stored in state, which contains its name and version. @@ -431,4 +484,41 @@ export const contractQueries = { queryKey: ['contract', 'instantiate2Address', options], queryFn: () => generateInstantiate2Address(queryClient, options), }), + /** + * List all contracts owned by a given account. + */ + listContractsOwnedByAccount: ( + queryClient: QueryClient, + { + chainId, + address, + key, + }: { + chainId: string + address: string + /** + * Optionally filter by an indexer code ID key. + */ + key?: string + } + ) => + indexerQueries.queryAccount(queryClient, { + chainId, + address, + formula: 'contract/ownedBy', + args: { + key, + }, + }), + /** + * List all vesting contracts owned by a given account. + */ + listVestingContractsOwnedByAccount: ( + queryClient: QueryClient, + options: Parameters[1] + ) => + queryOptions({ + queryKey: ['contract', 'listVestingContractsOwnedByAccount', options], + queryFn: () => listVestingContractsOwnedByAccount(queryClient, options), + }), } diff --git a/packages/stateful/widgets/widgets/VestingPayments/Renderer/TabRenderer/index.tsx b/packages/stateful/widgets/widgets/VestingPayments/Renderer/TabRenderer/index.tsx index 05e9008fa..d32f296fa 100644 --- a/packages/stateful/widgets/widgets/VestingPayments/Renderer/TabRenderer/index.tsx +++ b/packages/stateful/widgets/widgets/VestingPayments/Renderer/TabRenderer/index.tsx @@ -1,6 +1,8 @@ import { useQueries, useQueryClient } from '@tanstack/react-query' +import uniqBy from 'lodash.uniqby' import { + contractQueries, cwPayrollFactoryExtraQueries, cwVestingExtraQueries, } from '@dao-dao/state/query' @@ -31,7 +33,7 @@ import { TabRenderer as StatelessTabRenderer } from './TabRenderer' export const TabRenderer = ({ variables: { factories, factory, oldFactories }, }: WidgetRendererProps) => { - const { chainId: defaultChainId, coreAddress } = useDaoInfoContext() + const { chainId: defaultChainId, coreAddress, accounts } = useDaoInfoContext() const { getDaoProposalPath } = useDaoNavHelpers() const { isMember = false } = useMembership() @@ -67,11 +69,22 @@ export const TabRenderer = ({ address, }) ), + + // Contracts owned by any of this DAO's accounts. This detects contracts + // whose ownership was transferred to this DAO but that are still part of + // a different factory. + ...accounts.map(({ chainId, address }) => + contractQueries.listVestingContractsOwnedByAccount(queryClient, { + chainId, + address, + }) + ), ], combine: makeCombineQueryResultsIntoLoadingDataWithError({ firstLoad: 'one', }), }) + // Fetch infos individually so they refresh when data is updated elsewhere. const vestingInfosLoading = useQueries({ queries: @@ -87,6 +100,13 @@ export const TabRenderer = ({ ), combine: makeCombineQueryResultsIntoLoadingDataWithError({ firstLoad: 'one', + // De-dupe since the ownership queries will overlap with the factory list + // queries. + transform: (infos) => + uniqBy( + infos, + (info) => info.chainId + ':' + info.vestingContractAddress + ), }), })