diff --git a/packages/i18n/locales/en/translation.json b/packages/i18n/locales/en/translation.json index f620ea55b..56dbee234 100644 --- a/packages/i18n/locales/en/translation.json +++ b/packages/i18n/locales/en/translation.json @@ -240,6 +240,7 @@ "downArrow": "Down arrow", "family": "Family", "fileFolder": "File folder", + "filmSlate": "Film slate", "fire": "Fire", "gas": "Gas", "gear": "Gear", @@ -295,6 +296,9 @@ "counterpartyBalanceInsufficient": "The counterparty's balance of {{amount}} ${{tokenSymbol}} is insufficient. They may be unable to complete this swap.", "daoAndSubDaosAlreadyOnV2": "This DAO (and all of its SubDAOs, if it has any) have already been upgraded.", "daoFeatureUnsupported": "{{name}} does not support {{feature}} yet.", + "daoIsInactive_absolute_one": "This DAO is inactive. Proposals cannot be created until {{count}} token is staked.", + "daoIsInactive_absolute_other": "This DAO is inactive. Proposals cannot be created until {{count}} tokens are staked.", + "daoIsInactive_percent": "This DAO is inactive. Proposals cannot be created until {{percent}} of voting power is staked.", "daoIsPaused": "You cannot create a proposal when the DAO is paused.", "discordAuthFailed": "Discord authentication unexpectedly failed. Try again or reach out to us for assistance.", "emptyFile": "File is empty.", @@ -672,6 +676,7 @@ "actionWithdrawsTokenSwap_pending": "This action withdraws {{amount}} ${{tokenSymbol}} from the token swap contract below.", "actions_one": "{{count}} action", "actions_other": "{{count}} actions", + "activeThresholdDescription": "The amount of voting power that needs to be staked in order for the DAO to become active (i.e. proposal creation is allowed).", "addCw20ToTreasuryActionDescription": "Display the DAO's balance of a CW20 token in the treasury view.", "addCw721ToTreasuryActionDescription": "Display the NFTs owned by the DAO from a CW721 NFT collection in the treasury view.", "addedToDaoFollowPrompt": "This DAO has added you as a member. Follow it to receive updates.", @@ -710,6 +715,7 @@ "copiedLinkToClipboard": "Copied link to clipboard.", "copiedToClipboard": "Copied to clipboard.", "copyWalletAddressTooltip": "Copy wallet address", + "count": "Count", "createADaoDescription": "Create a new project, organization, or community organized the way you want.", "createCrossChainAccountDescription": "Create an account for this DAO on another chain.", "createCrossChainAccountExplanation": "This action creates an account on another chain, allowing this DAO to perform actions on that chain.", @@ -1109,6 +1115,7 @@ "actions_one": "Action", "actions_other": "Actions", "active": "Active", + "activeThreshold": "Active Threshold", "addCw20ToTreasury": "Display Token Balance in Treasury", "addCw721ToTreasury": "Display NFT Collection in Treasury", "advancedConfiguration": "Advanced configuration", diff --git a/packages/stateful/components/dao/DaoInfoBar.tsx b/packages/stateful/components/dao/DaoInfoBar.tsx index 402dbaacb..011e20c2e 100644 --- a/packages/stateful/components/dao/DaoInfoBar.tsx +++ b/packages/stateful/components/dao/DaoInfoBar.tsx @@ -1,4 +1,4 @@ -import { AccountBalanceOutlined } from '@mui/icons-material' +import { AccountBalanceOutlined, LockOpenOutlined } from '@mui/icons-material' import { useTranslation } from 'react-i18next' import { daoTvlSelector } from '@dao-dao/state' @@ -10,6 +10,10 @@ import { useChain, useDaoInfoContext, } from '@dao-dao/stateless' +import { + convertMicroDenomToDenomWithDecimals, + formatPercentOf100, +} from '@dao-dao/utils' import { useCw20CommonGovernanceTokenInfoIfExists, @@ -27,13 +31,14 @@ const InnerDaoInfoBar = () => { const { t } = useTranslation() const { chain_id: chainId } = useChain() const { - hooks: { useDaoInfoBarItems }, + hooks: { useDaoInfoBarItems, useCommonGovernanceTokenInfo }, } = useVotingModuleAdapter() const votingModuleItems = useDaoInfoBarItems() - const { coreAddress } = useDaoInfoContext() + const { coreAddress, activeThreshold } = useDaoInfoContext() const { denomOrAddress: cw20GovernanceTokenAddress } = useCw20CommonGovernanceTokenInfoIfExists() ?? {} + const tokenInfo = useCommonGovernanceTokenInfo?.() const treasuryUsdcValueLoading = useCachedLoading( daoTvlSelector({ @@ -75,6 +80,30 @@ const InnerDaoInfoBar = () => { /> ), }, + ...(activeThreshold + ? [ + { + Icon: LockOpenOutlined, + label: t('title.activeThreshold'), + tooltip: t('info.activeThresholdDescription'), + value: + 'percentage' in activeThreshold + ? formatPercentOf100( + Number(activeThreshold.percentage.percent) * 100 + ) + : tokenInfo && ( + + ), + }, + ] + : []), // Voting module-specific items. ...votingModuleItems, ]} diff --git a/packages/stateful/components/dao/commonVotingConfig/ActiveThresholdVotingConfigItem.tsx b/packages/stateful/components/dao/commonVotingConfig/ActiveThresholdVotingConfigItem.tsx new file mode 100644 index 000000000..27cb01f56 --- /dev/null +++ b/packages/stateful/components/dao/commonVotingConfig/ActiveThresholdVotingConfigItem.tsx @@ -0,0 +1,109 @@ +import { useTranslation } from 'react-i18next' + +import { + FilmSlateEmoji, + FormSwitchCard, + NumberInput, + SelectInput, +} from '@dao-dao/stateless' +import { + DaoCreationVotingConfigItem, + DaoCreationVotingConfigItemInputProps, + DaoCreationVotingConfigItemReviewProps, + DaoCreationVotingConfigWithActiveThreshold, +} from '@dao-dao/types' +import { + formatPercentOf100, + validatePositive, + validateRequired, +} from '@dao-dao/utils' + +const ActiveThresholdInput = ({ + data: { + activeThreshold: { enabled, type } = { + enabled: false, + type: 'percent', + value: 10, + }, + }, + register, + setValue, + watch, + errors, +}: DaoCreationVotingConfigItemInputProps) => { + const { t } = useTranslation() + + return ( +
+ + + {enabled && ( + <> +
+ + + + setValue('activeThreshold.type', newType as any) + } + validation={[validateRequired]} + value={type} + > + + + +
+ + )} +
+ ) +} + +const ActiveThresholdReview = ({ + data: { + activeThreshold: { enabled, type, value } = { + enabled: false, + type: 'percent', + value: 10, + }, + }, +}: DaoCreationVotingConfigItemReviewProps) => { + const { t } = useTranslation() + return enabled ? ( + <> + {type === 'absolute' + ? value.toLocaleString() + ' ' + t('info.tokens') + : formatPercentOf100(value)} + + ) : ( + <>{t('info.none')} + ) +} + +export const makeActiveThresholdVotingConfigItem = + (): DaoCreationVotingConfigItem => ({ + Icon: FilmSlateEmoji, + nameI18nKey: 'title.activeThreshold', + descriptionI18nKey: 'info.activeThresholdDescription', + Input: ActiveThresholdInput, + getInputError: ({ activeThreshold } = {}) => + activeThreshold?.type || activeThreshold?.value, + Review: ActiveThresholdReview, + }) diff --git a/packages/stateful/creators/NftBased/index.ts b/packages/stateful/creators/NftBased/index.ts index 3a2959bf9..ce54398b2 100644 --- a/packages/stateful/creators/NftBased/index.ts +++ b/packages/stateful/creators/NftBased/index.ts @@ -2,6 +2,7 @@ import { ImageEmoji } from '@dao-dao/stateless' import { DaoCreator, DurationUnits } from '@dao-dao/types' import { NftBasedCreatorId } from '@dao-dao/utils' +import { makeActiveThresholdVotingConfigItem } from '../../components/dao/commonVotingConfig/ActiveThresholdVotingConfigItem' import { GovernanceTokenType } from '../TokenBased/types' import { GovernanceConfigurationInput } from './GovernanceConfigurationInput' import { GovernanceConfigurationReview } from './GovernanceConfigurationReview' @@ -31,6 +32,7 @@ export const NftBasedCreator: DaoCreator = { }, votingConfig: { items: [UnstakingDurationVotingConfigItem], + advancedItems: [makeActiveThresholdVotingConfigItem()], }, mutate, } diff --git a/packages/stateful/creators/TokenBased/index.ts b/packages/stateful/creators/TokenBased/index.ts index ab13e521d..e8dda2945 100644 --- a/packages/stateful/creators/TokenBased/index.ts +++ b/packages/stateful/creators/TokenBased/index.ts @@ -2,6 +2,7 @@ import { DaoEmoji } from '@dao-dao/stateless' import { DaoCreator, DurationUnits, TokenType } from '@dao-dao/types' import { TokenBasedCreatorId } from '@dao-dao/utils' +import { makeActiveThresholdVotingConfigItem } from '../../components/dao/commonVotingConfig/ActiveThresholdVotingConfigItem' import { GovernanceConfigurationInput } from './GovernanceConfigurationInput' import { GovernanceConfigurationReview } from './GovernanceConfigurationReview' import { mutate } from './mutate' @@ -42,6 +43,11 @@ export const TokenBasedCreator: DaoCreator = { value: 2, units: DurationUnits.Weeks, }, + activeThreshold: { + enabled: false, + type: 'percent', + value: 10, + }, }, governanceConfig: { Input: GovernanceConfigurationInput, @@ -49,6 +55,7 @@ export const TokenBasedCreator: DaoCreator = { }, votingConfig: { items: [UnstakingDurationVotingConfigItem], + advancedItems: [makeActiveThresholdVotingConfigItem()], }, mutate, } diff --git a/packages/stateful/creators/TokenBased/mutate.ts b/packages/stateful/creators/TokenBased/mutate.ts index 9293449c1..fd6216a0d 100644 --- a/packages/stateful/creators/TokenBased/mutate.ts +++ b/packages/stateful/creators/TokenBased/mutate.ts @@ -2,6 +2,7 @@ import { Buffer } from 'buffer' import { DaoCreatorMutate, TokenType } from '@dao-dao/types' import { + ActiveThreshold, Cw20Coin, InstantiateMsg as DaoVotingCw20StakedInstantiateMsg, } from '@dao-dao/types/contracts/DaoVotingCw20Staked' @@ -28,6 +29,7 @@ export const mutate: DaoCreatorMutate = ( existingTokenType, existingTokenDenomOrAddress, unstakingDuration, + activeThreshold, }, t, codeIds @@ -40,6 +42,20 @@ export const mutate: DaoCreatorMutate = ( | DaoVotingCw20StakedInstantiateMsg | DaoVotingNativeStakedInstantiateMsg + const active_threshold: ActiveThreshold | null = activeThreshold?.enabled + ? !activeThreshold.type || activeThreshold.type === 'percent' + ? { + percentage: { + percent: (activeThreshold.value / 100).toString(), + }, + } + : { + absolute_count: { + count: BigInt(activeThreshold.value).toString(), + }, + } + : null + if (tokenType === GovernanceTokenType.NewCw20) { const microInitialBalances: Cw20Coin[] = tiers.flatMap( ({ weight, members }) => @@ -64,6 +80,7 @@ export const mutate: DaoCreatorMutate = ( ).toString() votingModuleAdapterInstantiateMsg = { + active_threshold, token_info: { new: { code_id: codeIds.Cw20Base, @@ -86,6 +103,7 @@ export const mutate: DaoCreatorMutate = ( } votingModuleAdapterInstantiateMsg = { + active_threshold, token_info: { existing: { address: existingTokenDenomOrAddress, @@ -105,6 +123,7 @@ export const mutate: DaoCreatorMutate = ( } votingModuleAdapterInstantiateMsg = { + active_threshold, denom: existingTokenDenomOrAddress, owner: { core_module: {} }, unstaking_duration: diff --git a/packages/stateful/creators/TokenBased/types.ts b/packages/stateful/creators/TokenBased/types.ts index cd5cd88ff..4d9868e2c 100644 --- a/packages/stateful/creators/TokenBased/types.ts +++ b/packages/stateful/creators/TokenBased/types.ts @@ -1,4 +1,5 @@ import { + DaoCreationVotingConfigWithActiveThreshold, DurationWithUnits, GenericToken, NewDaoTier, @@ -30,4 +31,4 @@ export type CreatorData = { } existingTokenSupply?: string unstakingDuration: DurationWithUnits -} +} & DaoCreationVotingConfigWithActiveThreshold diff --git a/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/NewProposal.stories.tsx b/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/NewProposal.stories.tsx index c533e3575..58731532c 100644 --- a/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/NewProposal.stories.tsx +++ b/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/NewProposal.stories.tsx @@ -50,6 +50,8 @@ Default.args = { }, loading: false, isPaused: false, + isActive: true, + activeThreshold: null, isMember: { loading: false, data: true }, depositUnsatisfied: false, connected: true, diff --git a/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/NewProposal.tsx b/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/NewProposal.tsx index 13a4faa4c..f8534abee 100644 --- a/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/NewProposal.tsx +++ b/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/NewProposal.tsx @@ -39,10 +39,12 @@ import { SuspenseLoaderProps, } from '@dao-dao/types' import { MultipleChoiceOptionType } from '@dao-dao/types/contracts/DaoProposalMultiple' +import { ActiveThreshold } from '@dao-dao/types/contracts/DaoVotingCw20Staked' import { MAX_NUM_PROPOSAL_CHOICES, convertActionsToMessages, formatDateTime, + formatPercentOf100, formatTime, processError, validateRequired, @@ -76,6 +78,8 @@ export interface NewProposalProps createProposal: (newProposalData: NewProposalData) => Promise loading: boolean isPaused: boolean + isActive: boolean + activeThreshold: ActiveThreshold | null isMember: LoadingData anyoneCanPropose: boolean depositUnsatisfied: boolean @@ -91,6 +95,8 @@ export const NewProposal = ({ createProposal, loading, isPaused, + isActive, + activeThreshold, isMember, anyoneCanPropose, depositUnsatisfied, @@ -333,6 +339,23 @@ export const NewProposal = ({ ? t('error.notEnoughForDeposit') : isPaused ? t('error.daoIsPaused') + : !isActive && activeThreshold + ? t('error.daoIsInactive', { + context: + 'percentage' in activeThreshold + ? 'percent' + : 'absolute', + percent: + 'percentage' in activeThreshold + ? formatPercentOf100( + Number(activeThreshold.percentage.percent) * 100 + ) + : undefined, + count: + 'percentage' in activeThreshold + ? undefined + : Number(activeThreshold.absolute_count.count), + }) : choices.length < 2 ? t('error.tooFewChoices') : choices.length > MAX_NUM_PROPOSAL_CHOICES @@ -348,6 +371,7 @@ export const NewProposal = ({ (!anyoneCanPropose && !isMember.loading && !isMember.data) || depositUnsatisfied || isPaused || + !isActive || choices.length < 2 || choices.length > MAX_NUM_PROPOSAL_CHOICES } diff --git a/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/index.tsx b/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/index.tsx index 9aded025f..2cfca54c5 100644 --- a/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/index.tsx +++ b/packages/stateful/proposal-module-adapter/adapters/DaoProposalMultiple/common/components/NewProposal/index.tsx @@ -51,6 +51,8 @@ export const NewProposal = ({ name: daoName, imageUrl: daoImageUrl, coreAddress, + isActive, + activeThreshold, } = useDaoInfoContext() const { isWalletConnected, getStargateClient } = useWallet() @@ -206,11 +208,13 @@ export const NewProposal = ({ Promise loading: boolean isPaused: boolean + isActive: boolean + activeThreshold: ActiveThreshold | null isMember: LoadingData anyoneCanPropose: boolean depositUnsatisfied: boolean @@ -85,6 +89,8 @@ export const NewProposal = ({ createProposal, loading, isPaused, + isActive, + activeThreshold, isMember, anyoneCanPropose, depositUnsatisfied, @@ -280,6 +286,23 @@ export const NewProposal = ({ ? t('error.notEnoughForDeposit') : isPaused ? t('error.daoIsPaused') + : !isActive && activeThreshold + ? t('error.daoIsInactive', { + context: + 'percentage' in activeThreshold + ? 'percent' + : 'absolute', + percent: + 'percentage' in activeThreshold + ? formatPercentOf100( + Number(activeThreshold.percentage.percent) * 100 + ) + : undefined, + count: + 'percentage' in activeThreshold + ? undefined + : Number(activeThreshold.absolute_count.count), + }) : undefined } > @@ -288,7 +311,8 @@ export const NewProposal = ({ !connected || (!anyoneCanPropose && !isMember.loading && !isMember.data) || depositUnsatisfied || - isPaused + isPaused || + !isActive } loading={loading} type="submit" diff --git a/packages/stateful/proposal-module-adapter/adapters/DaoProposalSingle/common/components/NewProposal/index.tsx b/packages/stateful/proposal-module-adapter/adapters/DaoProposalSingle/common/components/NewProposal/index.tsx index e5076cfc3..9b20200e1 100644 --- a/packages/stateful/proposal-module-adapter/adapters/DaoProposalSingle/common/components/NewProposal/index.tsx +++ b/packages/stateful/proposal-module-adapter/adapters/DaoProposalSingle/common/components/NewProposal/index.tsx @@ -51,6 +51,8 @@ export const NewProposal = ({ name: daoName, imageUrl: daoImageUrl, coreAddress, + isActive, + activeThreshold, } = useDaoInfoContext() const { isWalletConnected, getStargateClient } = useWallet() @@ -215,11 +217,13 @@ export const NewProposal = ({ => { + const cwClient = await cosmWasmClientRouter.connect(getRpcForChainId(chainId)) + try { const indexerDumpedState = await queryIndexer({ type: 'contract', @@ -602,6 +612,23 @@ const daoCoreDumpState = async ( indexerDumpedState.polytoneProxies || {} ) + let isActive = true + let activeThreshold: ActiveThreshold | null = null + try { + // All voting modules use the same active queries, so it's safe to just + // use one here. + const client = new DaoVotingCw20StakedQueryClient( + cwClient, + indexerDumpedState.voting_module + ) + isActive = (await client.isActive()).active + activeThreshold = + (await client.activeThreshold()).active_threshold || null + } catch { + // Some voting modules don't support the active queries, so if they + // fail, assume it's active. + } + return { ...indexerDumpedState, version: coreVersion, @@ -615,6 +642,8 @@ const daoCoreDumpState = async ( created: indexerDumpedState.createdAt ? new Date(indexerDumpedState.createdAt) : undefined, + isActive, + activeThreshold, items, parentDao: parentDaoInfo ? { @@ -637,7 +666,6 @@ const daoCoreDumpState = async ( console.error(error, processError(error)) } - const cwClient = await cosmWasmClientRouter.connect(getRpcForChainId(chainId)) const daoCoreClient = new DaoCoreV2QueryClient(cwClient, coreAddress) const dumpedState = await daoCoreClient.dumpState() @@ -681,6 +709,22 @@ const daoCoreDumpState = async ( } } + let isActive = true + let activeThreshold: ActiveThreshold | null = null + try { + // All voting modules use the same active queries, so it's safe to just use + // one here. + const client = new DaoVotingCw20StakedQueryClient( + cwClient, + dumpedState.voting_module + ) + isActive = (await client.isActive()).active + activeThreshold = (await client.activeThreshold()).active_threshold || null + } catch { + // Some voting modules don't support the active queries, so if they fail, + // assume it's active. + } + const { admin } = dumpedState const parentDao = await loadParentDaoInfo( chainId, @@ -779,6 +823,8 @@ const daoCoreDumpState = async ( ({ status }) => status === 'enabled' || status === 'Enabled' ), created: undefined, + isActive, + activeThreshold, items, parentDao: parentDao ? { diff --git a/packages/stateful/server/makeGetGovStaticProps.ts b/packages/stateful/server/makeGetGovStaticProps.ts index 151d793d2..900f6f769 100644 --- a/packages/stateful/server/makeGetGovStaticProps.ts +++ b/packages/stateful/server/makeGetGovStaticProps.ts @@ -124,6 +124,8 @@ export const makeGetGovStaticProps: GetGovStaticPropsMaker = description: '', imageUrl: getImageUrlForChainId(chain.chain_id), created: null, + isActive: true, + activeThreshold: null, items: {}, polytoneProxies: {}, parentDao: null, diff --git a/packages/stateless/components/dao/DaoSplashHeader.tsx b/packages/stateless/components/dao/DaoSplashHeader.tsx index 01b594937..c191cf0e1 100644 --- a/packages/stateless/components/dao/DaoSplashHeader.tsx +++ b/packages/stateless/components/dao/DaoSplashHeader.tsx @@ -1,5 +1,8 @@ +import { WarningRounded } from '@mui/icons-material' +import { useTranslation } from 'react-i18next' + import { DaoSplashHeaderProps } from '@dao-dao/types' -import { formatDate } from '@dao-dao/utils' +import { formatDate, formatPercentOf100 } from '@dao-dao/utils' import { DaoHeader } from './DaoHeader' @@ -8,19 +11,48 @@ export const DaoSplashHeader = ({ follow, DaoInfoBar, LinkWrapper, -}: DaoSplashHeaderProps) => ( - <> - +}: DaoSplashHeaderProps) => { + const { t } = useTranslation() + + return ( + <> + {!daoInfo.isActive && daoInfo.activeThreshold && ( +
+ + +

+ {t('error.daoIsInactive', { + context: + 'percentage' in daoInfo.activeThreshold + ? 'percent' + : 'absolute', + percent: + 'percentage' in daoInfo.activeThreshold + ? formatPercentOf100( + Number(daoInfo.activeThreshold.percentage.percent) * 100 + ) + : undefined, + count: + 'percentage' in daoInfo.activeThreshold + ? undefined + : Number(daoInfo.activeThreshold.absolute_count.count), + })} +

+
+ )} + + - - -) + + + ) +} diff --git a/packages/stateless/components/emoji.tsx b/packages/stateless/components/emoji.tsx index 4ccf38af3..5e61ef7ae 100644 --- a/packages/stateless/components/emoji.tsx +++ b/packages/stateless/components/emoji.tsx @@ -216,3 +216,7 @@ export const CurvedDownArrowEmoji = () => ( export const DownArrowEmoji = () => ( ) + +export const FilmSlateEmoji = () => ( + +) diff --git a/packages/storybook/decorators/DaoPageWrapperDecorator.tsx b/packages/storybook/decorators/DaoPageWrapperDecorator.tsx index 3d05b0c51..5388940a5 100644 --- a/packages/storybook/decorators/DaoPageWrapperDecorator.tsx +++ b/packages/storybook/decorators/DaoPageWrapperDecorator.tsx @@ -32,6 +32,8 @@ export const makeDaoInfo = (): DaoInfo => ({ created: new Date( Date.now() - Math.floor(Math.random() * 12 * 30 * 24 * 60 * 60 * 1000) ), + isActive: true, + activeThreshold: null, items: {}, polytoneProxies: {}, parentDao: null, diff --git a/packages/types/contracts/DaoVotingCw20Staked.ts b/packages/types/contracts/DaoVotingCw20Staked.ts index ea07b4eb8..89ec0c6ff 100644 --- a/packages/types/contracts/DaoVotingCw20Staked.ts +++ b/packages/types/contracts/DaoVotingCw20Staked.ts @@ -10,13 +10,11 @@ export type ActiveThreshold = | { absolute_count: { count: Uint128 - [k: string]: unknown } } | { percentage: { percent: Decimal - [k: string]: unknown } } export interface ActiveThresholdResponse { diff --git a/packages/types/dao.ts b/packages/types/dao.ts index aaa376d2d..faf6183a6 100644 --- a/packages/types/dao.ts +++ b/packages/types/dao.ts @@ -15,6 +15,7 @@ import { import { ContractVersion } from './chain' import { DepositRefundPolicy, ModuleInstantiateInfo } from './contracts/common' import { InstantiateMsg as DaoCoreV2InstantiateMsg } from './contracts/DaoCore.v2' +import { ActiveThreshold } from './contracts/DaoVotingCw20Staked' import { DaoCreator } from './creators' import { PercentOrMajorityValue, @@ -37,6 +38,8 @@ export type DaoInfo = { description: string imageUrl: string | null created: Date | undefined + isActive: boolean + activeThreshold: ActiveThreshold | null items: Record // Map chain ID to polytone proxy address. polytoneProxies: PolytoneProxies @@ -245,6 +248,14 @@ export type DaoCreationVotingConfigWithEnableMultipleChoice = { enableMultipleChoice: boolean } +export type DaoCreationVotingConfigWithActiveThreshold = { + activeThreshold: { + enabled: boolean + type: 'percent' | 'absolute' + value: number + } +} + export type DaoCreationVotingConfig = DaoCreationVotingConfigWithAllowRevoting & DaoCreationVotingConfigWithProposalDeposit & DaoCreationVotingConfigWithProposalSubmissionPolicy & diff --git a/packages/utils/error.ts b/packages/utils/error.ts index 9f5d368e5..878364d33 100644 --- a/packages/utils/error.ts +++ b/packages/utils/error.ts @@ -102,6 +102,7 @@ export enum CommonError { SignatureVerificationFailedLedger = 'Signature verification failed. If you are using a Ledger, this is likely due to some unsupported symbols, such as "&", "<", or ">". Remove these symbols from the proposal title or description and try again.', IbcClientExpired = 'IBC client expired. Reach out to us for help.', IndexerDisabled = 'Indexer disabled.', + DaoInactive = 'This DAO is inactive, which means insufficient voting power has been staked. You cannot create a proposal at this time.', } // List of error substrings to match to determine the common error. Elements in @@ -170,6 +171,9 @@ const commonErrorPatterns: Record = { ], ], [CommonError.IndexerDisabled]: ['Indexer disabled.'], + [CommonError.DaoInactive]: [ + 'the DAO is currently inactive, you cannot create proposals', + ], } const commonErrorPatternsEntries = Object.entries(commonErrorPatterns) as [ CommonError,