diff --git a/.gitignore b/.gitignore index de242ced9..749300dd3 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ next-env.d.ts .claude .vscode .zed +.tempor +.agents diff --git a/src/actions/capitalDistributor/components/capitalDistributorCreateCampaignActionCreate/capitalDistributorCreateCampaignActionCreateForm.tsx b/src/actions/capitalDistributor/components/capitalDistributorCreateCampaignActionCreate/capitalDistributorCreateCampaignActionCreateForm.tsx index 01f9562f7..b6c7f0ede 100644 --- a/src/actions/capitalDistributor/components/capitalDistributorCreateCampaignActionCreate/capitalDistributorCreateCampaignActionCreateForm.tsx +++ b/src/actions/capitalDistributor/components/capitalDistributorCreateCampaignActionCreate/capitalDistributorCreateCampaignActionCreateForm.tsx @@ -12,7 +12,7 @@ import { } from '@aragon/gov-ui-kit'; import { useCallback, useRef } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; -import { CapitalFlowDaoSlotId } from '@/modules/capitalFlow/constants/moduleDaoSlots'; +import { CapitalDistributorDaoSlotId } from '@/actions/capitalDistributor/constants/capitalDistributorDaoSlotId'; import type { IAsset } from '@/modules/finance/api/financeService'; import { AssetInput } from '@/modules/finance/components/assetInput'; import { useDao } from '@/shared/api/daoService'; @@ -251,7 +251,7 @@ export const CapitalDistributorCreateCampaignActionCreateForm: React.FC< dao={dao} pluginId={dao.id} slotId={ - CapitalFlowDaoSlotId.CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD + CapitalDistributorDaoSlotId.CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD } /> )} diff --git a/src/modules/capitalFlow/constants/moduleDaoSlots.ts b/src/actions/capitalDistributor/constants/capitalDistributorDaoSlotId.ts similarity index 69% rename from src/modules/capitalFlow/constants/moduleDaoSlots.ts rename to src/actions/capitalDistributor/constants/capitalDistributorDaoSlotId.ts index 5a522c0cb..3d81d2d90 100644 --- a/src/modules/capitalFlow/constants/moduleDaoSlots.ts +++ b/src/actions/capitalDistributor/constants/capitalDistributorDaoSlotId.ts @@ -1,3 +1,3 @@ -export enum CapitalFlowDaoSlotId { +export enum CapitalDistributorDaoSlotId { CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD = 'CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD', } diff --git a/src/daos/capitalDistributorTest/constants/capitalDistributorTestDialogId.ts b/src/daos/capitalDistributorTest/constants/capitalDistributorTestDialogId.ts deleted file mode 100644 index 873589328..000000000 --- a/src/daos/capitalDistributorTest/constants/capitalDistributorTestDialogId.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum CapitalDistributorTestDialogId { - MEMBERS_FILE_DOWNLOAD = 'CAPITAL_DISTRIBUTOR_TEST_MEMBERS_FILE_DOWNLOAD', -} diff --git a/src/daos/capitalDistributorTest/constants/capitalDistributorTestDialogsDefinitions.ts b/src/daos/capitalDistributorTest/constants/capitalDistributorTestDialogsDefinitions.ts deleted file mode 100644 index 0769988e0..000000000 --- a/src/daos/capitalDistributorTest/constants/capitalDistributorTestDialogsDefinitions.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { IDialogComponentDefinitions } from '@/shared/components/dialogProvider'; -import { CapitalDistributorTestMembersFileDownloadDialog } from '../dialogs/capitalDistributorTestMembersFileDownloadDialog'; -import { CapitalDistributorTestDialogId } from './capitalDistributorTestDialogId'; - -export const capitalDistributorTestDialogsDefinitions: Record< - CapitalDistributorTestDialogId, - IDialogComponentDefinitions -> = { - [CapitalDistributorTestDialogId.MEMBERS_FILE_DOWNLOAD]: { - Component: CapitalDistributorTestMembersFileDownloadDialog, - }, -}; diff --git a/src/daos/capitalDistributorTest/constants/capitalDistributorTestMinEpochId.ts b/src/daos/capitalDistributorTest/constants/capitalDistributorTestMinEpochId.ts deleted file mode 100644 index f6aba991b..000000000 --- a/src/daos/capitalDistributorTest/constants/capitalDistributorTestMinEpochId.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Minimum valid epoch ID for the capitalDistributorTest DAO. - * Epochs below this value are invalid because the system was not deployed yet. - */ -export const capitalDistributorTestMinEpochId = 1460; diff --git a/src/daos/capitalDistributorTest/dialogs/capitalDistributorTestMembersFileDownloadDialog/index.ts b/src/daos/capitalDistributorTest/dialogs/capitalDistributorTestMembersFileDownloadDialog/index.ts deleted file mode 100644 index 365b53638..000000000 --- a/src/daos/capitalDistributorTest/dialogs/capitalDistributorTestMembersFileDownloadDialog/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import dynamic from 'next/dynamic'; - -export const CapitalDistributorTestMembersFileDownloadDialog = dynamic(() => - import('./capitalDistributorTestMembersFileDownloadDialog').then( - (mod) => mod.CapitalDistributorTestMembersFileDownloadDialog, - ), -); - -export type { ICapitalDistributorTestMembersFileDownloadDialogParams } from './capitalDistributorTestMembersFileDownloadDialog'; diff --git a/src/daos/capitalDistributorTest/index.ts b/src/daos/capitalDistributorTest/index.ts deleted file mode 100644 index b10a7e6ac..000000000 --- a/src/daos/capitalDistributorTest/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CapitalFlowDaoSlotId } from '@/modules/capitalFlow/constants/moduleDaoSlots'; -import { pluginRegistryUtils } from '@/shared/utils/pluginRegistryUtils'; -import { CapitalDistributorTestMembersFileDownload } from './components/capitalDistributorTestMembersFileDownload'; -import { capitalDistributorTestDao } from './constants/capitalDistributorTestDao'; - -export const initialiseCapitalDistributorTest = () => { - pluginRegistryUtils - .registerPlugin(capitalDistributorTestDao) - - .registerSlotComponent({ - slotId: CapitalFlowDaoSlotId.CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD, - pluginId: capitalDistributorTestDao.id, - component: CapitalDistributorTestMembersFileDownload, - }); -}; diff --git a/src/daos/cryptex/index.ts b/src/daos/cryptex/index.ts index 2b9b8a7d6..471d4ac0d 100644 --- a/src/daos/cryptex/index.ts +++ b/src/daos/cryptex/index.ts @@ -1,7 +1,9 @@ +import { CapitalDistributorDaoSlotId } from '@/actions/capitalDistributor/constants/capitalDistributorDaoSlotId'; import { DashboardDaoSlotId } from '@/modules/dashboard/constants/moduleDaoSlots'; import { pluginRegistryUtils } from '@/shared/utils/pluginRegistryUtils'; import { CryptexPageHeader } from './components/cryptexPageHeader'; import { cryptex } from './constants/cryptex'; +import { CryptexRewardsMembersFileDownload } from './rewards'; export const initialiseCryptex = () => { pluginRegistryUtils @@ -11,5 +13,11 @@ export const initialiseCryptex = () => { slotId: DashboardDaoSlotId.DASHBOARD_DAO_HEADER, pluginId: cryptex.id, component: CryptexPageHeader, + }) + + .registerSlotComponent({ + slotId: CapitalDistributorDaoSlotId.CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD, + pluginId: cryptex.id, + component: CryptexRewardsMembersFileDownload, }); }; diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/domain/index.ts b/src/daos/cryptex/rewards/api/tokenRewardService/domain/index.ts new file mode 100644 index 000000000..f007f2a61 --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/domain/index.ts @@ -0,0 +1,4 @@ +export type { + ITokenRewardDistribution, + ITokenRewardEntry, +} from './tokenReward'; diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/domain/tokenReward.ts b/src/daos/cryptex/rewards/api/tokenRewardService/domain/tokenReward.ts new file mode 100644 index 000000000..c0aae25cf --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/domain/tokenReward.ts @@ -0,0 +1,19 @@ +/** + * A single reward entry in the token reward distribution response. + */ +export interface ITokenRewardEntry { + /** + * Staker address receiving the reward. + */ + address: string; + /** + * Reward amount in wei. + */ + amount: string; +} + +/** + * Response from the GET /v2/token/rewards/:pluginAddress/:network endpoint. + * Array of reward entries where Σ amount === totalAmount from the request. + */ +export type ITokenRewardDistribution = ITokenRewardEntry[]; diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/index.ts b/src/daos/cryptex/rewards/api/tokenRewardService/index.ts new file mode 100644 index 000000000..ec2c8d1fe --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/index.ts @@ -0,0 +1,5 @@ +export type * from './domain'; +export * from './queries'; +export { tokenRewardService } from './tokenRewardService'; +export type * from './tokenRewardService.api'; +export { tokenRewardServiceKeys } from './tokenRewardServiceKeys'; diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/queries/index.ts b/src/daos/cryptex/rewards/api/tokenRewardService/queries/index.ts new file mode 100644 index 000000000..630a7d454 --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/queries/index.ts @@ -0,0 +1 @@ +export * from './useTokenRewardDistribution'; diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/queries/useTokenRewardDistribution/index.ts b/src/daos/cryptex/rewards/api/tokenRewardService/queries/useTokenRewardDistribution/index.ts new file mode 100644 index 000000000..5a51febff --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/queries/useTokenRewardDistribution/index.ts @@ -0,0 +1,4 @@ +export { + tokenRewardDistributionOptions, + useTokenRewardDistribution, +} from './useTokenRewardDistribution'; diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/queries/useTokenRewardDistribution/useTokenRewardDistribution.ts b/src/daos/cryptex/rewards/api/tokenRewardService/queries/useTokenRewardDistribution/useTokenRewardDistribution.ts new file mode 100644 index 000000000..6ec8d0514 --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/queries/useTokenRewardDistribution/useTokenRewardDistribution.ts @@ -0,0 +1,23 @@ +import { useQuery } from '@tanstack/react-query'; +import type { + QueryOptions, + SharedQueryOptions, +} from '@/shared/types/queryOptions'; +import type { ITokenRewardDistribution } from '../../domain'; +import { tokenRewardService } from '../../tokenRewardService'; +import type { IGetTokenRewardDistributionParams } from '../../tokenRewardService.api'; +import { tokenRewardServiceKeys } from '../../tokenRewardServiceKeys'; + +export const tokenRewardDistributionOptions = ( + params: IGetTokenRewardDistributionParams, + options?: QueryOptions, +): SharedQueryOptions => ({ + queryKey: tokenRewardServiceKeys.rewardDistribution(params), + queryFn: () => tokenRewardService.getRewardDistribution(params), + ...options, +}); + +export const useTokenRewardDistribution = ( + params: IGetTokenRewardDistributionParams, + options?: QueryOptions, +) => useQuery(tokenRewardDistributionOptions(params, options)); diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardService.api.ts b/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardService.api.ts new file mode 100644 index 000000000..952843b7b --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardService.api.ts @@ -0,0 +1,32 @@ +import type { Hex } from 'viem'; +import type { Network } from '@/shared/api/daoService'; +import type { IRequestUrlQueryParams } from '@/shared/api/httpService'; + +export interface IGetTokenRewardDistributionUrlParams { + /** + * Token voting governance plugin address. + */ + pluginAddress: Hex; + /** + * Network of the DAO. + */ + network: Network; +} + +export interface IGetTokenRewardDistributionQueryParams { + /** + * Total amount to distribute in wei. + */ + rewardTotalAmount: string; + /** + * ISO date string (e.g. "2026-01-15"). Backend includes proposals + * with endDate >= lookbackDate and endDate <= now. + */ + lookbackDate: string; +} + +export interface IGetTokenRewardDistributionParams + extends IRequestUrlQueryParams< + IGetTokenRewardDistributionUrlParams, + IGetTokenRewardDistributionQueryParams + > {} diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardService.ts b/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardService.ts new file mode 100644 index 000000000..a5151e500 --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardService.ts @@ -0,0 +1,25 @@ +import { AragonBackendService } from '@/shared/api/aragonBackendService'; +import type { ITokenRewardDistribution } from './domain'; +import type { IGetTokenRewardDistributionParams } from './tokenRewardService.api'; + +class TokenRewardService extends AragonBackendService { + private urls = { + rewardDistribution: '/v2/token/rewards/:pluginAddress/:network', + }; + + /** + * Computes the token voting reward distribution for a given plugin and lookback period. + * Returns per-staker reward entries based on governance participation. + */ + getRewardDistribution = async ( + params: IGetTokenRewardDistributionParams, + ): Promise => { + const result = await this.request( + this.urls.rewardDistribution, + params, + ); + return result; + }; +} + +export const tokenRewardService = new TokenRewardService(); diff --git a/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardServiceKeys.ts b/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardServiceKeys.ts new file mode 100644 index 000000000..b9466a2cb --- /dev/null +++ b/src/daos/cryptex/rewards/api/tokenRewardService/tokenRewardServiceKeys.ts @@ -0,0 +1,12 @@ +import type { IGetTokenRewardDistributionParams } from './tokenRewardService.api'; + +export enum TokenRewardServiceKey { + TOKEN_REWARD_DISTRIBUTION = 'TOKEN_REWARD_DISTRIBUTION', +} + +export const tokenRewardServiceKeys = { + rewardDistribution: (params: IGetTokenRewardDistributionParams) => [ + TokenRewardServiceKey.TOKEN_REWARD_DISTRIBUTION, + params, + ], +}; diff --git a/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/cryptexRewardsMembersFileDownload.api.ts b/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/cryptexRewardsMembersFileDownload.api.ts new file mode 100644 index 000000000..3cfcc1a3f --- /dev/null +++ b/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/cryptexRewardsMembersFileDownload.api.ts @@ -0,0 +1,13 @@ +import type { IAsset } from '@/modules/finance/api/financeService'; +import type { IDao } from '@/shared/api/daoService'; + +export interface ICryptexRewardsMembersFileDownloadProps { + /** + * DAO to display in the header. + */ + dao: IDao; + /** + * Asset selected in the campaign creation form, passed via PluginSingleComponent. + */ + asset?: IAsset; +} diff --git a/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/cryptexRewardsMembersFileDownload.tsx b/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/cryptexRewardsMembersFileDownload.tsx new file mode 100644 index 000000000..56dc73341 --- /dev/null +++ b/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/cryptexRewardsMembersFileDownload.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { Button, Card, IconType, InputContainer } from '@aragon/gov-ui-kit'; +import { useState } from 'react'; +import type { Hex } from 'viem'; +import type { IDaoPlugin } from '@/shared/api/daoService'; +import { PluginInterfaceType } from '@/shared/api/daoService'; +import { useDialogContext } from '@/shared/components/dialogProvider'; +import { useDaoPlugins } from '@/shared/hooks/useDaoPlugins'; +import { CryptexRewardsDialogId } from '../../constants/cryptexRewardsDialogId'; +import type { ICryptexRewardsMembersFileDownloadDialogParams } from '../../dialogs/cryptexRewardsMembersFileDownloadDialog'; +import type { ICryptexRewardsMembersFileDownloadProps } from './cryptexRewardsMembersFileDownload.api'; + +export const CryptexRewardsMembersFileDownload: React.FC< + ICryptexRewardsMembersFileDownloadProps +> = (props) => { + const { dao, asset } = props; + + const { open } = useDialogContext(); + + const [downloadedFileName, setDownloadedFileName] = useState( + null, + ); + + const governancePlugins = useDaoPlugins({ + daoId: dao.id, + interfaceType: PluginInterfaceType.TOKEN_VOTING, + includeSubPlugins: false, + }); + const governancePlugin = governancePlugins?.[0]?.meta as + | IDaoPlugin + | undefined; + + const handleClick = () => { + if (governancePlugin?.address == null) { + return; + } + + const params: ICryptexRewardsMembersFileDownloadDialogParams = { + pluginAddress: governancePlugin.address as Hex, + network: dao.network, + asset, + onDownload: setDownloadedFileName, + }; + + open(CryptexRewardsDialogId.CRYPTEX_REWARDS_MEMBERS_FILE_DOWNLOAD, { + params, + disableOutsideClick: true, + }); + }; + + return ( + + {downloadedFileName && ( + +

+ {downloadedFileName} +

+
+ )} + +
+ ); +}; diff --git a/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/index.ts b/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/index.ts new file mode 100644 index 000000000..10ab37f0d --- /dev/null +++ b/src/daos/cryptex/rewards/components/cryptexRewardsMembersFileDownload/index.ts @@ -0,0 +1,2 @@ +export { CryptexRewardsMembersFileDownload } from './cryptexRewardsMembersFileDownload'; +export type { ICryptexRewardsMembersFileDownloadProps } from './cryptexRewardsMembersFileDownload.api'; diff --git a/src/daos/cryptex/rewards/constants/cryptexRewardsDialogId.ts b/src/daos/cryptex/rewards/constants/cryptexRewardsDialogId.ts new file mode 100644 index 000000000..88ed87158 --- /dev/null +++ b/src/daos/cryptex/rewards/constants/cryptexRewardsDialogId.ts @@ -0,0 +1,3 @@ +export enum CryptexRewardsDialogId { + CRYPTEX_REWARDS_MEMBERS_FILE_DOWNLOAD = 'CRYPTEX_REWARDS_MEMBERS_FILE_DOWNLOAD', +} diff --git a/src/daos/cryptex/rewards/constants/cryptexRewardsDialogsDefinitions.ts b/src/daos/cryptex/rewards/constants/cryptexRewardsDialogsDefinitions.ts new file mode 100644 index 000000000..865eceb84 --- /dev/null +++ b/src/daos/cryptex/rewards/constants/cryptexRewardsDialogsDefinitions.ts @@ -0,0 +1,12 @@ +import type { IDialogComponentDefinitions } from '@/shared/components/dialogProvider'; +import { CryptexRewardsMembersFileDownloadDialog } from '../dialogs/cryptexRewardsMembersFileDownloadDialog'; +import { CryptexRewardsDialogId } from './cryptexRewardsDialogId'; + +export const cryptexRewardsDialogsDefinitions: Record< + CryptexRewardsDialogId, + IDialogComponentDefinitions +> = { + [CryptexRewardsDialogId.CRYPTEX_REWARDS_MEMBERS_FILE_DOWNLOAD]: { + Component: CryptexRewardsMembersFileDownloadDialog, + }, +}; diff --git a/src/daos/cryptex/rewards/dialogs/cryptexRewardsMembersFileDownloadDialog/cryptexRewardsMembersFileDownloadDialog.tsx b/src/daos/cryptex/rewards/dialogs/cryptexRewardsMembersFileDownloadDialog/cryptexRewardsMembersFileDownloadDialog.tsx new file mode 100644 index 000000000..24c55eec5 --- /dev/null +++ b/src/daos/cryptex/rewards/dialogs/cryptexRewardsMembersFileDownloadDialog/cryptexRewardsMembersFileDownloadDialog.tsx @@ -0,0 +1,152 @@ +'use client'; + +import { Dialog, InputDate, InputNumber, invariant } from '@aragon/gov-ui-kit'; +import { DateTime } from 'luxon'; +import { useState } from 'react'; +import { type Hex, parseUnits } from 'viem'; +import type { IAsset } from '@/modules/finance/api/financeService'; +import type { AragonBackendServiceError } from '@/shared/api/aragonBackendService'; +import type { Network } from '@/shared/api/daoService'; +import type { IDialogComponentProps } from '@/shared/components/dialogProvider'; +import { useDialogContext } from '@/shared/components/dialogProvider'; +import { useTokenRewardDistribution } from '../../api/tokenRewardService'; +import { CryptexRewardsDialogId } from '../../constants/cryptexRewardsDialogId'; + +export interface ICryptexRewardsMembersFileDownloadDialogParams { + /** + * Token voting governance plugin address. + */ + pluginAddress: Hex; + /** + * Network of the DAO. + */ + network: Network; + /** + * Asset selected in the campaign creation form. + */ + asset?: IAsset; + /** + * Callback when file is downloaded with the filename. + */ + onDownload: (fileName: string) => void; +} + +export interface ICryptexRewardsMembersFileDownloadDialogProps + extends IDialogComponentProps {} + +export const CryptexRewardsMembersFileDownloadDialog: React.FC< + ICryptexRewardsMembersFileDownloadDialogProps +> = (props) => { + const { location } = props; + + invariant( + location.params != null, + 'CryptexRewardsMembersFileDownloadDialog: params must be defined', + ); + + const { pluginAddress, network, asset, onDownload } = location.params; + const { close } = useDialogContext(); + + const [lookbackDate, setLookbackDate] = useState(''); + const [totalAmount, setTotalAmount] = useState(''); + + const tokenDecimals = asset?.token.decimals ?? 18; + const tokenSymbol = asset?.token.symbol; + + const rewardTotalAmountInWei = + totalAmount !== '' + ? parseUnits(totalAmount, tokenDecimals).toString() + : ''; + + const rewardDistribution = useTokenRewardDistribution( + { + urlParams: { pluginAddress, network }, + queryParams: { + rewardTotalAmount: rewardTotalAmountInWei, + lookbackDate, + }, + }, + { enabled: false }, + ); + + const errorCode = + rewardDistribution.error && + (rewardDistribution.error as AragonBackendServiceError).code; + + const isFormValid = lookbackDate !== '' && totalAmount !== ''; + + const handleGenerate = () => { + rewardDistribution.refetch().then((result) => { + if (result.data == null) { + return; + } + + const blob = new Blob([JSON.stringify(result.data, null, 2)], { + type: 'application/json', + }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + const fileName = `cryptex-rewards-${lookbackDate}.json`; + anchor.href = url; + anchor.download = fileName; + anchor.click(); + URL.revokeObjectURL(url); + onDownload(fileName); + handleClose(); + }); + }; + + const handleClose = () => { + close(CryptexRewardsDialogId.CRYPTEX_REWARDS_MEMBERS_FILE_DOWNLOAD); + }; + + const todayFormatted = DateTime.now().toFormat('yyyy-MM-dd'); + + return ( + <> + + +
+ + setLookbackDate(event.target.value) + } + value={lookbackDate} + /> + + {rewardDistribution.isError && ( +

+ {errorCode + ? `Failed to generate rewards: ${errorCode}` + : 'Failed to generate reward distribution. Please try again.'} +

+ )} +
+
+ + + ); +}; diff --git a/src/daos/cryptex/rewards/dialogs/cryptexRewardsMembersFileDownloadDialog/index.ts b/src/daos/cryptex/rewards/dialogs/cryptexRewardsMembersFileDownloadDialog/index.ts new file mode 100644 index 000000000..da6a984fb --- /dev/null +++ b/src/daos/cryptex/rewards/dialogs/cryptexRewardsMembersFileDownloadDialog/index.ts @@ -0,0 +1,12 @@ +import dynamic from 'next/dynamic'; + +export const CryptexRewardsMembersFileDownloadDialog = dynamic(() => + import('./cryptexRewardsMembersFileDownloadDialog').then( + (mod) => mod.CryptexRewardsMembersFileDownloadDialog, + ), +); + +export type { + ICryptexRewardsMembersFileDownloadDialogParams, + ICryptexRewardsMembersFileDownloadDialogProps, +} from './cryptexRewardsMembersFileDownloadDialog'; diff --git a/src/daos/cryptex/rewards/index.ts b/src/daos/cryptex/rewards/index.ts new file mode 100644 index 000000000..4423a34d7 --- /dev/null +++ b/src/daos/cryptex/rewards/index.ts @@ -0,0 +1,2 @@ +export { CryptexRewardsMembersFileDownload } from './components/cryptexRewardsMembersFileDownload'; +export { cryptexRewardsDialogsDefinitions } from './constants/cryptexRewardsDialogsDefinitions'; diff --git a/src/daos/index.ts b/src/daos/index.ts index 44a58696d..97de72438 100644 --- a/src/daos/index.ts +++ b/src/daos/index.ts @@ -1,19 +1,22 @@ import { initialiseAragonDemo } from './aragonDemo'; import { initialiseAragonDemoPolicies } from './aragonDemoPolicies'; import { initialiseBoundless } from './boundless'; -import { initialiseCapitalDistributorTest } from './capitalDistributorTest'; import { initialiseCryptex } from './cryptex'; -import { initialiseKatanaCDDemo } from './katanaCDDemo'; -import { initialiseKatanaEmissionsTest } from './katanaEmissionsTest'; +import { cryptexRewardsDialogsDefinitions } from './cryptex/rewards'; +import { initialiseKatana } from './katana'; +import { katanaRewardsDialogsDefinitions } from './katana/rewards'; import { initialiseXmaquina } from './xmaquina'; +export const capitalDistributorDialogsDefinitions = { + ...katanaRewardsDialogsDefinitions, + ...cryptexRewardsDialogsDefinitions, +}; + export const initialiseDaos = () => { initialiseAragonDemo(); initialiseBoundless(); initialiseCryptex(); initialiseXmaquina(); initialiseAragonDemoPolicies(); - initialiseCapitalDistributorTest(); - initialiseKatanaCDDemo(); - initialiseKatanaEmissionsTest(); + initialiseKatana(); }; diff --git a/src/daos/capitalDistributorTest/constants/capitalDistributorTestDao.ts b/src/daos/katana/constants/capitalDistributorTestDao.ts similarity index 100% rename from src/daos/capitalDistributorTest/constants/capitalDistributorTestDao.ts rename to src/daos/katana/constants/capitalDistributorTestDao.ts diff --git a/src/daos/katanaCDDemo/constants/katanaCDDemo.ts b/src/daos/katana/constants/katanaCDDemo.ts similarity index 100% rename from src/daos/katanaCDDemo/constants/katanaCDDemo.ts rename to src/daos/katana/constants/katanaCDDemo.ts diff --git a/src/daos/katanaEmissionsTest/constants/katanaEmissionsTest.ts b/src/daos/katana/constants/katanaEmissionsTest.ts similarity index 100% rename from src/daos/katanaEmissionsTest/constants/katanaEmissionsTest.ts rename to src/daos/katana/constants/katanaEmissionsTest.ts diff --git a/src/daos/katana/index.ts b/src/daos/katana/index.ts new file mode 100644 index 000000000..f40833f8e --- /dev/null +++ b/src/daos/katana/index.ts @@ -0,0 +1,23 @@ +import { CapitalDistributorDaoSlotId } from '@/actions/capitalDistributor/constants/capitalDistributorDaoSlotId'; +import { pluginRegistryUtils } from '@/shared/utils/pluginRegistryUtils'; +import { capitalDistributorTestDao } from './constants/capitalDistributorTestDao'; +import { katanaCDDemo } from './constants/katanaCDDemo'; +import { katanaEmissionsTest } from './constants/katanaEmissionsTest'; +import { KatanaRewardsMembersFileDownload } from './rewards'; + +export const initialiseKatana = () => { + for (const dao of [ + capitalDistributorTestDao, + katanaCDDemo, + katanaEmissionsTest, + ]) { + pluginRegistryUtils + .registerPlugin(dao) + + .registerSlotComponent({ + slotId: CapitalDistributorDaoSlotId.CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD, + pluginId: dao.id, + component: KatanaRewardsMembersFileDownload, + }); + } +}; diff --git a/src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/index.ts b/src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/index.ts new file mode 100644 index 000000000..1b98b3b4a --- /dev/null +++ b/src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/index.ts @@ -0,0 +1,2 @@ +export { KatanaRewardsMembersFileDownload } from './katanaRewardsMembersFileDownload'; +export type { IKatanaRewardsMembersFileDownloadProps } from './katanaRewardsMembersFileDownload.api'; diff --git a/src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/katanaRewardsMembersFileDownload.api.ts b/src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/katanaRewardsMembersFileDownload.api.ts new file mode 100644 index 000000000..dca0fdec2 --- /dev/null +++ b/src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/katanaRewardsMembersFileDownload.api.ts @@ -0,0 +1,13 @@ +import type { IAsset } from '@/modules/finance/api/financeService'; +import type { IDao } from '@/shared/api/daoService'; + +export interface IKatanaRewardsMembersFileDownloadProps { + /** + * DAO to display in the header. + */ + dao: IDao; + /** + * Asset selected in the campaign creation form, passed via PluginSingleComponent. + */ + asset?: IAsset; +} diff --git a/src/daos/capitalDistributorTest/components/capitalDistributorTestMembersFileDownload.tsx b/src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/katanaRewardsMembersFileDownload.tsx similarity index 72% rename from src/daos/capitalDistributorTest/components/capitalDistributorTestMembersFileDownload.tsx rename to src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/katanaRewardsMembersFileDownload.tsx index de506c775..4bcb3b499 100644 --- a/src/daos/capitalDistributorTest/components/capitalDistributorTestMembersFileDownload.tsx +++ b/src/daos/katana/rewards/components/katanaRewardsMembersFileDownload/katanaRewardsMembersFileDownload.tsx @@ -2,28 +2,17 @@ import { Button, Card, IconType, InputContainer } from '@aragon/gov-ui-kit'; import { useState } from 'react'; -import type { IAsset } from '@/modules/finance/api/financeService'; import type { IGaugeVoterPlugin } from '@/plugins/gaugeVoterPlugin/types'; -import { type IDao, PluginInterfaceType } from '@/shared/api/daoService'; +import { PluginInterfaceType } from '@/shared/api/daoService'; import { useDialogContext } from '@/shared/components/dialogProvider'; import { useTranslations } from '@/shared/components/translationsProvider'; import { useDaoPlugins } from '@/shared/hooks/useDaoPlugins'; -import { CapitalDistributorTestDialogId } from '../constants/capitalDistributorTestDialogId'; -import type { ICapitalDistributorTestMembersFileDownloadDialogParams } from '../dialogs/capitalDistributorTestMembersFileDownloadDialog'; +import { KatanaRewardsDialogId } from '../../constants/katanaRewardsDialogId'; +import type { IKatanaRewardsMembersFileDownloadDialogParams } from '../../dialogs/katanaRewardsMembersFileDownloadDialog'; +import type { IKatanaRewardsMembersFileDownloadProps } from './katanaRewardsMembersFileDownload.api'; -export interface ICapitalDistributorTestMembersFileDownloadProps { - /** - * DAO to display in the header. - */ - dao: IDao; - /** - * Asset selected in the campaign creation form, passed via PluginSingleComponent. - */ - asset?: IAsset; -} - -export const CapitalDistributorTestMembersFileDownload: React.FC< - ICapitalDistributorTestMembersFileDownloadProps +export const KatanaRewardsMembersFileDownload: React.FC< + IKatanaRewardsMembersFileDownloadProps > = (props) => { const { dao, asset } = props; @@ -46,14 +35,14 @@ export const CapitalDistributorTestMembersFileDownload: React.FC< return; } - const params: ICapitalDistributorTestMembersFileDownloadDialogParams = { + const params: IKatanaRewardsMembersFileDownloadDialogParams = { gaugePlugin, network: dao.network, asset, onDownload: setDownloadedFileName, }; - open(CapitalDistributorTestDialogId.MEMBERS_FILE_DOWNLOAD, { + open(KatanaRewardsDialogId.KATANA_REWARDS_MEMBERS_FILE_DOWNLOAD, { params, disableOutsideClick: true, }); @@ -64,7 +53,7 @@ export const CapitalDistributorTestMembersFileDownload: React.FC< helpText={t( 'app.daos.capitalDistributorTest.capitalDistributorTestMembersFileDownload.helpText', )} - id="katana-members-file" + id="katana-rewards-members-file" label={t( 'app.daos.capitalDistributorTest.capitalDistributorTestMembersFileDownload.label', )} diff --git a/src/daos/katana/rewards/constants/katanaRewardsDialogId.ts b/src/daos/katana/rewards/constants/katanaRewardsDialogId.ts new file mode 100644 index 000000000..23dd03439 --- /dev/null +++ b/src/daos/katana/rewards/constants/katanaRewardsDialogId.ts @@ -0,0 +1,3 @@ +export enum KatanaRewardsDialogId { + KATANA_REWARDS_MEMBERS_FILE_DOWNLOAD = 'KATANA_REWARDS_MEMBERS_FILE_DOWNLOAD', +} diff --git a/src/daos/katana/rewards/constants/katanaRewardsDialogsDefinitions.ts b/src/daos/katana/rewards/constants/katanaRewardsDialogsDefinitions.ts new file mode 100644 index 000000000..6e871512a --- /dev/null +++ b/src/daos/katana/rewards/constants/katanaRewardsDialogsDefinitions.ts @@ -0,0 +1,12 @@ +import type { IDialogComponentDefinitions } from '@/shared/components/dialogProvider'; +import { KatanaRewardsMembersFileDownloadDialog } from '../dialogs/katanaRewardsMembersFileDownloadDialog'; +import { KatanaRewardsDialogId } from './katanaRewardsDialogId'; + +export const katanaRewardsDialogsDefinitions: Record< + KatanaRewardsDialogId, + IDialogComponentDefinitions +> = { + [KatanaRewardsDialogId.KATANA_REWARDS_MEMBERS_FILE_DOWNLOAD]: { + Component: KatanaRewardsMembersFileDownloadDialog, + }, +}; diff --git a/src/daos/katana/rewards/constants/katanaRewardsMinEpochId.ts b/src/daos/katana/rewards/constants/katanaRewardsMinEpochId.ts new file mode 100644 index 000000000..dfa34c972 --- /dev/null +++ b/src/daos/katana/rewards/constants/katanaRewardsMinEpochId.ts @@ -0,0 +1,5 @@ +/** + * Minimum valid epoch ID for Katana reward distributions. + * Epochs below this value are invalid because the system was not deployed yet. + */ +export const katanaRewardsMinEpochId = 1460; diff --git a/src/daos/katana/rewards/dialogs/katanaRewardsMembersFileDownloadDialog/index.ts b/src/daos/katana/rewards/dialogs/katanaRewardsMembersFileDownloadDialog/index.ts new file mode 100644 index 000000000..0f79a7276 --- /dev/null +++ b/src/daos/katana/rewards/dialogs/katanaRewardsMembersFileDownloadDialog/index.ts @@ -0,0 +1,9 @@ +import dynamic from 'next/dynamic'; + +export const KatanaRewardsMembersFileDownloadDialog = dynamic(() => + import('./katanaRewardsMembersFileDownloadDialog').then( + (mod) => mod.KatanaRewardsMembersFileDownloadDialog, + ), +); + +export type { IKatanaRewardsMembersFileDownloadDialogParams } from './katanaRewardsMembersFileDownloadDialog'; diff --git a/src/daos/capitalDistributorTest/dialogs/capitalDistributorTestMembersFileDownloadDialog/capitalDistributorTestMembersFileDownloadDialog.tsx b/src/daos/katana/rewards/dialogs/katanaRewardsMembersFileDownloadDialog/katanaRewardsMembersFileDownloadDialog.tsx similarity index 91% rename from src/daos/capitalDistributorTest/dialogs/capitalDistributorTestMembersFileDownloadDialog/capitalDistributorTestMembersFileDownloadDialog.tsx rename to src/daos/katana/rewards/dialogs/katanaRewardsMembersFileDownloadDialog/katanaRewardsMembersFileDownloadDialog.tsx index 249084d27..5fcd651c1 100644 --- a/src/daos/capitalDistributorTest/dialogs/capitalDistributorTestMembersFileDownloadDialog/capitalDistributorTestMembersFileDownloadDialog.tsx +++ b/src/daos/katana/rewards/dialogs/katanaRewardsMembersFileDownloadDialog/katanaRewardsMembersFileDownloadDialog.tsx @@ -21,11 +21,11 @@ import type { Network } from '@/shared/api/daoService'; import type { IDialogComponentProps } from '@/shared/components/dialogProvider'; import { useDialogContext } from '@/shared/components/dialogProvider'; import { useTranslations } from '@/shared/components/translationsProvider'; -import { CapitalDistributorTestDialogId } from '../../constants/capitalDistributorTestDialogId'; -import { capitalDistributorTestMinEpochId } from '../../constants/capitalDistributorTestMinEpochId'; +import { KatanaRewardsDialogId } from '../../constants/katanaRewardsDialogId'; +import { katanaRewardsMinEpochId } from '../../constants/katanaRewardsMinEpochId'; import { rewardUtils } from '../../utils/rewardUtils'; -export interface ICapitalDistributorTestMembersFileDownloadDialogParams { +export interface IKatanaRewardsMembersFileDownloadDialogParams { /** * Gauge plugin. */ @@ -44,17 +44,17 @@ export interface ICapitalDistributorTestMembersFileDownloadDialogParams { onDownload?: (fileName: string) => void; } -export interface ICapitalDistributorTestMembersFileDownloadDialogProps - extends IDialogComponentProps {} +export interface IKatanaRewardsMembersFileDownloadDialogProps + extends IDialogComponentProps {} -export const CapitalDistributorTestMembersFileDownloadDialog: React.FC< - ICapitalDistributorTestMembersFileDownloadDialogProps +export const KatanaRewardsMembersFileDownloadDialog: React.FC< + IKatanaRewardsMembersFileDownloadDialogProps > = (props) => { const { location } = props; invariant( location.params != null, - 'CapitalDistributorTestMembersFileDownloadDialog: params must be defined', + 'KatanaRewardsMembersFileDownloadDialog: params must be defined', ); const { gaugePlugin, network, asset } = location.params; @@ -91,9 +91,9 @@ export const CapitalDistributorTestMembersFileDownloadDialog: React.FC< const epochs: number[] = []; const firstEpoch = - capitalDistributorTestMinEpochId > currentEpochId + katanaRewardsMinEpochId > currentEpochId ? 1 - : capitalDistributorTestMinEpochId; + : katanaRewardsMinEpochId; for ( let i = currentEpochId; @@ -174,7 +174,7 @@ export const CapitalDistributorTestMembersFileDownloadDialog: React.FC< }; const handleClose = () => { - close(CapitalDistributorTestDialogId.MEMBERS_FILE_DOWNLOAD); + close(KatanaRewardsDialogId.KATANA_REWARDS_MEMBERS_FILE_DOWNLOAD); }; const selectedEpochLabel = diff --git a/src/daos/katana/rewards/index.ts b/src/daos/katana/rewards/index.ts new file mode 100644 index 000000000..55051bbe2 --- /dev/null +++ b/src/daos/katana/rewards/index.ts @@ -0,0 +1,2 @@ +export { KatanaRewardsMembersFileDownload } from './components/katanaRewardsMembersFileDownload'; +export { katanaRewardsDialogsDefinitions } from './constants/katanaRewardsDialogsDefinitions'; diff --git a/src/daos/capitalDistributorTest/utils/rewardUtils/index.ts b/src/daos/katana/rewards/utils/rewardUtils/index.ts similarity index 100% rename from src/daos/capitalDistributorTest/utils/rewardUtils/index.ts rename to src/daos/katana/rewards/utils/rewardUtils/index.ts diff --git a/src/daos/capitalDistributorTest/utils/rewardUtils/rewardUtils.api.ts b/src/daos/katana/rewards/utils/rewardUtils/rewardUtils.api.ts similarity index 100% rename from src/daos/capitalDistributorTest/utils/rewardUtils/rewardUtils.api.ts rename to src/daos/katana/rewards/utils/rewardUtils/rewardUtils.api.ts diff --git a/src/daos/capitalDistributorTest/utils/rewardUtils/rewardUtils.test.ts b/src/daos/katana/rewards/utils/rewardUtils/rewardUtils.test.ts similarity index 100% rename from src/daos/capitalDistributorTest/utils/rewardUtils/rewardUtils.test.ts rename to src/daos/katana/rewards/utils/rewardUtils/rewardUtils.test.ts diff --git a/src/daos/capitalDistributorTest/utils/rewardUtils/rewardUtils.ts b/src/daos/katana/rewards/utils/rewardUtils/rewardUtils.ts similarity index 100% rename from src/daos/capitalDistributorTest/utils/rewardUtils/rewardUtils.ts rename to src/daos/katana/rewards/utils/rewardUtils/rewardUtils.ts diff --git a/src/daos/katanaCDDemo/index.ts b/src/daos/katanaCDDemo/index.ts deleted file mode 100644 index 5c963dad9..000000000 --- a/src/daos/katanaCDDemo/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CapitalFlowDaoSlotId } from '@/modules/capitalFlow/constants/moduleDaoSlots'; -import { pluginRegistryUtils } from '@/shared/utils/pluginRegistryUtils'; -import { CapitalDistributorTestMembersFileDownload } from '../capitalDistributorTest/components/capitalDistributorTestMembersFileDownload'; -import { katanaCDDemo } from './constants/katanaCDDemo'; - -export const initialiseKatanaCDDemo = () => { - pluginRegistryUtils - .registerPlugin(katanaCDDemo) - - .registerSlotComponent({ - slotId: CapitalFlowDaoSlotId.CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD, - pluginId: katanaCDDemo.id, - component: CapitalDistributorTestMembersFileDownload, - }); -}; diff --git a/src/daos/katanaEmissionsTest/index.ts b/src/daos/katanaEmissionsTest/index.ts deleted file mode 100644 index e1a61a48b..000000000 --- a/src/daos/katanaEmissionsTest/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CapitalFlowDaoSlotId } from '@/modules/capitalFlow/constants/moduleDaoSlots'; -import { pluginRegistryUtils } from '@/shared/utils/pluginRegistryUtils'; -import { CapitalDistributorTestMembersFileDownload } from '../capitalDistributorTest/components/capitalDistributorTestMembersFileDownload'; -import { katanaEmissionsTest } from './constants/katanaEmissionsTest'; - -export const initialiseKatanaEmissionsTest = () => { - pluginRegistryUtils - .registerPlugin(katanaEmissionsTest) - - .registerSlotComponent({ - slotId: CapitalFlowDaoSlotId.CAPITAL_DISTRIBUTOR_MEMBERS_FILE_DOWNLOAD, - pluginId: katanaEmissionsTest.id, - component: CapitalDistributorTestMembersFileDownload, - }); -}; diff --git a/src/modules/application/components/providers/providersDialogs.ts b/src/modules/application/components/providers/providersDialogs.ts index f0a5ca773..39928acef 100644 --- a/src/modules/application/components/providers/providersDialogs.ts +++ b/src/modules/application/components/providers/providersDialogs.ts @@ -1,5 +1,5 @@ import { actionsDialogsDefinitions } from '@/actions'; -import { capitalDistributorTestDialogsDefinitions } from '@/daos/capitalDistributorTest/constants/capitalDistributorTestDialogsDefinitions'; +import { capitalDistributorDialogsDefinitions } from '@/daos'; import { applicationDialogsDefinitions } from '@/modules/application/constants/applicationDialogsDefinitions'; import { capitalFlowDialogsDefinitions } from '@/modules/capitalFlow/constants/capitalFlowDialogsDefinitions'; import { createDaoDialogsDefinitions } from '@/modules/createDao/constants/createDaoDialogsDefinitions'; @@ -18,5 +18,5 @@ export const providersDialogs: Record = { ...settingsDialogDefinitions, ...capitalFlowDialogsDefinitions, ...actionsDialogsDefinitions, - ...capitalDistributorTestDialogsDefinitions, + ...capitalDistributorDialogsDefinitions, };