diff --git a/frontend/src/lib/constants/neurons.constants.ts b/frontend/src/lib/constants/neurons.constants.ts index f0d8a17ffea..7787215ae33 100644 --- a/frontend/src/lib/constants/neurons.constants.ts +++ b/frontend/src/lib/constants/neurons.constants.ts @@ -20,6 +20,11 @@ const FIRST_TOPICS = [ Topic.SnsAndCommunityFund, ]; const LAST_TOPICS = [Topic.ExchangeRate]; + +// Topics that neurons cannot yet set following for. +// TODO: Remove this list when the NNS Governance supports following on those topics. +export const TOPICS_WITH_FOLLOWING_DISABLED = [Topic.ProtocolCanisterManagement, Topic.ServiceNervousSystemManagement]; + // This list should include ALL topics ordered as we want. // Filtering out topics is done in the utils. export const TOPICS_TO_FOLLOW_NNS = [ diff --git a/frontend/src/lib/i18n/en.governance.json b/frontend/src/lib/i18n/en.governance.json index d53b6738921..7131f34f45e 100644 --- a/frontend/src/lib/i18n/en.governance.json +++ b/frontend/src/lib/i18n/en.governance.json @@ -23,7 +23,9 @@ "SubnetReplicaVersionManagement": "IC OS Version Deployment", "SnsAndCommunityFund": "SNS & Neurons' Fund", "ApiBoundaryNodeManagement": "API Boundary Node Management", - "SubnetRental": "Subnet Rental" + "SubnetRental": "Subnet Rental", + "ProtocolCanisterManagement": "Protocol Canister Management", + "ServiceNervousSystemManagement": "Service Nervous System Management" }, "topics_description": { "Unspecified": "", @@ -42,7 +44,9 @@ "SubnetReplicaVersionManagement": "Proposals handling deployments of IC OS versions.", "SnsAndCommunityFund": "Proposals relating to service nervous systems (SNS) and Neurons' Fund.", "ApiBoundaryNodeManagement": "Proposals related to the management of API boundary nodes.", - "SubnetRental": "All proposals related to renting a subnet — for example a subnet rental request." + "SubnetRental": "Proposals related to renting a subnet — for example a subnet rental request.", + "ProtocolCanisterManagement": "Proposals related to managing canisters that are considered as part of the IC protocol.", + "ServiceNervousSystemManagement": "All proposals related to managing the service nervous system (SNS)." }, "rewards": { "Unknown": "Unknown", @@ -208,4 +212,4 @@ "DeployHostosToSomeNodes": "Deploy a HostOS version to a given set of nodes. The proposal changes the HostOS version that is used on the specified nodes.", "SubnetRentalRequest": "A proposal to rent a subnet on the Internet Computer.

The Subnet Rental Canister is called when this proposal is executed, and the rental request is stored there. The user specified in the proposal needs to make a sufficient upfront payment in ICP in order for the proposal to be valid, and the subnet must be available for rent. The available rental conditions can be checked by calling the Subnet Rental Canister." } -} +} \ No newline at end of file diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index c53b0c856f9..b05c3df7721 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -366,6 +366,10 @@ "topic_15_subtitle": "Proposals related to the management of API boundary nodes", "topic_16_title": "Subnet Rental", "topic_16_subtitle": "All proposals related to renting a subnet, for example a subnet rental request.", + "topic_17_title": "Protocol Canister Management", + "topic_17_subtitle": "Proposals related to managing canisters that are considered as part of the IC protocol.", + "topic_18_title": "Service Nervous System Management", + "topic_18_subtitle": "Proposals related to managing the service nervous system (SNS).", "current_followees": "Currently Following", "add": "Add Followee" }, @@ -1028,4 +1032,4 @@ "show_all": "Show all", "import_token": "Import Token" } -} +} \ No newline at end of file diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 4a48963d97d..ce84475859f 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -380,6 +380,10 @@ interface I18nFollow_neurons { topic_15_subtitle: string; topic_16_title: string; topic_16_subtitle: string; + topic_17_title: string; + topic_17_subtitle: string; + topic_18_title: string; + topic_18_subtitle: string; current_followees: string; add: string; } @@ -1114,6 +1118,8 @@ interface I18nTopics { SnsAndCommunityFund: string; ApiBoundaryNodeManagement: string; SubnetRental: string; + ProtocolCanisterManagement: string; + ServiceNervousSystemManagement: string; } interface I18nTopics_description { @@ -1134,6 +1140,8 @@ interface I18nTopics_description { SnsAndCommunityFund: string; ApiBoundaryNodeManagement: string; SubnetRental: string; + ProtocolCanisterManagement: string; + ServiceNervousSystemManagement: string; } interface I18nRewards { diff --git a/frontend/src/lib/utils/neuron.utils.ts b/frontend/src/lib/utils/neuron.utils.ts index 02e2da1803b..3876b351a1e 100644 --- a/frontend/src/lib/utils/neuron.utils.ts +++ b/frontend/src/lib/utils/neuron.utils.ts @@ -16,6 +16,7 @@ import { MAX_NEURONS_MERGED, MIN_NEURON_STAKE, TOPICS_TO_FOLLOW_NNS, + TOPICS_WITH_FOLLOWING_DISABLED, } from "$lib/constants/neurons.constants"; import { DEPRECATED_TOPICS } from "$lib/constants/proposals.constants"; import type { IcpAccountsStoreData } from "$lib/derived/icp-accounts.derived"; @@ -207,9 +208,9 @@ export const bonusMultiplier = ({ }): number => 1 + multiplier * - (Math.min(Number(amount), max) / - // to avoid NaN - (max === 0 ? 1 : max)); + (Math.min(Number(amount), max) / + // to avoid NaN + (max === 0 ? 1 : max)); // TODO: Do we need this? What does it mean to have a valid stake? // TODO: https://dfinity.atlassian.net/browse/L2-507 @@ -217,16 +218,16 @@ export const hasValidStake = (neuron: NeuronInfo): boolean => // Ignore if we can't validate the stake nonNullish(neuron.fullNeuron) ? neuron.fullNeuron.cachedNeuronStake + - neuron.fullNeuron.maturityE8sEquivalent > - BigInt(DEFAULT_TRANSACTION_FEE_E8S) + neuron.fullNeuron.maturityE8sEquivalent > + BigInt(DEFAULT_TRANSACTION_FEE_E8S) : false; export const getDissolvingTimestampSeconds = ( neuron: NeuronInfo ): bigint | undefined => neuron.state === NeuronState.Dissolving && - neuron.fullNeuron?.dissolveState !== undefined && - "WhenDissolvedTimestampSeconds" in neuron.fullNeuron.dissolveState + neuron.fullNeuron?.dissolveState !== undefined && + "WhenDissolvedTimestampSeconds" in neuron.fullNeuron.dissolveState ? neuron.fullNeuron.dissolveState.WhenDissolvedTimestampSeconds : undefined; @@ -282,7 +283,7 @@ export const formattedMaturity = ({ fullNeuron }: NeuronInfo): string => export const formattedTotalMaturity = ({ fullNeuron }: NeuronInfo): string => formatMaturity( (fullNeuron?.maturityE8sEquivalent ?? 0n) + - (fullNeuron?.stakedMaturityE8sEquivalent ?? 0n) + (fullNeuron?.stakedMaturityE8sEquivalent ?? 0n) ); /** @@ -368,7 +369,7 @@ export const isNeuronControllable = ({ fullNeuron?.controller !== undefined && (fullNeuron.controller === identity?.getPrincipal().toText() || getAccountByPrincipal({ principal: fullNeuron.controller, accounts }) !== - undefined); + undefined); export const isNeuronControlledByHardwareWallet = ({ neuron, @@ -734,12 +735,12 @@ export const canBeMerged = ( } return sameManageNeuronFollowees(neurons) ? { - isValid: true, - } + isValid: true, + } : { - isValid: false, - messageKey: "error.merge_neurons_not_same_manage_neuron_followees", - }; + isValid: false, + messageKey: "error.merge_neurons_not_same_manage_neuron_followees", + }; }; export const mapNeuronIds = ({ @@ -814,7 +815,7 @@ export const topicsToFollow = (neuron: NeuronInfo): Topic[] => (followeesByTopic({ neuron, topic: Topic.ManageNeuron }) === undefined ? TOPICS_TO_FOLLOW_NNS.filter((topic) => topic !== Topic.ManageNeuron) : TOPICS_TO_FOLLOW_NNS - ).filter((topic) => !DEPRECATED_TOPICS.includes(topic)); + ).filter((topic) => !DEPRECATED_TOPICS.includes(topic) && !TOPICS_WITH_FOLLOWING_DISABLED.includes(topic)); // NeuronInfo is public info. // fullNeuron is only for users with access. @@ -972,7 +973,7 @@ export const maturityLastDistribution = ({ return ( actual_timestamp_seconds - (fromNullable(rounds_since_last_distribution) ?? 1n) * - BigInt(SECONDS_IN_DAY) + BigInt(SECONDS_IN_DAY) ); }; @@ -1004,6 +1005,8 @@ export const getTopicTitle = ({ [Topic.SnsAndCommunityFund]: i18n.follow_neurons.topic_14_title, [Topic.ApiBoundaryNodeManagement]: i18n.follow_neurons.topic_15_title, [Topic.SubnetRental]: i18n.follow_neurons.topic_16_title, + [Topic.ProtocolCanisterManagement]: i18n.follow_neurons.topic_17_title, + [Topic.ServiceNervousSystemManagement]: i18n.follow_neurons.topic_18_title, }; return mapper[topic]; }; @@ -1034,6 +1037,9 @@ export const getTopicSubtitle = ({ [Topic.SnsAndCommunityFund]: i18n.follow_neurons.topic_14_subtitle, [Topic.ApiBoundaryNodeManagement]: i18n.follow_neurons.topic_15_subtitle, [Topic.SubnetRental]: i18n.follow_neurons.topic_16_subtitle, + [Topic.ProtocolCanisterManagement]: i18n.follow_neurons.topic_17_subtitle, + [Topic.ServiceNervousSystemManagement]: + i18n.follow_neurons.topic_18_subtitle, }; return mapper[topic]; }; diff --git a/frontend/src/tests/lib/utils/enum.utils.spec.ts b/frontend/src/tests/lib/utils/enum.utils.spec.ts index 6126829a84e..61ffdb45230 100644 --- a/frontend/src/tests/lib/utils/enum.utils.spec.ts +++ b/frontend/src/tests/lib/utils/enum.utils.spec.ts @@ -67,6 +67,8 @@ describe("enum-utils", () => { Topic.SnsAndCommunityFund, Topic.ApiBoundaryNodeManagement, Topic.SubnetRental, + Topic.ProtocolCanisterManagement, + Topic.ServiceNervousSystemManagement, ]; expect( diff --git a/frontend/src/tests/lib/utils/neuron.utils.spec.ts b/frontend/src/tests/lib/utils/neuron.utils.spec.ts index 8054d0c6f99..0fcb40b3090 100644 --- a/frontend/src/tests/lib/utils/neuron.utils.spec.ts +++ b/frontend/src/tests/lib/utils/neuron.utils.spec.ts @@ -11,6 +11,7 @@ import { MAX_NEURONS_MERGED, MIN_NEURON_STAKE, TOPICS_TO_FOLLOW_NNS, + TOPICS_WITH_FOLLOWING_DISABLED, } from "$lib/constants/neurons.constants"; import { DEPRECATED_TOPICS } from "$lib/constants/proposals.constants"; import type { IcpAccountsStoreData } from "$lib/derived/icp-accounts.derived"; @@ -2166,22 +2167,22 @@ describe("neuron-utils", () => { }, }; - it("should not return deprecated topics", () => { + it("should not return deprecated or disabled topics", () => { expect(topicsToFollow(neuronWithoutManageNeuron)).toEqual( TOPICS_TO_FOLLOW_NNS.filter( (topic) => - topic !== Topic.ManageNeuron && !DEPRECATED_TOPICS.includes(topic) + topic !== Topic.ManageNeuron && !DEPRECATED_TOPICS.includes(topic) && !TOPICS_WITH_FOLLOWING_DISABLED.includes(topic) ) ); expect(topicsToFollow(neuronWithoutFollowees)).toEqual( TOPICS_TO_FOLLOW_NNS.filter( (topic) => - topic !== Topic.ManageNeuron && !DEPRECATED_TOPICS.includes(topic) + topic !== Topic.ManageNeuron && !DEPRECATED_TOPICS.includes(topic) && !TOPICS_WITH_FOLLOWING_DISABLED.includes(topic) ) ); expect(topicsToFollow(neuronWithManageNeuron)).toEqual( TOPICS_TO_FOLLOW_NNS.filter( - (topic) => !DEPRECATED_TOPICS.includes(topic) + (topic) => !DEPRECATED_TOPICS.includes(topic) && !TOPICS_WITH_FOLLOWING_DISABLED.includes(topic) ) ); }); @@ -2189,7 +2190,7 @@ describe("neuron-utils", () => { it("should return topics with ManageNeuron if neuron follows some neuron on the ManageNeuron topic", () => { expect(topicsToFollow(neuronWithManageNeuron)).toEqual( TOPICS_TO_FOLLOW_NNS.filter( - (topic) => !DEPRECATED_TOPICS.includes(topic) + (topic) => !DEPRECATED_TOPICS.includes(topic) && !TOPICS_WITH_FOLLOWING_DISABLED.includes(topic) ) ); });