diff --git a/README.md b/README.md index 16a8fea2..62bd0e4b 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,12 @@ To assign a user as an admin, run the following command: dfx canister call backend update_user_profile '(record { user_id = "${userId}"; username = opt "${username}"; config = opt variant { admin = record { bio = opt "${bio}" } } })' ``` +To only upgrade a user to admin without changing any other properties: + +```bash +dfx canister call backend update_user_profile '(record { user_id = "${userId}"; config = opt variant { admin = record {} } })' +``` + To assign a user as a reviewer, run the following command: - Replace `${userId}` with the ID of the profile that was created earlier. @@ -316,10 +322,16 @@ dfx canister call backend update_user_profile '(record { user_id = "${userId}"; ### Listing open proposals -To list open replica version management proposals: +To list open IcOsVersionElection proposals: + +```bash +./scripts/list-open-ic-os-version-election-proposals.sh +``` + +To list open IcOsVersionDeployment proposals on mainnet: ```bash -./scripts/list-open-rvm-proposals.sh +./scripts/list-open-ic-os-version-election-proposals.sh --ic ``` ### Creating closed proposals @@ -366,33 +378,6 @@ To list open replica version management proposals: - `payload` is the Candid encoded argument for the corresponding NNS function. The types for this argument can be found in the appropriate canister's declaration. A mapping between NNS functions and their corresponding canisters can be found in the [`NnsFunction::canister_and_function`](https://github.com/dfinity/ic/blob/master/rs/nns/governance/src/governance.rs#L527-L631) function definition. - For example, the `UpdateElectedReplicaVersions` uses number `38` and its payload is the [`UpdateElectedReplicaVersionsPayload`](https://github.com/dfinity/ic/blob/master/rs/registry/canister/canister/registry.did#L217-L223) record. -### Getting proposals for testing - -Open the [NNS canister interface on the dashboard](https://dashboard.internetcomputer.org/canister/rrkah-fqaaa-aaaaa-aaaaq-cai#list_proposals). It should open on the `list_proposals` method. - -- Set `limit` to whatever you want, although lower numbers are recommended for a more manageable data set. -- Set the `exclude_topic` length to `17` -- Add the following topics to the `exclude_topic` list: - - `0` for `Unspecified` - - `1` for `NeuronManagement` - - `2` for `ExchangeRate` - - `3` for `NetworkEconomics` - - `4` for `Governance` - - `5` for `NodeAdmin` - - `6` for `ParticipantManagement` - - `7` for `SubnetManagement` - - `8` for `NetworkCanisterManagement` - - `9` for `Kyc` - - `10` for `NodeProviderRewards` - - `12` for `IcOsVersionDeployment` - - `14` for `SnsAndCommunityFund` - - `15` for `ApiBoundaryNodeManagement` - - `16` for `SubnetRental` - - `17` for `ProtocolCanisterManagement` - - `18` for `ServiceNervousSystemManagement` -- Note that `11` currently doesn't exist, and `13` is `IcOsVersionElection`, the topic we want. -- Finally, click the `Call` button to get a list of proposals. - ### Manually syncing proposals To manually trigger the proposals synchronization from the Nervous Systems, run the following command: @@ -402,3 +387,13 @@ dfx canister call backend sync_proposals ``` This method can be called at any time, since if the proposals were already synced in the cron job, they won't be synced again. + +## Updating Candid files for external canisters + +To update the Candid files for external canisters, update the `RELEASE` variable in the `./scripts/update-scripts-canisters.sh` file. Make sure to also update referenced tag for the `ic-nns-governance` and `ic-nns-common` crates in the `Cargo.toml` file. + +Then run the following script: + +```bash +./scripts/update-external-canisters.sh +``` diff --git a/lib/nns-utils/src/canisters/governance.d.ts b/lib/nns-utils/src/canisters/governance.d.ts index d5585819..d7e3d939 100644 --- a/lib/nns-utils/src/canisters/governance.d.ts +++ b/lib/nns-utils/src/canisters/governance.d.ts @@ -8,6 +8,9 @@ export interface AccountIdentifier { export type Action = | { RegisterKnownNeuron: KnownNeuron } | { ManageNeuron: ManageNeuron } + | { UpdateCanisterSettings: UpdateCanisterSettings } + | { InstallCode: InstallCode } + | { StopOrStartCanister: StopOrStartCanister } | { CreateServiceNervousSystem: CreateServiceNervousSystem } | { ExecuteNnsFunction: ExecuteNnsFunction } | { RewardNodeProvider: RewardNodeProvider } @@ -16,7 +19,7 @@ export type Action = | { SetDefaultFollowees: SetDefaultFollowees } | { RewardNodeProviders: RewardNodeProviders } | { ManageNetworkEconomics: NetworkEconomics } - | { ApproveGenesisKyc: ApproveGenesisKyc } + | { ApproveGenesisKyc: Principals } | { AddOrRemoveNodeProvider: AddOrRemoveNodeProvider } | { Motion: Motion }; export interface AddHotKey { @@ -37,7 +40,7 @@ export interface Ballot { } export interface BallotInfo { vote: number; - proposal_id: [] | [NeuronId]; + proposal_id: [] | [ProposalId]; } export type By = | { NeuronIdOrSubaccount: {} } @@ -46,6 +49,15 @@ export type By = export interface Canister { id: [] | [Principal]; } +export interface CanisterSettings { + freezing_threshold: [] | [bigint]; + wasm_memory_threshold: [] | [bigint]; + controllers: [] | [Controllers]; + log_visibility: [] | [number]; + wasm_memory_limit: [] | [bigint]; + memory_allocation: [] | [bigint]; + compute_allocation: [] | [bigint]; +} export interface CanisterStatusResultV2 { status: [] | [number]; freezing_threshold: [] | [bigint]; @@ -59,15 +71,6 @@ export interface CanisterSummary { status: [] | [CanisterStatusResultV2]; canister_id: [] | [Principal]; } -export interface CfNeuron { - has_created_neuron_recipes: [] | [boolean]; - nns_neuron_id: bigint; - amount_icp_e8s: bigint; -} -export interface CfParticipant { - hotkey_principal: string; - cf_neurons: Array; -} export type Change = { ToRemove: NodeProvider } | { ToAdd: NodeProvider }; export interface ChangeAutoStakeMaturity { requested_setting_for_auto_stake_maturity: boolean; @@ -89,6 +92,7 @@ export type Command = | { Spawn: Spawn } | { Split: Split } | { Follow: Follow } + | { RefreshVotingPower: RefreshVotingPower } | { ClaimOrRefresh: ClaimOrRefresh } | { Configure: Configure } | { RegisterVote: RegisterVote } @@ -103,6 +107,7 @@ export type Command_1 = | { Spawn: SpawnResponse } | { Split: SpawnResponse } | { Follow: {} } + | { RefreshVotingPower: RefreshVotingPowerResponse } | { ClaimOrRefresh: ClaimOrRefreshResponse } | { Configure: {} } | { RegisterVote: {} } @@ -135,6 +140,9 @@ export interface Committed_1 { export interface Configure { operation: [] | [Operation]; } +export interface Controllers { + controllers: Array; +} export interface Countries { iso_codes: Array; } @@ -150,6 +158,13 @@ export interface CreateServiceNervousSystem { swap_parameters: [] | [SwapParameters]; initial_token_distribution: [] | [InitialTokenDistribution]; } +export interface DateRangeFilter { + start_timestamp_seconds: [] | [bigint]; + end_timestamp_seconds: [] | [bigint]; +} +export interface Decimal { + human_readable: [] | [string]; +} export interface DerivedProposalInformation { swap_background_information: [] | [SwapBackgroundInformation]; } @@ -193,20 +208,8 @@ export interface Followers { export interface FollowersMap { followers_map: Array<[bigint, Followers]>; } -export interface GenesisNeuronAccount { - id: bigint; - error_count: bigint; - neuron_type: number; - account_ids: Array; - tag_end_timestamp_seconds: [] | [bigint]; - amount_icp_e8s: bigint; - tag_start_timestamp_seconds: [] | [bigint]; -} -export interface GenesisNeuronAccounts { - genesis_neuron_accounts: Array; -} export interface GetNeuronsFundAuditInfoRequest { - nns_proposal_id: [] | [NeuronId]; + nns_proposal_id: [] | [ProposalId]; } export interface GetNeuronsFundAuditInfoResponse { result: [] | [Result_6]; @@ -217,17 +220,15 @@ export interface GlobalTimeOfDay { export interface Governance { default_followees: Array<[number, Followees]>; making_sns_proposal: [] | [MakingSnsProposal]; - most_recent_monthly_node_provider_rewards: - | [] - | [MostRecentMonthlyNodeProviderRewards]; + most_recent_monthly_node_provider_rewards: [] | [MonthlyNodeProviderRewards]; maturity_modulation_last_updated_at_timestamp_seconds: [] | [bigint]; - genesis_neuron_accounts: [] | [GenesisNeuronAccounts]; wait_for_quiet_threshold_seconds: bigint; metrics: [] | [GovernanceCachedMetrics]; neuron_management_voting_period_seconds: [] | [bigint]; node_providers: Array; cached_daily_maturity_modulation_basis_points: [] | [number]; economics: [] | [NetworkEconomics]; + restore_aging_summary: [] | [RestoreAgingSummary]; spawning_neurons: [] | [boolean]; latest_reward_event: [] | [RewardEvent]; to_claim_transfers: Array; @@ -235,6 +236,7 @@ export interface Governance { topic_followee_index: Array<[number, FollowersMap]>; migrations: [] | [Migrations]; proposals: Array<[bigint, ProposalData]>; + xdr_conversion_rate: [] | [XdrConversionRate]; in_flight_commands: Array<[bigint, NeuronInFlightCommand]>; neurons: Array<[bigint, Neuron]>; genesis_timestamp_seconds: bigint; @@ -257,14 +259,18 @@ export interface GovernanceCachedMetrics { total_staked_e8s_seed: bigint; total_staked_maturity_e8s_equivalent_ect: bigint; total_staked_e8s: bigint; + fully_lost_voting_power_neuron_subset_metrics: [] | [NeuronSubsetMetrics]; not_dissolving_neurons_count: bigint; total_locked_e8s: bigint; neurons_fund_total_active_neurons: bigint; + total_voting_power_non_self_authenticating_controller: [] | [bigint]; total_staked_maturity_e8s_equivalent: bigint; not_dissolving_neurons_e8s_buckets_ect: Array<[bigint, number]>; + declining_voting_power_neuron_subset_metrics: [] | [NeuronSubsetMetrics]; total_staked_e8s_ect: bigint; not_dissolving_neurons_staked_maturity_e8s_equivalent_sum: bigint; dissolved_neurons_e8s: bigint; + total_staked_e8s_non_self_authenticating_controller: [] | [bigint]; dissolving_neurons_e8s_buckets_seed: Array<[bigint, number]>; neurons_with_less_than_6_months_dissolve_delay_e8s: bigint; not_dissolving_neurons_staked_maturity_e8s_equivalent_buckets: Array< @@ -272,11 +278,15 @@ export interface GovernanceCachedMetrics { >; dissolving_neurons_count_buckets: Array<[bigint, bigint]>; dissolving_neurons_e8s_buckets_ect: Array<[bigint, number]>; + non_self_authenticating_controller_neuron_subset_metrics: + | [] + | [NeuronSubsetMetrics]; dissolving_neurons_count: bigint; dissolving_neurons_e8s_buckets: Array<[bigint, number]>; total_staked_maturity_e8s_equivalent_seed: bigint; community_fund_total_staked_e8s: bigint; not_dissolving_neurons_e8s_buckets_seed: Array<[bigint, number]>; + public_neuron_subset_metrics: [] | [NeuronSubsetMetrics]; timestamp_seconds: bigint; seed_neuron_count: bigint; } @@ -310,6 +320,20 @@ export interface InitialTokenDistribution { developer_distribution: [] | [DeveloperDistribution]; swap_distribution: [] | [SwapDistribution]; } +export interface InstallCode { + skip_stopping_before_installing: [] | [boolean]; + wasm_module_hash: [] | [Uint8Array | number[]]; + canister_id: [] | [Principal]; + arg_hash: [] | [Uint8Array | number[]]; + install_mode: [] | [number]; +} +export interface InstallCodeRequest { + arg: [] | [Uint8Array | number[]]; + wasm_module: [] | [Uint8Array | number[]]; + skip_stopping_before_installing: [] | [boolean]; + canister_id: [] | [Principal]; + install_mode: [] | [number]; +} export interface KnownNeuron { id: [] | [NeuronId]; known_neuron_data: [] | [KnownNeuronData]; @@ -328,20 +352,28 @@ export interface ListKnownNeuronsResponse { known_neurons: Array; } export interface ListNeurons { + include_public_neurons_in_full_neurons: [] | [boolean]; neuron_ids: BigUint64Array | bigint[]; + include_empty_neurons_readable_by_caller: [] | [boolean]; include_neurons_readable_by_caller: boolean; } export interface ListNeuronsResponse { neuron_infos: Array<[bigint, NeuronInfo]>; full_neurons: Array; } +export interface ListNodeProviderRewardsRequest { + date_filter: [] | [DateRangeFilter]; +} +export interface ListNodeProviderRewardsResponse { + rewards: Array; +} export interface ListNodeProvidersResponse { node_providers: Array; } export interface ListProposalInfo { include_reward_status: Int32Array | number[]; omit_large_fields: [] | [boolean]; - before_proposal: [] | [NeuronId]; + before_proposal: [] | [ProposalId]; limit: number; exclude_topic: Int32Array | number[]; include_all_manage_neuron_proposals: [] | [boolean]; @@ -350,9 +382,15 @@ export interface ListProposalInfo { export interface ListProposalInfoResponse { proposal_info: Array; } +export interface MakeProposalRequest { + url: string; + title: [] | [string]; + action: [] | [ProposalActionRequest]; + summary: string; +} export interface MakeProposalResponse { message: [] | [string]; - proposal_id: [] | [NeuronId]; + proposal_id: [] | [ProposalId]; } export interface MakingSnsProposal { proposal: [] | [Proposal]; @@ -364,6 +402,25 @@ export interface ManageNeuron { command: [] | [Command]; neuron_id_or_subaccount: [] | [NeuronIdOrSubaccount]; } +export type ManageNeuronCommandRequest = + | { Spawn: Spawn } + | { Split: Split } + | { Follow: Follow } + | { RefreshVotingPower: RefreshVotingPower } + | { ClaimOrRefresh: ClaimOrRefresh } + | { Configure: Configure } + | { RegisterVote: RegisterVote } + | { Merge: Merge } + | { DisburseToNeuron: DisburseToNeuron } + | { MakeProposal: MakeProposalRequest } + | { StakeMaturity: StakeMaturity } + | { MergeMaturity: MergeMaturity } + | { Disburse: Disburse }; +export interface ManageNeuronRequest { + id: [] | [NeuronId]; + command: [] | [ManageNeuronCommandRequest]; + neuron_id_or_subaccount: [] | [NeuronIdOrSubaccount]; +} export interface ManageNeuronResponse { command: [] | [Command_1]; } @@ -392,15 +449,21 @@ export interface Migrations { neuron_indexes_migration: [] | [Migration]; copy_inactive_neurons_to_stable_memory_migration: [] | [Migration]; } -export interface MostRecentMonthlyNodeProviderRewards { +export interface MonthlyNodeProviderRewards { + minimum_xdr_permyriad_per_icp: [] | [bigint]; + registry_version: [] | [bigint]; + node_providers: Array; timestamp: bigint; rewards: Array; + xdr_conversion_rate: [] | [XdrConversionRate]; + maximum_node_provider_rewards_e8s: [] | [bigint]; } export interface Motion { motion_text: string; } export interface NetworkEconomics { neuron_minimum_stake_e8s: bigint; + voting_power_economics: [] | [VotingPowerEconomics]; max_proposals_to_keep_per_topic: number; neuron_management_fee_per_proposal_e8s: bigint; reject_cost_e8s: bigint; @@ -408,16 +471,20 @@ export interface NetworkEconomics { neuron_spawn_dissolve_delay_seconds: bigint; minimum_icp_xdr_rate: bigint; maximum_node_provider_rewards_e8s: bigint; + neurons_fund_economics: [] | [NeuronsFundEconomics]; } export interface Neuron { id: [] | [NeuronId]; staked_maturity_e8s_equivalent: [] | [bigint]; controller: [] | [Principal]; recent_ballots: Array; + voting_power_refreshed_timestamp_seconds: [] | [bigint]; kyc_verified: boolean; + potential_voting_power: [] | [bigint]; neuron_type: [] | [number]; not_for_profit: boolean; maturity_e8s_equivalent: bigint; + deciding_voting_power: [] | [bigint]; cached_neuron_stake_e8s: bigint; created_timestamp_seconds: bigint; auto_stake_maturity: [] | [boolean]; @@ -428,6 +495,7 @@ export interface Neuron { dissolve_state: [] | [DissolveState]; followees: Array<[number, Followees]>; neuron_fees_e8s: bigint; + visibility: [] | [number]; transfer: [] | [NeuronStakeTransfer]; known_neuron_data: [] | [KnownNeuronData]; spawn_at_timestamp_seconds: [] | [bigint]; @@ -460,12 +528,16 @@ export interface NeuronInFlightCommand { export interface NeuronInfo { dissolve_delay_seconds: bigint; recent_ballots: Array; + voting_power_refreshed_timestamp_seconds: [] | [bigint]; + potential_voting_power: [] | [bigint]; neuron_type: [] | [number]; + deciding_voting_power: [] | [bigint]; created_timestamp_seconds: bigint; state: number; stake_e8s: bigint; joined_community_fund_timestamp_seconds: [] | [bigint]; retrieved_at_timestamp_seconds: bigint; + visibility: [] | [number]; known_neuron_data: [] | [KnownNeuronData]; voting_power: bigint; age_seconds: bigint; @@ -479,6 +551,22 @@ export interface NeuronStakeTransfer { transfer_timestamp: bigint; block_height: bigint; } +export interface NeuronSubsetMetrics { + total_maturity_e8s_equivalent: [] | [bigint]; + maturity_e8s_equivalent_buckets: Array<[bigint, bigint]>; + voting_power_buckets: Array<[bigint, bigint]>; + total_staked_e8s: [] | [bigint]; + count: [] | [bigint]; + deciding_voting_power_buckets: Array<[bigint, bigint]>; + total_staked_maturity_e8s_equivalent: [] | [bigint]; + total_potential_voting_power: [] | [bigint]; + total_deciding_voting_power: [] | [bigint]; + staked_maturity_e8s_equivalent_buckets: Array<[bigint, bigint]>; + staked_e8s_buckets: Array<[bigint, bigint]>; + total_voting_power: [] | [bigint]; + potential_voting_power_buckets: Array<[bigint, bigint]>; + count_buckets: Array<[bigint, bigint]>; +} export interface NeuronsFundAuditInfo { final_neurons_fund_participation: [] | [NeuronsFundParticipation]; initial_neurons_fund_participation: [] | [NeuronsFundParticipation]; @@ -489,14 +577,29 @@ export interface NeuronsFundData { initial_neurons_fund_participation: [] | [NeuronsFundParticipation]; neurons_fund_refunds: [] | [NeuronsFundSnapshot]; } +export interface NeuronsFundEconomics { + maximum_icp_xdr_rate: [] | [Percentage]; + neurons_fund_matched_funding_curve_coefficients: + | [] + | [NeuronsFundMatchedFundingCurveCoefficients]; + max_theoretical_neurons_fund_participation_amount_xdr: [] | [Decimal]; + minimum_icp_xdr_rate: [] | [Percentage]; +} +export interface NeuronsFundMatchedFundingCurveCoefficients { + contribution_threshold_xdr: [] | [Decimal]; + one_third_participation_milestone_xdr: [] | [Decimal]; + full_participation_milestone_xdr: [] | [Decimal]; +} export interface NeuronsFundNeuron { - hotkey_principal: [] | [string]; + controller: [] | [Principal]; + hotkeys: [] | [Principals]; is_capped: [] | [boolean]; nns_neuron_id: [] | [bigint]; amount_icp_e8s: [] | [bigint]; } export interface NeuronsFundNeuronPortion { - hotkey_principal: [] | [Principal]; + controller: [] | [Principal]; + hotkeys: Array; is_capped: [] | [boolean]; maturity_equivalent_icp_e8s: [] | [bigint]; nns_neuron_id: [] | [NeuronId]; @@ -539,6 +642,7 @@ export type Operation = | { StopDissolving: {} } | { StartDissolving: {} } | { IncreaseDissolveDelay: IncreaseDissolveDelay } + | { SetVisibility: SetVisibility } | { JoinCommunityFund: {} } | { LeaveCommunityFund: {} } | { SetDissolveTimestamp: SetDissolveTimestamp }; @@ -560,6 +664,9 @@ export interface Params { export interface Percentage { basis_points: [] | [bigint]; } +export interface Principals { + principals: Array; +} export type Progress = { LastNeuronId: NeuronId }; export interface Proposal { url: string; @@ -567,10 +674,23 @@ export interface Proposal { action: [] | [Action]; summary: string; } +export type ProposalActionRequest = + | { RegisterKnownNeuron: KnownNeuron } + | { ManageNeuron: ManageNeuronRequest } + | { UpdateCanisterSettings: UpdateCanisterSettings } + | { InstallCode: InstallCodeRequest } + | { StopOrStartCanister: StopOrStartCanister } + | { CreateServiceNervousSystem: CreateServiceNervousSystem } + | { ExecuteNnsFunction: ExecuteNnsFunction } + | { RewardNodeProvider: RewardNodeProvider } + | { RewardNodeProviders: RewardNodeProviders } + | { ManageNetworkEconomics: NetworkEconomics } + | { ApproveGenesisKyc: Principals } + | { AddOrRemoveNodeProvider: AddOrRemoveNodeProvider } + | { Motion: Motion }; export interface ProposalData { - id: [] | [NeuronId]; + id: [] | [ProposalId]; failure_reason: [] | [GovernanceError]; - cf_participants: Array; ballots: Array<[bigint, Ballot]>; proposal_timestamp_seconds: bigint; reward_event_round: bigint; @@ -579,6 +699,7 @@ export interface ProposalData { reject_cost_e8s: bigint; derived_proposal_information: [] | [DerivedProposalInformation]; latest_tally: [] | [Tally]; + total_potential_voting_power: [] | [bigint]; sns_token_swap_lifecycle: [] | [number]; decided_timestamp_seconds: bigint; proposal: [] | [Proposal]; @@ -587,8 +708,11 @@ export interface ProposalData { executed_timestamp_seconds: bigint; original_total_community_fund_maturity_e8s_equivalent: [] | [bigint]; } +export interface ProposalId { + id: bigint; +} export interface ProposalInfo { - id: [] | [NeuronId]; + id: [] | [ProposalId]; status: number; topic: number; failure_reason: [] | [GovernanceError]; @@ -600,19 +724,32 @@ export interface ProposalInfo { reject_cost_e8s: bigint; derived_proposal_information: [] | [DerivedProposalInformation]; latest_tally: [] | [Tally]; + total_potential_voting_power: [] | [bigint]; reward_status: number; decided_timestamp_seconds: bigint; proposal: [] | [Proposal]; proposer: [] | [NeuronId]; executed_timestamp_seconds: bigint; } +export type RefreshVotingPower = {}; +export type RefreshVotingPowerResponse = {}; export interface RegisterVote { vote: number; - proposal: [] | [NeuronId]; + proposal: [] | [ProposalId]; } export interface RemoveHotKey { hot_key_to_remove: [] | [Principal]; } +export interface RestoreAgingNeuronGroup { + count: [] | [bigint]; + previous_total_stake_e8s: [] | [bigint]; + current_total_stake_e8s: [] | [bigint]; + group_type: number; +} +export interface RestoreAgingSummary { + groups: Array; + timestamp_seconds: [] | [bigint]; +} export type Result = { Ok: null } | { Err: GovernanceError }; export type Result_1 = { Error: GovernanceError } | { NeuronId: NeuronId }; export type Result_10 = { Ok: Ok_1 } | { Err: GovernanceError }; @@ -620,7 +757,9 @@ export type Result_2 = { Ok: Neuron } | { Err: GovernanceError }; export type Result_3 = | { Ok: GovernanceCachedMetrics } | { Err: GovernanceError }; -export type Result_4 = { Ok: RewardNodeProviders } | { Err: GovernanceError }; +export type Result_4 = + | { Ok: MonthlyNodeProviderRewards } + | { Err: GovernanceError }; export type Result_5 = { Ok: NeuronInfo } | { Err: GovernanceError }; export type Result_6 = { Ok: Ok } | { Err: GovernanceError }; export type Result_7 = { Ok: NodeProvider } | { Err: GovernanceError }; @@ -633,7 +772,7 @@ export interface RewardEvent { total_available_e8s_equivalent: bigint; latest_round_available_e8s_equivalent: [] | [bigint]; distributed_e8s_equivalent: bigint; - settled_proposals: Array; + settled_proposals: Array; } export type RewardMode = | { RewardToNeuron: RewardToNeuron } @@ -666,6 +805,9 @@ export interface SetSnsTokenSwapOpenTimeWindow { request: [] | [SetOpenTimeWindowRequest]; swap_canister_id: [] | [Principal]; } +export interface SetVisibility { + visibility: [] | [number]; +} export interface SettleCommunityFundParticipation { result: [] | [Result_8]; open_sns_token_swap_proposal_id: [] | [bigint]; @@ -695,6 +837,10 @@ export interface StakeMaturityResponse { maturity_e8s: bigint; staked_maturity_e8s: bigint; } +export interface StopOrStartCanister { + action: [] | [number]; + canister_id: [] | [Principal]; +} export interface SwapBackgroundInformation { ledger_index_canister_summary: [] | [CanisterSummary]; fallback_controller_principal_ids: Array; @@ -745,9 +891,17 @@ export interface TimeWindow { export interface Tokens { e8s: [] | [bigint]; } +export interface UpdateCanisterSettings { + canister_id: [] | [Principal]; + settings: [] | [CanisterSettings]; +} export interface UpdateNodeProvider { reward_account: [] | [AccountIdentifier]; } +export interface VotingPowerEconomics { + start_reducing_voting_power_after_seconds: [] | [bigint]; + clear_following_after_seconds: [] | [bigint]; +} export interface VotingRewardParameters { reward_rate_transition_duration: [] | [Duration]; initial_reward_rate: [] | [Percentage]; @@ -756,6 +910,10 @@ export interface VotingRewardParameters { export interface WaitForQuietState { current_deadline_timestamp_seconds: bigint; } +export interface XdrConversionRate { + xdr_permyriad_per_icp: [] | [bigint]; + timestamp_seconds: [] | [bigint]; +} export interface _SERVICE { claim_gtc_neurons: ActorMethod<[Principal, Array], Result>; claim_or_refresh_neuron_from_account: ActorMethod< @@ -773,7 +931,7 @@ export interface _SERVICE { get_monthly_node_provider_rewards: ActorMethod<[], Result_4>; get_most_recent_monthly_node_provider_rewards: ActorMethod< [], - [] | [MostRecentMonthlyNodeProviderRewards] + [] | [MonthlyNodeProviderRewards] >; get_network_economics_parameters: ActorMethod<[], NetworkEconomics>; get_neuron_ids: ActorMethod<[], BigUint64Array | bigint[]>; @@ -789,11 +947,16 @@ export interface _SERVICE { get_node_provider_by_caller: ActorMethod<[null], Result_7>; get_pending_proposals: ActorMethod<[], Array>; get_proposal_info: ActorMethod<[bigint], [] | [ProposalInfo]>; + get_restore_aging_summary: ActorMethod<[], RestoreAgingSummary>; list_known_neurons: ActorMethod<[], ListKnownNeuronsResponse>; list_neurons: ActorMethod<[ListNeurons], ListNeuronsResponse>; + list_node_provider_rewards: ActorMethod< + [ListNodeProviderRewardsRequest], + ListNodeProviderRewardsResponse + >; list_node_providers: ActorMethod<[], ListNodeProvidersResponse>; list_proposals: ActorMethod<[ListProposalInfo], ListProposalInfoResponse>; - manage_neuron: ActorMethod<[ManageNeuron], ManageNeuronResponse>; + manage_neuron: ActorMethod<[ManageNeuronRequest], ManageNeuronResponse>; settle_community_fund_participation: ActorMethod< [SettleCommunityFundParticipation], Result @@ -802,7 +965,10 @@ export interface _SERVICE { [SettleNeuronsFundParticipationRequest], SettleNeuronsFundParticipationResponse >; - simulate_manage_neuron: ActorMethod<[ManageNeuron], ManageNeuronResponse>; + simulate_manage_neuron: ActorMethod< + [ManageNeuronRequest], + ManageNeuronResponse + >; transfer_gtc_neuron: ActorMethod<[NeuronId, NeuronId], Result>; update_node_provider: ActorMethod<[UpdateNodeProvider], Result>; } diff --git a/lib/nns-utils/src/canisters/governance.js b/lib/nns-utils/src/canisters/governance.js index adc6b430..47651810 100644 --- a/lib/nns-utils/src/canisters/governance.js +++ b/lib/nns-utils/src/canisters/governance.js @@ -1,4 +1,5 @@ export const idlFactory = ({ IDL }) => { + const ManageNeuronRequest = IDL.Rec(); const Proposal = IDL.Rec(); const NeuronId = IDL.Record({ id: IDL.Nat64 }); const Followees = IDL.Record({ followees: IDL.Vec(NeuronId) }); @@ -20,6 +21,7 @@ export const idlFactory = ({ IDL }) => { topic: IDL.Int32, followees: IDL.Vec(NeuronId), }); + const RefreshVotingPower = IDL.Record({}); const ClaimOrRefreshNeuronFromAccount = IDL.Record({ controller: IDL.Opt(IDL.Principal), memo: IDL.Nat64, @@ -40,6 +42,7 @@ export const idlFactory = ({ IDL }) => { const IncreaseDissolveDelay = IDL.Record({ additional_dissolve_delay_seconds: IDL.Nat32, }); + const SetVisibility = IDL.Record({ visibility: IDL.Opt(IDL.Int32) }); const SetDissolveTimestamp = IDL.Record({ dissolve_timestamp_seconds: IDL.Nat64, }); @@ -50,14 +53,16 @@ export const idlFactory = ({ IDL }) => { StopDissolving: IDL.Record({}), StartDissolving: IDL.Record({}), IncreaseDissolveDelay: IncreaseDissolveDelay, + SetVisibility: SetVisibility, JoinCommunityFund: IDL.Record({}), LeaveCommunityFund: IDL.Record({}), SetDissolveTimestamp: SetDissolveTimestamp, }); const Configure = IDL.Record({ operation: IDL.Opt(Operation) }); + const ProposalId = IDL.Record({ id: IDL.Nat64 }); const RegisterVote = IDL.Record({ vote: IDL.Int32, - proposal: IDL.Opt(NeuronId), + proposal: IDL.Opt(ProposalId), }); const Merge = IDL.Record({ source_neuron_id: IDL.Opt(NeuronId) }); const DisburseToNeuron = IDL.Record({ @@ -81,6 +86,7 @@ export const idlFactory = ({ IDL }) => { Spawn: Spawn, Split: Split, Follow: Follow, + RefreshVotingPower: RefreshVotingPower, ClaimOrRefresh: ClaimOrRefresh, Configure: Configure, RegisterVote: RegisterVote, @@ -100,6 +106,31 @@ export const idlFactory = ({ IDL }) => { command: IDL.Opt(Command), neuron_id_or_subaccount: IDL.Opt(NeuronIdOrSubaccount), }); + const Controllers = IDL.Record({ controllers: IDL.Vec(IDL.Principal) }); + const CanisterSettings = IDL.Record({ + freezing_threshold: IDL.Opt(IDL.Nat64), + wasm_memory_threshold: IDL.Opt(IDL.Nat64), + controllers: IDL.Opt(Controllers), + log_visibility: IDL.Opt(IDL.Int32), + wasm_memory_limit: IDL.Opt(IDL.Nat64), + memory_allocation: IDL.Opt(IDL.Nat64), + compute_allocation: IDL.Opt(IDL.Nat64), + }); + const UpdateCanisterSettings = IDL.Record({ + canister_id: IDL.Opt(IDL.Principal), + settings: IDL.Opt(CanisterSettings), + }); + const InstallCode = IDL.Record({ + skip_stopping_before_installing: IDL.Opt(IDL.Bool), + wasm_module_hash: IDL.Opt(IDL.Vec(IDL.Nat8)), + canister_id: IDL.Opt(IDL.Principal), + arg_hash: IDL.Opt(IDL.Vec(IDL.Nat8)), + install_mode: IDL.Opt(IDL.Int32), + }); + const StopOrStartCanister = IDL.Record({ + action: IDL.Opt(IDL.Int32), + canister_id: IDL.Opt(IDL.Principal), + }); const Percentage = IDL.Record({ basis_points: IDL.Opt(IDL.Nat64) }); const Duration = IDL.Record({ seconds: IDL.Opt(IDL.Nat64) }); const Tokens = IDL.Record({ e8s: IDL.Opt(IDL.Nat64) }); @@ -245,8 +276,27 @@ export const idlFactory = ({ IDL }) => { use_registry_derived_rewards: IDL.Opt(IDL.Bool), rewards: IDL.Vec(RewardNodeProvider), }); + const VotingPowerEconomics = IDL.Record({ + start_reducing_voting_power_after_seconds: IDL.Opt(IDL.Nat64), + clear_following_after_seconds: IDL.Opt(IDL.Nat64), + }); + const Decimal = IDL.Record({ human_readable: IDL.Opt(IDL.Text) }); + const NeuronsFundMatchedFundingCurveCoefficients = IDL.Record({ + contribution_threshold_xdr: IDL.Opt(Decimal), + one_third_participation_milestone_xdr: IDL.Opt(Decimal), + full_participation_milestone_xdr: IDL.Opt(Decimal), + }); + const NeuronsFundEconomics = IDL.Record({ + maximum_icp_xdr_rate: IDL.Opt(Percentage), + neurons_fund_matched_funding_curve_coefficients: IDL.Opt( + NeuronsFundMatchedFundingCurveCoefficients, + ), + max_theoretical_neurons_fund_participation_amount_xdr: IDL.Opt(Decimal), + minimum_icp_xdr_rate: IDL.Opt(Percentage), + }); const NetworkEconomics = IDL.Record({ neuron_minimum_stake_e8s: IDL.Nat64, + voting_power_economics: IDL.Opt(VotingPowerEconomics), max_proposals_to_keep_per_topic: IDL.Nat32, neuron_management_fee_per_proposal_e8s: IDL.Nat64, reject_cost_e8s: IDL.Nat64, @@ -254,10 +304,9 @@ export const idlFactory = ({ IDL }) => { neuron_spawn_dissolve_delay_seconds: IDL.Nat64, minimum_icp_xdr_rate: IDL.Nat64, maximum_node_provider_rewards_e8s: IDL.Nat64, + neurons_fund_economics: IDL.Opt(NeuronsFundEconomics), }); - const ApproveGenesisKyc = IDL.Record({ - principals: IDL.Vec(IDL.Principal), - }); + const Principals = IDL.Record({ principals: IDL.Vec(IDL.Principal) }); const Change = IDL.Variant({ ToRemove: NodeProvider, ToAdd: NodeProvider, @@ -267,6 +316,9 @@ export const idlFactory = ({ IDL }) => { const Action = IDL.Variant({ RegisterKnownNeuron: KnownNeuron, ManageNeuron: ManageNeuron, + UpdateCanisterSettings: UpdateCanisterSettings, + InstallCode: InstallCode, + StopOrStartCanister: StopOrStartCanister, CreateServiceNervousSystem: CreateServiceNervousSystem, ExecuteNnsFunction: ExecuteNnsFunction, RewardNodeProvider: RewardNodeProvider, @@ -275,7 +327,7 @@ export const idlFactory = ({ IDL }) => { SetDefaultFollowees: SetDefaultFollowees, RewardNodeProviders: RewardNodeProviders, ManageNetworkEconomics: NetworkEconomics, - ApproveGenesisKyc: ApproveGenesisKyc, + ApproveGenesisKyc: Principals, AddOrRemoveNodeProvider: AddOrRemoveNodeProvider, Motion: Motion, }); @@ -292,21 +344,36 @@ export const idlFactory = ({ IDL }) => { caller: IDL.Opt(IDL.Principal), proposer_id: IDL.Opt(NeuronId), }); - const MostRecentMonthlyNodeProviderRewards = IDL.Record({ + const XdrConversionRate = IDL.Record({ + xdr_permyriad_per_icp: IDL.Opt(IDL.Nat64), + timestamp_seconds: IDL.Opt(IDL.Nat64), + }); + const MonthlyNodeProviderRewards = IDL.Record({ + minimum_xdr_permyriad_per_icp: IDL.Opt(IDL.Nat64), + registry_version: IDL.Opt(IDL.Nat64), + node_providers: IDL.Vec(NodeProvider), timestamp: IDL.Nat64, rewards: IDL.Vec(RewardNodeProvider), - }); - const GenesisNeuronAccount = IDL.Record({ - id: IDL.Nat64, - error_count: IDL.Nat64, - neuron_type: IDL.Int32, - account_ids: IDL.Vec(IDL.Text), - tag_end_timestamp_seconds: IDL.Opt(IDL.Nat64), - amount_icp_e8s: IDL.Nat64, - tag_start_timestamp_seconds: IDL.Opt(IDL.Nat64), - }); - const GenesisNeuronAccounts = IDL.Record({ - genesis_neuron_accounts: IDL.Vec(GenesisNeuronAccount), + xdr_conversion_rate: IDL.Opt(XdrConversionRate), + maximum_node_provider_rewards_e8s: IDL.Opt(IDL.Nat64), + }); + const NeuronSubsetMetrics = IDL.Record({ + total_maturity_e8s_equivalent: IDL.Opt(IDL.Nat64), + maturity_e8s_equivalent_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + voting_power_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + total_staked_e8s: IDL.Opt(IDL.Nat64), + count: IDL.Opt(IDL.Nat64), + deciding_voting_power_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + total_staked_maturity_e8s_equivalent: IDL.Opt(IDL.Nat64), + total_potential_voting_power: IDL.Opt(IDL.Nat64), + total_deciding_voting_power: IDL.Opt(IDL.Nat64), + staked_maturity_e8s_equivalent_buckets: IDL.Vec( + IDL.Tuple(IDL.Nat64, IDL.Nat64), + ), + staked_e8s_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + total_voting_power: IDL.Opt(IDL.Nat64), + potential_voting_power_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + count_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), }); const GovernanceCachedMetrics = IDL.Record({ total_maturity_e8s_equivalent: IDL.Nat64, @@ -330,16 +397,20 @@ export const idlFactory = ({ IDL }) => { total_staked_e8s_seed: IDL.Nat64, total_staked_maturity_e8s_equivalent_ect: IDL.Nat64, total_staked_e8s: IDL.Nat64, + fully_lost_voting_power_neuron_subset_metrics: IDL.Opt(NeuronSubsetMetrics), not_dissolving_neurons_count: IDL.Nat64, total_locked_e8s: IDL.Nat64, neurons_fund_total_active_neurons: IDL.Nat64, + total_voting_power_non_self_authenticating_controller: IDL.Opt(IDL.Nat64), total_staked_maturity_e8s_equivalent: IDL.Nat64, not_dissolving_neurons_e8s_buckets_ect: IDL.Vec( IDL.Tuple(IDL.Nat64, IDL.Float64), ), + declining_voting_power_neuron_subset_metrics: IDL.Opt(NeuronSubsetMetrics), total_staked_e8s_ect: IDL.Nat64, not_dissolving_neurons_staked_maturity_e8s_equivalent_sum: IDL.Nat64, dissolved_neurons_e8s: IDL.Nat64, + total_staked_e8s_non_self_authenticating_controller: IDL.Opt(IDL.Nat64), dissolving_neurons_e8s_buckets_seed: IDL.Vec( IDL.Tuple(IDL.Nat64, IDL.Float64), ), @@ -351,6 +422,8 @@ export const idlFactory = ({ IDL }) => { dissolving_neurons_e8s_buckets_ect: IDL.Vec( IDL.Tuple(IDL.Nat64, IDL.Float64), ), + non_self_authenticating_controller_neuron_subset_metrics: + IDL.Opt(NeuronSubsetMetrics), dissolving_neurons_count: IDL.Nat64, dissolving_neurons_e8s_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Float64)), total_staked_maturity_e8s_equivalent_seed: IDL.Nat64, @@ -358,9 +431,20 @@ export const idlFactory = ({ IDL }) => { not_dissolving_neurons_e8s_buckets_seed: IDL.Vec( IDL.Tuple(IDL.Nat64, IDL.Float64), ), + public_neuron_subset_metrics: IDL.Opt(NeuronSubsetMetrics), timestamp_seconds: IDL.Nat64, seed_neuron_count: IDL.Nat64, }); + const RestoreAgingNeuronGroup = IDL.Record({ + count: IDL.Opt(IDL.Nat64), + previous_total_stake_e8s: IDL.Opt(IDL.Nat64), + current_total_stake_e8s: IDL.Opt(IDL.Nat64), + group_type: IDL.Int32, + }); + const RestoreAgingSummary = IDL.Record({ + groups: IDL.Vec(RestoreAgingNeuronGroup), + timestamp_seconds: IDL.Opt(IDL.Nat64), + }); const RewardEvent = IDL.Record({ rounds_since_last_distribution: IDL.Opt(IDL.Nat64), day_after_genesis: IDL.Nat64, @@ -368,7 +452,7 @@ export const idlFactory = ({ IDL }) => { total_available_e8s_equivalent: IDL.Nat64, latest_round_available_e8s_equivalent: IDL.Opt(IDL.Nat64), distributed_e8s_equivalent: IDL.Nat64, - settled_proposals: IDL.Vec(NeuronId), + settled_proposals: IDL.Vec(ProposalId), }); const NeuronStakeTransfer = IDL.Record({ to_subaccount: IDL.Vec(IDL.Nat8), @@ -397,15 +481,6 @@ export const idlFactory = ({ IDL }) => { error_message: IDL.Text, error_type: IDL.Int32, }); - const CfNeuron = IDL.Record({ - has_created_neuron_recipes: IDL.Opt(IDL.Bool), - nns_neuron_id: IDL.Nat64, - amount_icp_e8s: IDL.Nat64, - }); - const CfParticipant = IDL.Record({ - hotkey_principal: IDL.Text, - cf_neurons: IDL.Vec(CfNeuron), - }); const Ballot = IDL.Record({ vote: IDL.Int32, voting_power: IDL.Nat64 }); const SwapParticipationLimits = IDL.Record({ min_participant_icp_e8s: IDL.Opt(IDL.Nat64), @@ -414,7 +489,8 @@ export const idlFactory = ({ IDL }) => { max_direct_participation_icp_e8s: IDL.Opt(IDL.Nat64), }); const NeuronsFundNeuronPortion = IDL.Record({ - hotkey_principal: IDL.Opt(IDL.Principal), + controller: IDL.Opt(IDL.Principal), + hotkeys: IDL.Vec(IDL.Principal), is_capped: IDL.Opt(IDL.Bool), maturity_equivalent_icp_e8s: IDL.Opt(IDL.Nat64), nns_neuron_id: IDL.Opt(NeuronId), @@ -479,9 +555,8 @@ export const idlFactory = ({ IDL }) => { current_deadline_timestamp_seconds: IDL.Nat64, }); const ProposalData = IDL.Record({ - id: IDL.Opt(NeuronId), + id: IDL.Opt(ProposalId), failure_reason: IDL.Opt(GovernanceError), - cf_participants: IDL.Vec(CfParticipant), ballots: IDL.Vec(IDL.Tuple(IDL.Nat64, Ballot)), proposal_timestamp_seconds: IDL.Nat64, reward_event_round: IDL.Nat64, @@ -490,6 +565,7 @@ export const idlFactory = ({ IDL }) => { reject_cost_e8s: IDL.Nat64, derived_proposal_information: IDL.Opt(DerivedProposalInformation), latest_tally: IDL.Opt(Tally), + total_potential_voting_power: IDL.Opt(IDL.Nat64), sns_token_swap_lifecycle: IDL.Opt(IDL.Int32), decided_timestamp_seconds: IDL.Nat64, proposal: IDL.Opt(Proposal), @@ -515,7 +591,7 @@ export const idlFactory = ({ IDL }) => { }); const BallotInfo = IDL.Record({ vote: IDL.Int32, - proposal_id: IDL.Opt(NeuronId), + proposal_id: IDL.Opt(ProposalId), }); const DissolveState = IDL.Variant({ DissolveDelaySeconds: IDL.Nat64, @@ -526,10 +602,13 @@ export const idlFactory = ({ IDL }) => { staked_maturity_e8s_equivalent: IDL.Opt(IDL.Nat64), controller: IDL.Opt(IDL.Principal), recent_ballots: IDL.Vec(BallotInfo), + voting_power_refreshed_timestamp_seconds: IDL.Opt(IDL.Nat64), kyc_verified: IDL.Bool, + potential_voting_power: IDL.Opt(IDL.Nat64), neuron_type: IDL.Opt(IDL.Int32), not_for_profit: IDL.Bool, maturity_e8s_equivalent: IDL.Nat64, + deciding_voting_power: IDL.Opt(IDL.Nat64), cached_neuron_stake_e8s: IDL.Nat64, created_timestamp_seconds: IDL.Nat64, auto_stake_maturity: IDL.Opt(IDL.Bool), @@ -540,6 +619,7 @@ export const idlFactory = ({ IDL }) => { dissolve_state: IDL.Opt(DissolveState), followees: IDL.Vec(IDL.Tuple(IDL.Int32, Followees)), neuron_fees_e8s: IDL.Nat64, + visibility: IDL.Opt(IDL.Int32), transfer: IDL.Opt(NeuronStakeTransfer), known_neuron_data: IDL.Opt(KnownNeuronData), spawn_at_timestamp_seconds: IDL.Opt(IDL.Nat64), @@ -548,16 +628,16 @@ export const idlFactory = ({ IDL }) => { default_followees: IDL.Vec(IDL.Tuple(IDL.Int32, Followees)), making_sns_proposal: IDL.Opt(MakingSnsProposal), most_recent_monthly_node_provider_rewards: IDL.Opt( - MostRecentMonthlyNodeProviderRewards, + MonthlyNodeProviderRewards, ), maturity_modulation_last_updated_at_timestamp_seconds: IDL.Opt(IDL.Nat64), - genesis_neuron_accounts: IDL.Opt(GenesisNeuronAccounts), wait_for_quiet_threshold_seconds: IDL.Nat64, metrics: IDL.Opt(GovernanceCachedMetrics), neuron_management_voting_period_seconds: IDL.Opt(IDL.Nat64), node_providers: IDL.Vec(NodeProvider), cached_daily_maturity_modulation_basis_points: IDL.Opt(IDL.Int32), economics: IDL.Opt(NetworkEconomics), + restore_aging_summary: IDL.Opt(RestoreAgingSummary), spawning_neurons: IDL.Opt(IDL.Bool), latest_reward_event: IDL.Opt(RewardEvent), to_claim_transfers: IDL.Vec(NeuronStakeTransfer), @@ -565,6 +645,7 @@ export const idlFactory = ({ IDL }) => { topic_followee_index: IDL.Vec(IDL.Tuple(IDL.Int32, FollowersMap)), migrations: IDL.Opt(Migrations), proposals: IDL.Vec(IDL.Tuple(IDL.Nat64, ProposalData)), + xdr_conversion_rate: IDL.Opt(XdrConversionRate), in_flight_commands: IDL.Vec(IDL.Tuple(IDL.Nat64, NeuronInFlightCommand)), neurons: IDL.Vec(IDL.Tuple(IDL.Nat64, Neuron)), genesis_timestamp_seconds: IDL.Nat64, @@ -583,25 +664,29 @@ export const idlFactory = ({ IDL }) => { Err: GovernanceError, }); const Result_4 = IDL.Variant({ - Ok: RewardNodeProviders, + Ok: MonthlyNodeProviderRewards, Err: GovernanceError, }); const NeuronInfo = IDL.Record({ dissolve_delay_seconds: IDL.Nat64, recent_ballots: IDL.Vec(BallotInfo), + voting_power_refreshed_timestamp_seconds: IDL.Opt(IDL.Nat64), + potential_voting_power: IDL.Opt(IDL.Nat64), neuron_type: IDL.Opt(IDL.Int32), + deciding_voting_power: IDL.Opt(IDL.Nat64), created_timestamp_seconds: IDL.Nat64, state: IDL.Int32, stake_e8s: IDL.Nat64, joined_community_fund_timestamp_seconds: IDL.Opt(IDL.Nat64), retrieved_at_timestamp_seconds: IDL.Nat64, + visibility: IDL.Opt(IDL.Int32), known_neuron_data: IDL.Opt(KnownNeuronData), voting_power: IDL.Nat64, age_seconds: IDL.Nat64, }); const Result_5 = IDL.Variant({ Ok: NeuronInfo, Err: GovernanceError }); const GetNeuronsFundAuditInfoRequest = IDL.Record({ - nns_proposal_id: IDL.Opt(NeuronId), + nns_proposal_id: IDL.Opt(ProposalId), }); const NeuronsFundAuditInfo = IDL.Record({ final_neurons_fund_participation: IDL.Opt(NeuronsFundParticipation), @@ -620,7 +705,7 @@ export const idlFactory = ({ IDL }) => { Err: GovernanceError, }); const ProposalInfo = IDL.Record({ - id: IDL.Opt(NeuronId), + id: IDL.Opt(ProposalId), status: IDL.Int32, topic: IDL.Int32, failure_reason: IDL.Opt(GovernanceError), @@ -632,6 +717,7 @@ export const idlFactory = ({ IDL }) => { reject_cost_e8s: IDL.Nat64, derived_proposal_information: IDL.Opt(DerivedProposalInformation), latest_tally: IDL.Opt(Tally), + total_potential_voting_power: IDL.Opt(IDL.Nat64), reward_status: IDL.Int32, decided_timestamp_seconds: IDL.Nat64, proposal: IDL.Opt(Proposal), @@ -642,20 +728,32 @@ export const idlFactory = ({ IDL }) => { known_neurons: IDL.Vec(KnownNeuron), }); const ListNeurons = IDL.Record({ + include_public_neurons_in_full_neurons: IDL.Opt(IDL.Bool), neuron_ids: IDL.Vec(IDL.Nat64), + include_empty_neurons_readable_by_caller: IDL.Opt(IDL.Bool), include_neurons_readable_by_caller: IDL.Bool, }); const ListNeuronsResponse = IDL.Record({ neuron_infos: IDL.Vec(IDL.Tuple(IDL.Nat64, NeuronInfo)), full_neurons: IDL.Vec(Neuron), }); + const DateRangeFilter = IDL.Record({ + start_timestamp_seconds: IDL.Opt(IDL.Nat64), + end_timestamp_seconds: IDL.Opt(IDL.Nat64), + }); + const ListNodeProviderRewardsRequest = IDL.Record({ + date_filter: IDL.Opt(DateRangeFilter), + }); + const ListNodeProviderRewardsResponse = IDL.Record({ + rewards: IDL.Vec(MonthlyNodeProviderRewards), + }); const ListNodeProvidersResponse = IDL.Record({ node_providers: IDL.Vec(NodeProvider), }); const ListProposalInfo = IDL.Record({ include_reward_status: IDL.Vec(IDL.Int32), omit_large_fields: IDL.Opt(IDL.Bool), - before_proposal: IDL.Opt(NeuronId), + before_proposal: IDL.Opt(ProposalId), limit: IDL.Nat32, exclude_topic: IDL.Vec(IDL.Int32), include_all_manage_neuron_proposals: IDL.Opt(IDL.Bool), @@ -664,7 +762,58 @@ export const idlFactory = ({ IDL }) => { const ListProposalInfoResponse = IDL.Record({ proposal_info: IDL.Vec(ProposalInfo), }); + const InstallCodeRequest = IDL.Record({ + arg: IDL.Opt(IDL.Vec(IDL.Nat8)), + wasm_module: IDL.Opt(IDL.Vec(IDL.Nat8)), + skip_stopping_before_installing: IDL.Opt(IDL.Bool), + canister_id: IDL.Opt(IDL.Principal), + install_mode: IDL.Opt(IDL.Int32), + }); + const ProposalActionRequest = IDL.Variant({ + RegisterKnownNeuron: KnownNeuron, + ManageNeuron: ManageNeuronRequest, + UpdateCanisterSettings: UpdateCanisterSettings, + InstallCode: InstallCodeRequest, + StopOrStartCanister: StopOrStartCanister, + CreateServiceNervousSystem: CreateServiceNervousSystem, + ExecuteNnsFunction: ExecuteNnsFunction, + RewardNodeProvider: RewardNodeProvider, + RewardNodeProviders: RewardNodeProviders, + ManageNetworkEconomics: NetworkEconomics, + ApproveGenesisKyc: Principals, + AddOrRemoveNodeProvider: AddOrRemoveNodeProvider, + Motion: Motion, + }); + const MakeProposalRequest = IDL.Record({ + url: IDL.Text, + title: IDL.Opt(IDL.Text), + action: IDL.Opt(ProposalActionRequest), + summary: IDL.Text, + }); + const ManageNeuronCommandRequest = IDL.Variant({ + Spawn: Spawn, + Split: Split, + Follow: Follow, + RefreshVotingPower: RefreshVotingPower, + ClaimOrRefresh: ClaimOrRefresh, + Configure: Configure, + RegisterVote: RegisterVote, + Merge: Merge, + DisburseToNeuron: DisburseToNeuron, + MakeProposal: MakeProposalRequest, + StakeMaturity: StakeMaturity, + MergeMaturity: MergeMaturity, + Disburse: Disburse, + }); + ManageNeuronRequest.fill( + IDL.Record({ + id: IDL.Opt(NeuronId), + command: IDL.Opt(ManageNeuronCommandRequest), + neuron_id_or_subaccount: IDL.Opt(NeuronIdOrSubaccount), + }), + ); const SpawnResponse = IDL.Record({ created_neuron_id: IDL.Opt(NeuronId) }); + const RefreshVotingPowerResponse = IDL.Record({}); const ClaimOrRefreshResponse = IDL.Record({ refreshed_neuron_id: IDL.Opt(NeuronId), }); @@ -676,7 +825,7 @@ export const idlFactory = ({ IDL }) => { }); const MakeProposalResponse = IDL.Record({ message: IDL.Opt(IDL.Text), - proposal_id: IDL.Opt(NeuronId), + proposal_id: IDL.Opt(ProposalId), }); const StakeMaturityResponse = IDL.Record({ maturity_e8s: IDL.Nat64, @@ -692,6 +841,7 @@ export const idlFactory = ({ IDL }) => { Spawn: SpawnResponse, Split: SpawnResponse, Follow: IDL.Record({}), + RefreshVotingPower: RefreshVotingPowerResponse, ClaimOrRefresh: ClaimOrRefreshResponse, Configure: IDL.Record({}), RegisterVote: IDL.Record({}), @@ -730,7 +880,8 @@ export const idlFactory = ({ IDL }) => { nns_proposal_id: IDL.Opt(IDL.Nat64), }); const NeuronsFundNeuron = IDL.Record({ - hotkey_principal: IDL.Opt(IDL.Text), + controller: IDL.Opt(IDL.Principal), + hotkeys: IDL.Opt(Principals), is_capped: IDL.Opt(IDL.Bool), nns_neuron_id: IDL.Opt(IDL.Nat64), amount_icp_e8s: IDL.Opt(IDL.Nat64), @@ -768,7 +919,7 @@ export const idlFactory = ({ IDL }) => { get_monthly_node_provider_rewards: IDL.Func([], [Result_4], []), get_most_recent_monthly_node_provider_rewards: IDL.Func( [], - [IDL.Opt(MostRecentMonthlyNodeProviderRewards)], + [IDL.Opt(MonthlyNodeProviderRewards)], ['query'], ), get_network_economics_parameters: IDL.Func( @@ -795,15 +946,21 @@ export const idlFactory = ({ IDL }) => { [IDL.Opt(ProposalInfo)], ['query'], ), + get_restore_aging_summary: IDL.Func([], [RestoreAgingSummary], ['query']), list_known_neurons: IDL.Func([], [ListKnownNeuronsResponse], ['query']), list_neurons: IDL.Func([ListNeurons], [ListNeuronsResponse], ['query']), + list_node_provider_rewards: IDL.Func( + [ListNodeProviderRewardsRequest], + [ListNodeProviderRewardsResponse], + ['query'], + ), list_node_providers: IDL.Func([], [ListNodeProvidersResponse], ['query']), list_proposals: IDL.Func( [ListProposalInfo], [ListProposalInfoResponse], ['query'], ), - manage_neuron: IDL.Func([ManageNeuron], [ManageNeuronResponse], []), + manage_neuron: IDL.Func([ManageNeuronRequest], [ManageNeuronResponse], []), settle_community_fund_participation: IDL.Func( [SettleCommunityFundParticipation], [Result], @@ -815,7 +972,7 @@ export const idlFactory = ({ IDL }) => { [], ), simulate_manage_neuron: IDL.Func( - [ManageNeuron], + [ManageNeuronRequest], [ManageNeuronResponse], [], ), @@ -845,6 +1002,7 @@ export const init = ({ IDL }) => { topic: IDL.Int32, followees: IDL.Vec(NeuronId), }); + const RefreshVotingPower = IDL.Record({}); const ClaimOrRefreshNeuronFromAccount = IDL.Record({ controller: IDL.Opt(IDL.Principal), memo: IDL.Nat64, @@ -865,6 +1023,7 @@ export const init = ({ IDL }) => { const IncreaseDissolveDelay = IDL.Record({ additional_dissolve_delay_seconds: IDL.Nat32, }); + const SetVisibility = IDL.Record({ visibility: IDL.Opt(IDL.Int32) }); const SetDissolveTimestamp = IDL.Record({ dissolve_timestamp_seconds: IDL.Nat64, }); @@ -875,14 +1034,16 @@ export const init = ({ IDL }) => { StopDissolving: IDL.Record({}), StartDissolving: IDL.Record({}), IncreaseDissolveDelay: IncreaseDissolveDelay, + SetVisibility: SetVisibility, JoinCommunityFund: IDL.Record({}), LeaveCommunityFund: IDL.Record({}), SetDissolveTimestamp: SetDissolveTimestamp, }); const Configure = IDL.Record({ operation: IDL.Opt(Operation) }); + const ProposalId = IDL.Record({ id: IDL.Nat64 }); const RegisterVote = IDL.Record({ vote: IDL.Int32, - proposal: IDL.Opt(NeuronId), + proposal: IDL.Opt(ProposalId), }); const Merge = IDL.Record({ source_neuron_id: IDL.Opt(NeuronId) }); const DisburseToNeuron = IDL.Record({ @@ -906,6 +1067,7 @@ export const init = ({ IDL }) => { Spawn: Spawn, Split: Split, Follow: Follow, + RefreshVotingPower: RefreshVotingPower, ClaimOrRefresh: ClaimOrRefresh, Configure: Configure, RegisterVote: RegisterVote, @@ -925,6 +1087,31 @@ export const init = ({ IDL }) => { command: IDL.Opt(Command), neuron_id_or_subaccount: IDL.Opt(NeuronIdOrSubaccount), }); + const Controllers = IDL.Record({ controllers: IDL.Vec(IDL.Principal) }); + const CanisterSettings = IDL.Record({ + freezing_threshold: IDL.Opt(IDL.Nat64), + wasm_memory_threshold: IDL.Opt(IDL.Nat64), + controllers: IDL.Opt(Controllers), + log_visibility: IDL.Opt(IDL.Int32), + wasm_memory_limit: IDL.Opt(IDL.Nat64), + memory_allocation: IDL.Opt(IDL.Nat64), + compute_allocation: IDL.Opt(IDL.Nat64), + }); + const UpdateCanisterSettings = IDL.Record({ + canister_id: IDL.Opt(IDL.Principal), + settings: IDL.Opt(CanisterSettings), + }); + const InstallCode = IDL.Record({ + skip_stopping_before_installing: IDL.Opt(IDL.Bool), + wasm_module_hash: IDL.Opt(IDL.Vec(IDL.Nat8)), + canister_id: IDL.Opt(IDL.Principal), + arg_hash: IDL.Opt(IDL.Vec(IDL.Nat8)), + install_mode: IDL.Opt(IDL.Int32), + }); + const StopOrStartCanister = IDL.Record({ + action: IDL.Opt(IDL.Int32), + canister_id: IDL.Opt(IDL.Principal), + }); const Percentage = IDL.Record({ basis_points: IDL.Opt(IDL.Nat64) }); const Duration = IDL.Record({ seconds: IDL.Opt(IDL.Nat64) }); const Tokens = IDL.Record({ e8s: IDL.Opt(IDL.Nat64) }); @@ -1070,8 +1257,27 @@ export const init = ({ IDL }) => { use_registry_derived_rewards: IDL.Opt(IDL.Bool), rewards: IDL.Vec(RewardNodeProvider), }); + const VotingPowerEconomics = IDL.Record({ + start_reducing_voting_power_after_seconds: IDL.Opt(IDL.Nat64), + clear_following_after_seconds: IDL.Opt(IDL.Nat64), + }); + const Decimal = IDL.Record({ human_readable: IDL.Opt(IDL.Text) }); + const NeuronsFundMatchedFundingCurveCoefficients = IDL.Record({ + contribution_threshold_xdr: IDL.Opt(Decimal), + one_third_participation_milestone_xdr: IDL.Opt(Decimal), + full_participation_milestone_xdr: IDL.Opt(Decimal), + }); + const NeuronsFundEconomics = IDL.Record({ + maximum_icp_xdr_rate: IDL.Opt(Percentage), + neurons_fund_matched_funding_curve_coefficients: IDL.Opt( + NeuronsFundMatchedFundingCurveCoefficients, + ), + max_theoretical_neurons_fund_participation_amount_xdr: IDL.Opt(Decimal), + minimum_icp_xdr_rate: IDL.Opt(Percentage), + }); const NetworkEconomics = IDL.Record({ neuron_minimum_stake_e8s: IDL.Nat64, + voting_power_economics: IDL.Opt(VotingPowerEconomics), max_proposals_to_keep_per_topic: IDL.Nat32, neuron_management_fee_per_proposal_e8s: IDL.Nat64, reject_cost_e8s: IDL.Nat64, @@ -1079,10 +1285,9 @@ export const init = ({ IDL }) => { neuron_spawn_dissolve_delay_seconds: IDL.Nat64, minimum_icp_xdr_rate: IDL.Nat64, maximum_node_provider_rewards_e8s: IDL.Nat64, + neurons_fund_economics: IDL.Opt(NeuronsFundEconomics), }); - const ApproveGenesisKyc = IDL.Record({ - principals: IDL.Vec(IDL.Principal), - }); + const Principals = IDL.Record({ principals: IDL.Vec(IDL.Principal) }); const Change = IDL.Variant({ ToRemove: NodeProvider, ToAdd: NodeProvider, @@ -1092,6 +1297,9 @@ export const init = ({ IDL }) => { const Action = IDL.Variant({ RegisterKnownNeuron: KnownNeuron, ManageNeuron: ManageNeuron, + UpdateCanisterSettings: UpdateCanisterSettings, + InstallCode: InstallCode, + StopOrStartCanister: StopOrStartCanister, CreateServiceNervousSystem: CreateServiceNervousSystem, ExecuteNnsFunction: ExecuteNnsFunction, RewardNodeProvider: RewardNodeProvider, @@ -1100,7 +1308,7 @@ export const init = ({ IDL }) => { SetDefaultFollowees: SetDefaultFollowees, RewardNodeProviders: RewardNodeProviders, ManageNetworkEconomics: NetworkEconomics, - ApproveGenesisKyc: ApproveGenesisKyc, + ApproveGenesisKyc: Principals, AddOrRemoveNodeProvider: AddOrRemoveNodeProvider, Motion: Motion, }); @@ -1117,21 +1325,36 @@ export const init = ({ IDL }) => { caller: IDL.Opt(IDL.Principal), proposer_id: IDL.Opt(NeuronId), }); - const MostRecentMonthlyNodeProviderRewards = IDL.Record({ + const XdrConversionRate = IDL.Record({ + xdr_permyriad_per_icp: IDL.Opt(IDL.Nat64), + timestamp_seconds: IDL.Opt(IDL.Nat64), + }); + const MonthlyNodeProviderRewards = IDL.Record({ + minimum_xdr_permyriad_per_icp: IDL.Opt(IDL.Nat64), + registry_version: IDL.Opt(IDL.Nat64), + node_providers: IDL.Vec(NodeProvider), timestamp: IDL.Nat64, rewards: IDL.Vec(RewardNodeProvider), - }); - const GenesisNeuronAccount = IDL.Record({ - id: IDL.Nat64, - error_count: IDL.Nat64, - neuron_type: IDL.Int32, - account_ids: IDL.Vec(IDL.Text), - tag_end_timestamp_seconds: IDL.Opt(IDL.Nat64), - amount_icp_e8s: IDL.Nat64, - tag_start_timestamp_seconds: IDL.Opt(IDL.Nat64), - }); - const GenesisNeuronAccounts = IDL.Record({ - genesis_neuron_accounts: IDL.Vec(GenesisNeuronAccount), + xdr_conversion_rate: IDL.Opt(XdrConversionRate), + maximum_node_provider_rewards_e8s: IDL.Opt(IDL.Nat64), + }); + const NeuronSubsetMetrics = IDL.Record({ + total_maturity_e8s_equivalent: IDL.Opt(IDL.Nat64), + maturity_e8s_equivalent_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + voting_power_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + total_staked_e8s: IDL.Opt(IDL.Nat64), + count: IDL.Opt(IDL.Nat64), + deciding_voting_power_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + total_staked_maturity_e8s_equivalent: IDL.Opt(IDL.Nat64), + total_potential_voting_power: IDL.Opt(IDL.Nat64), + total_deciding_voting_power: IDL.Opt(IDL.Nat64), + staked_maturity_e8s_equivalent_buckets: IDL.Vec( + IDL.Tuple(IDL.Nat64, IDL.Nat64), + ), + staked_e8s_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + total_voting_power: IDL.Opt(IDL.Nat64), + potential_voting_power_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), + count_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Nat64)), }); const GovernanceCachedMetrics = IDL.Record({ total_maturity_e8s_equivalent: IDL.Nat64, @@ -1155,16 +1378,20 @@ export const init = ({ IDL }) => { total_staked_e8s_seed: IDL.Nat64, total_staked_maturity_e8s_equivalent_ect: IDL.Nat64, total_staked_e8s: IDL.Nat64, + fully_lost_voting_power_neuron_subset_metrics: IDL.Opt(NeuronSubsetMetrics), not_dissolving_neurons_count: IDL.Nat64, total_locked_e8s: IDL.Nat64, neurons_fund_total_active_neurons: IDL.Nat64, + total_voting_power_non_self_authenticating_controller: IDL.Opt(IDL.Nat64), total_staked_maturity_e8s_equivalent: IDL.Nat64, not_dissolving_neurons_e8s_buckets_ect: IDL.Vec( IDL.Tuple(IDL.Nat64, IDL.Float64), ), + declining_voting_power_neuron_subset_metrics: IDL.Opt(NeuronSubsetMetrics), total_staked_e8s_ect: IDL.Nat64, not_dissolving_neurons_staked_maturity_e8s_equivalent_sum: IDL.Nat64, dissolved_neurons_e8s: IDL.Nat64, + total_staked_e8s_non_self_authenticating_controller: IDL.Opt(IDL.Nat64), dissolving_neurons_e8s_buckets_seed: IDL.Vec( IDL.Tuple(IDL.Nat64, IDL.Float64), ), @@ -1176,6 +1403,8 @@ export const init = ({ IDL }) => { dissolving_neurons_e8s_buckets_ect: IDL.Vec( IDL.Tuple(IDL.Nat64, IDL.Float64), ), + non_self_authenticating_controller_neuron_subset_metrics: + IDL.Opt(NeuronSubsetMetrics), dissolving_neurons_count: IDL.Nat64, dissolving_neurons_e8s_buckets: IDL.Vec(IDL.Tuple(IDL.Nat64, IDL.Float64)), total_staked_maturity_e8s_equivalent_seed: IDL.Nat64, @@ -1183,9 +1412,20 @@ export const init = ({ IDL }) => { not_dissolving_neurons_e8s_buckets_seed: IDL.Vec( IDL.Tuple(IDL.Nat64, IDL.Float64), ), + public_neuron_subset_metrics: IDL.Opt(NeuronSubsetMetrics), timestamp_seconds: IDL.Nat64, seed_neuron_count: IDL.Nat64, }); + const RestoreAgingNeuronGroup = IDL.Record({ + count: IDL.Opt(IDL.Nat64), + previous_total_stake_e8s: IDL.Opt(IDL.Nat64), + current_total_stake_e8s: IDL.Opt(IDL.Nat64), + group_type: IDL.Int32, + }); + const RestoreAgingSummary = IDL.Record({ + groups: IDL.Vec(RestoreAgingNeuronGroup), + timestamp_seconds: IDL.Opt(IDL.Nat64), + }); const RewardEvent = IDL.Record({ rounds_since_last_distribution: IDL.Opt(IDL.Nat64), day_after_genesis: IDL.Nat64, @@ -1193,7 +1433,7 @@ export const init = ({ IDL }) => { total_available_e8s_equivalent: IDL.Nat64, latest_round_available_e8s_equivalent: IDL.Opt(IDL.Nat64), distributed_e8s_equivalent: IDL.Nat64, - settled_proposals: IDL.Vec(NeuronId), + settled_proposals: IDL.Vec(ProposalId), }); const NeuronStakeTransfer = IDL.Record({ to_subaccount: IDL.Vec(IDL.Nat8), @@ -1222,15 +1462,6 @@ export const init = ({ IDL }) => { error_message: IDL.Text, error_type: IDL.Int32, }); - const CfNeuron = IDL.Record({ - has_created_neuron_recipes: IDL.Opt(IDL.Bool), - nns_neuron_id: IDL.Nat64, - amount_icp_e8s: IDL.Nat64, - }); - const CfParticipant = IDL.Record({ - hotkey_principal: IDL.Text, - cf_neurons: IDL.Vec(CfNeuron), - }); const Ballot = IDL.Record({ vote: IDL.Int32, voting_power: IDL.Nat64 }); const SwapParticipationLimits = IDL.Record({ min_participant_icp_e8s: IDL.Opt(IDL.Nat64), @@ -1239,7 +1470,8 @@ export const init = ({ IDL }) => { max_direct_participation_icp_e8s: IDL.Opt(IDL.Nat64), }); const NeuronsFundNeuronPortion = IDL.Record({ - hotkey_principal: IDL.Opt(IDL.Principal), + controller: IDL.Opt(IDL.Principal), + hotkeys: IDL.Vec(IDL.Principal), is_capped: IDL.Opt(IDL.Bool), maturity_equivalent_icp_e8s: IDL.Opt(IDL.Nat64), nns_neuron_id: IDL.Opt(NeuronId), @@ -1304,9 +1536,8 @@ export const init = ({ IDL }) => { current_deadline_timestamp_seconds: IDL.Nat64, }); const ProposalData = IDL.Record({ - id: IDL.Opt(NeuronId), + id: IDL.Opt(ProposalId), failure_reason: IDL.Opt(GovernanceError), - cf_participants: IDL.Vec(CfParticipant), ballots: IDL.Vec(IDL.Tuple(IDL.Nat64, Ballot)), proposal_timestamp_seconds: IDL.Nat64, reward_event_round: IDL.Nat64, @@ -1315,6 +1546,7 @@ export const init = ({ IDL }) => { reject_cost_e8s: IDL.Nat64, derived_proposal_information: IDL.Opt(DerivedProposalInformation), latest_tally: IDL.Opt(Tally), + total_potential_voting_power: IDL.Opt(IDL.Nat64), sns_token_swap_lifecycle: IDL.Opt(IDL.Int32), decided_timestamp_seconds: IDL.Nat64, proposal: IDL.Opt(Proposal), @@ -1340,7 +1572,7 @@ export const init = ({ IDL }) => { }); const BallotInfo = IDL.Record({ vote: IDL.Int32, - proposal_id: IDL.Opt(NeuronId), + proposal_id: IDL.Opt(ProposalId), }); const DissolveState = IDL.Variant({ DissolveDelaySeconds: IDL.Nat64, @@ -1351,10 +1583,13 @@ export const init = ({ IDL }) => { staked_maturity_e8s_equivalent: IDL.Opt(IDL.Nat64), controller: IDL.Opt(IDL.Principal), recent_ballots: IDL.Vec(BallotInfo), + voting_power_refreshed_timestamp_seconds: IDL.Opt(IDL.Nat64), kyc_verified: IDL.Bool, + potential_voting_power: IDL.Opt(IDL.Nat64), neuron_type: IDL.Opt(IDL.Int32), not_for_profit: IDL.Bool, maturity_e8s_equivalent: IDL.Nat64, + deciding_voting_power: IDL.Opt(IDL.Nat64), cached_neuron_stake_e8s: IDL.Nat64, created_timestamp_seconds: IDL.Nat64, auto_stake_maturity: IDL.Opt(IDL.Bool), @@ -1365,6 +1600,7 @@ export const init = ({ IDL }) => { dissolve_state: IDL.Opt(DissolveState), followees: IDL.Vec(IDL.Tuple(IDL.Int32, Followees)), neuron_fees_e8s: IDL.Nat64, + visibility: IDL.Opt(IDL.Int32), transfer: IDL.Opt(NeuronStakeTransfer), known_neuron_data: IDL.Opt(KnownNeuronData), spawn_at_timestamp_seconds: IDL.Opt(IDL.Nat64), @@ -1373,16 +1609,16 @@ export const init = ({ IDL }) => { default_followees: IDL.Vec(IDL.Tuple(IDL.Int32, Followees)), making_sns_proposal: IDL.Opt(MakingSnsProposal), most_recent_monthly_node_provider_rewards: IDL.Opt( - MostRecentMonthlyNodeProviderRewards, + MonthlyNodeProviderRewards, ), maturity_modulation_last_updated_at_timestamp_seconds: IDL.Opt(IDL.Nat64), - genesis_neuron_accounts: IDL.Opt(GenesisNeuronAccounts), wait_for_quiet_threshold_seconds: IDL.Nat64, metrics: IDL.Opt(GovernanceCachedMetrics), neuron_management_voting_period_seconds: IDL.Opt(IDL.Nat64), node_providers: IDL.Vec(NodeProvider), cached_daily_maturity_modulation_basis_points: IDL.Opt(IDL.Int32), economics: IDL.Opt(NetworkEconomics), + restore_aging_summary: IDL.Opt(RestoreAgingSummary), spawning_neurons: IDL.Opt(IDL.Bool), latest_reward_event: IDL.Opt(RewardEvent), to_claim_transfers: IDL.Vec(NeuronStakeTransfer), @@ -1390,6 +1626,7 @@ export const init = ({ IDL }) => { topic_followee_index: IDL.Vec(IDL.Tuple(IDL.Int32, FollowersMap)), migrations: IDL.Opt(Migrations), proposals: IDL.Vec(IDL.Tuple(IDL.Nat64, ProposalData)), + xdr_conversion_rate: IDL.Opt(XdrConversionRate), in_flight_commands: IDL.Vec(IDL.Tuple(IDL.Nat64, NeuronInFlightCommand)), neurons: IDL.Vec(IDL.Tuple(IDL.Nat64, Neuron)), genesis_timestamp_seconds: IDL.Nat64, diff --git a/lib/nns-utils/src/canisters/ledger.d.ts b/lib/nns-utils/src/canisters/ledger.d.ts index e094ed04..923aa3f5 100644 --- a/lib/nns-utils/src/canisters/ledger.d.ts +++ b/lib/nns-utils/src/canisters/ledger.d.ts @@ -257,7 +257,6 @@ export type TransferFromResult = | { Err: TransferFromError }; export type TransferResult = { Ok: BlockIndex } | { Err: TransferError }; export interface UpgradeArgs { - maximum_number_of_accounts: [] | [bigint]; icrc1_minting_account: [] | [Account]; feature_flags: [] | [FeatureFlags]; } @@ -266,12 +265,61 @@ export type Value = | { Nat: bigint } | { Blob: Uint8Array | number[] } | { Text: string }; +export interface icrc21_consent_info { + metadata: icrc21_consent_message_metadata; + consent_message: icrc21_consent_message; +} +export type icrc21_consent_message = + | { + LineDisplayMessage: { pages: Array<{ lines: Array }> }; + } + | { GenericDisplayMessage: string }; +export interface icrc21_consent_message_metadata { + utc_offset_minutes: [] | [number]; + language: string; +} +export interface icrc21_consent_message_request { + arg: Uint8Array | number[]; + method: string; + user_preferences: icrc21_consent_message_spec; +} +export type icrc21_consent_message_response = + | { Ok: icrc21_consent_info } + | { Err: icrc21_error }; +export interface icrc21_consent_message_spec { + metadata: icrc21_consent_message_metadata; + device_spec: + | [] + | [ + | { GenericDisplay: null } + | { + LineDisplay: { + characters_per_line: number; + lines_per_page: number; + }; + }, + ]; +} +export type icrc21_error = + | { + GenericError: { description: string; error_code: bigint }; + } + | { InsufficientPayment: icrc21_error_info } + | { UnsupportedCanisterCall: icrc21_error_info } + | { ConsentMessageUnavailable: icrc21_error_info }; +export interface icrc21_error_info { + description: string; +} export interface _SERVICE { account_balance: ActorMethod<[AccountBalanceArgs], Tokens>; account_balance_dfx: ActorMethod<[AccountBalanceArgsDfx], Tokens>; account_identifier: ActorMethod<[Account], AccountIdentifier>; archives: ActorMethod<[], Archives>; decimals: ActorMethod<[], { decimals: number }>; + icrc10_supported_standards: ActorMethod< + [], + Array<{ url: string; name: string }> + >; icrc1_balance_of: ActorMethod<[Account], Icrc1Tokens>; icrc1_decimals: ActorMethod<[], number>; icrc1_fee: ActorMethod<[], Icrc1Tokens>; @@ -285,9 +333,14 @@ export interface _SERVICE { icrc1_symbol: ActorMethod<[], string>; icrc1_total_supply: ActorMethod<[], Icrc1Tokens>; icrc1_transfer: ActorMethod<[TransferArg], Icrc1TransferResult>; + icrc21_canister_call_consent_message: ActorMethod< + [icrc21_consent_message_request], + icrc21_consent_message_response + >; icrc2_allowance: ActorMethod<[AllowanceArgs], Allowance>; icrc2_approve: ActorMethod<[ApproveArgs], ApproveResult>; icrc2_transfer_from: ActorMethod<[TransferFromArgs], TransferFromResult>; + is_ledger_ready: ActorMethod<[], boolean>; name: ActorMethod<[], { name: string }>; query_blocks: ActorMethod<[GetBlocksArgs], QueryBlocksResponse>; query_encoded_blocks: ActorMethod< diff --git a/lib/nns-utils/src/canisters/ledger.js b/lib/nns-utils/src/canisters/ledger.js index 55e92527..480d6f2c 100644 --- a/lib/nns-utils/src/canisters/ledger.js +++ b/lib/nns-utils/src/canisters/ledger.js @@ -6,7 +6,6 @@ export const idlFactory = ({ IDL }) => { }); const FeatureFlags = IDL.Record({ icrc2: IDL.Bool }); const UpgradeArgs = IDL.Record({ - maximum_number_of_accounts: IDL.Opt(IDL.Nat64), icrc1_minting_account: IDL.Opt(Account), feature_flags: IDL.Opt(FeatureFlags), }); @@ -83,6 +82,51 @@ export const idlFactory = ({ IDL }) => { Ok: Icrc1BlockIndex, Err: Icrc1TransferError, }); + const icrc21_consent_message_metadata = IDL.Record({ + utc_offset_minutes: IDL.Opt(IDL.Int16), + language: IDL.Text, + }); + const icrc21_consent_message_spec = IDL.Record({ + metadata: icrc21_consent_message_metadata, + device_spec: IDL.Opt( + IDL.Variant({ + GenericDisplay: IDL.Null, + LineDisplay: IDL.Record({ + characters_per_line: IDL.Nat16, + lines_per_page: IDL.Nat16, + }), + }), + ), + }); + const icrc21_consent_message_request = IDL.Record({ + arg: IDL.Vec(IDL.Nat8), + method: IDL.Text, + user_preferences: icrc21_consent_message_spec, + }); + const icrc21_consent_message = IDL.Variant({ + LineDisplayMessage: IDL.Record({ + pages: IDL.Vec(IDL.Record({ lines: IDL.Vec(IDL.Text) })), + }), + GenericDisplayMessage: IDL.Text, + }); + const icrc21_consent_info = IDL.Record({ + metadata: icrc21_consent_message_metadata, + consent_message: icrc21_consent_message, + }); + const icrc21_error_info = IDL.Record({ description: IDL.Text }); + const icrc21_error = IDL.Variant({ + GenericError: IDL.Record({ + description: IDL.Text, + error_code: IDL.Nat, + }), + InsufficientPayment: icrc21_error_info, + UnsupportedCanisterCall: icrc21_error_info, + ConsentMessageUnavailable: icrc21_error_info, + }); + const icrc21_consent_message_response = IDL.Variant({ + Ok: icrc21_consent_info, + Err: icrc21_error, + }); const AllowanceArgs = IDL.Record({ account: Account, spender: Account, @@ -276,6 +320,11 @@ export const idlFactory = ({ IDL }) => { account_identifier: IDL.Func([Account], [AccountIdentifier], ['query']), archives: IDL.Func([], [Archives], ['query']), decimals: IDL.Func([], [IDL.Record({ decimals: IDL.Nat32 })], ['query']), + icrc10_supported_standards: IDL.Func( + [], + [IDL.Vec(IDL.Record({ url: IDL.Text, name: IDL.Text }))], + ['query'], + ), icrc1_balance_of: IDL.Func([Account], [Icrc1Tokens], ['query']), icrc1_decimals: IDL.Func([], [IDL.Nat8], ['query']), icrc1_fee: IDL.Func([], [Icrc1Tokens], ['query']), @@ -294,9 +343,15 @@ export const idlFactory = ({ IDL }) => { icrc1_symbol: IDL.Func([], [IDL.Text], ['query']), icrc1_total_supply: IDL.Func([], [Icrc1Tokens], ['query']), icrc1_transfer: IDL.Func([TransferArg], [Icrc1TransferResult], []), + icrc21_canister_call_consent_message: IDL.Func( + [icrc21_consent_message_request], + [icrc21_consent_message_response], + [], + ), icrc2_allowance: IDL.Func([AllowanceArgs], [Allowance], ['query']), icrc2_approve: IDL.Func([ApproveArgs], [ApproveResult], []), icrc2_transfer_from: IDL.Func([TransferFromArgs], [TransferFromResult], []), + is_ledger_ready: IDL.Func([], [IDL.Bool], ['query']), name: IDL.Func([], [IDL.Record({ name: IDL.Text })], ['query']), query_blocks: IDL.Func([GetBlocksArgs], [QueryBlocksResponse], ['query']), query_encoded_blocks: IDL.Func( @@ -318,7 +373,6 @@ export const init = ({ IDL }) => { }); const FeatureFlags = IDL.Record({ icrc2: IDL.Bool }); const UpgradeArgs = IDL.Record({ - maximum_number_of_accounts: IDL.Opt(IDL.Nat64), icrc1_minting_account: IDL.Opt(Account), feature_flags: IDL.Opt(FeatureFlags), }); diff --git a/lib/utils/src/date.ts b/lib/utils/src/date.ts new file mode 100644 index 00000000..301d0da6 --- /dev/null +++ b/lib/utils/src/date.ts @@ -0,0 +1,5 @@ +export function addDays(date: Date, days: number): Date { + const result = new Date(date); + result.setDate(date.getDate() + days); + return result; +} diff --git a/lib/utils/src/index.ts b/lib/utils/src/index.ts index 59bf2020..e813f2f2 100644 --- a/lib/utils/src/index.ts +++ b/lib/utils/src/index.ts @@ -1 +1,2 @@ +export * from './date'; export * from './nil'; diff --git a/package.json b/package.json index 9cd28ac9..af3db808 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "ng-packagr": "^19.0.1", "prettier": "^3.4.2", "prettier-plugin-astro": "^0.13.0", + "prettier-plugin-motoko": "^0.10.2", "turbo": "^2.3.3", "typescript": "~5.5.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a714d876..8883037c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -201,6 +201,9 @@ importers: prettier-plugin-astro: specifier: ^0.13.0 version: 0.13.0 + prettier-plugin-motoko: + specifier: ^0.10.2 + version: 0.10.2(prettier@3.4.2) turbo: specifier: ^2.3.3 version: 2.3.3 @@ -253,7 +256,7 @@ importers: version: 8.4.7(lit@3.2.1)(storybook@8.4.7(prettier@3.4.2)) '@storybook/web-components-vite': specifier: ^8.4.7 - version: 8.4.7(lit@3.2.1)(storybook@8.4.7(prettier@3.4.2))(vite@5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0)) + version: 8.4.7(lit@3.2.1)(storybook@8.4.7(prettier@3.4.2))(vite@5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0)) storybook: specifier: ^8.4.7 version: 8.4.7(prettier@3.4.2) @@ -268,6 +271,9 @@ importers: '@cg/nns-utils': specifier: workspace:* version: link:../../../lib/nns-utils + '@cg/utils': + specifier: workspace:* + version: link:../../../lib/utils '@dfinity/response-verification': specifier: ^2.4.0 version: 2.6.0 @@ -317,12 +323,15 @@ importers: '@cg/styles': specifier: workspace:* version: link:../../lib/styles + '@cg/utils': + specifier: workspace:* + version: link:../../lib/utils src/marketing: dependencies: '@astrojs/netlify': specifier: ^5.2.0 - version: 5.5.4(@types/node@22.10.3)(astro@4.16.18(@types/node@22.10.3)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4))(encoding@0.1.13)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0) + version: 5.5.4(@types/node@20.17.11)(astro@4.16.18(@types/node@20.17.11)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4))(encoding@0.1.13)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0) '@cg/styles': specifier: workspace:* version: link:../../lib/styles @@ -331,10 +340,10 @@ importers: version: link:../../lib/ui '@storyblok/astro': specifier: ^4.0.4 - version: 4.1.1(astro@4.16.18(@types/node@22.10.3)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4)) + version: 4.1.1(astro@4.16.18(@types/node@20.17.11)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4)) astro: specifier: ^4.5.16 - version: 4.16.18(@types/node@22.10.3)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4) + version: 4.16.18(@types/node@20.17.11)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4) devDependencies: '@astrojs/check': specifier: ^0.5.10 @@ -348,6 +357,9 @@ importers: '@cg/nns-utils': specifier: workspace:* version: link:../../lib/nns-utils + '@cg/utils': + specifier: workspace:* + version: link:../../lib/utils packages: @@ -5531,6 +5543,10 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@7.1.7: + resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + deprecated: Glob versions prior to v9 are no longer supported + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -7267,6 +7283,11 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + out-of-character@1.2.2: + resolution: {integrity: sha512-UpQ85sBLHdx84mnHR3jjX4xWpr06s7ptAVYYO2ya+xIvgHf+XDBtK1GOxNfXXIUXV8hHFEAXUIG1K+juchKaUQ==} + engines: {node: '>=6.0.0'} + hasBin: true + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -7770,6 +7791,11 @@ packages: resolution: {integrity: sha512-5HrJNnPmZqTUNoA97zn4gNQv9BgVhv+et03314WpQ9H9N8m2L9OSV798olwmG2YLXPl1iSstlJCR1zB3x5xG4g==} engines: {node: ^14.15.0 || >=16.0.0} + prettier-plugin-motoko@0.10.2: + resolution: {integrity: sha512-/94ns1Js51+6dYQF66kLBJvps7a147Pl7UHamxec5HovhV3eU/s0gkjLooxb9zYpjkd6e8Q2Ol5ugdrTiaz24A==} + peerDependencies: + prettier: ^2.7 || ^3.0 + prettier@2.8.7: resolution: {integrity: sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==} engines: {node: '>=10.13.0'} @@ -10064,7 +10090,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) '@inquirer/confirm': 5.0.2(@types/node@20.17.11) - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.11(@types/node@20.17.11)(less@4.2.0)(sass@1.80.7)(terser@5.37.0)) + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.11(@types/node@20.17.11)(less@4.2.0)(sass@1.80.7)(terser@5.36.0)) beasties: 0.1.0 browserslist: 4.24.3 esbuild: 0.24.0 @@ -10307,15 +10333,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/netlify@5.5.4(@types/node@22.10.3)(astro@4.16.18(@types/node@22.10.3)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4))(encoding@0.1.13)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)': + '@astrojs/netlify@5.5.4(@types/node@20.17.11)(astro@4.16.18(@types/node@20.17.11)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4))(encoding@0.1.13)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)': dependencies: '@astrojs/internal-helpers': 0.4.1 '@astrojs/underscore-redirects': 0.3.4 '@netlify/functions': 2.8.2 '@vercel/nft': 0.27.10(encoding@0.1.13)(rollup@4.29.1) - astro: 4.16.18(@types/node@22.10.3)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4) + astro: 4.16.18(@types/node@20.17.11)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4) esbuild: 0.21.5 - vite: 5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0) + vite: 5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0) transitivePeerDependencies: - '@types/node' - encoding @@ -13175,10 +13201,10 @@ snapshots: dependencies: '@stencil/core': 4.23.1 - '@storyblok/astro@4.1.1(astro@4.16.18(@types/node@22.10.3)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4))': + '@storyblok/astro@4.1.1(astro@4.16.18(@types/node@20.17.11)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4))': dependencies: '@storyblok/js': 3.2.1 - astro: 4.16.18(@types/node@22.10.3)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4) + astro: 4.16.18(@types/node@20.17.11)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4) camelcase: 8.0.0 lodash.mergewith: 4.6.2 @@ -13277,13 +13303,13 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-vite@8.4.7(storybook@8.4.7(prettier@3.4.2))(vite@5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0))': + '@storybook/builder-vite@8.4.7(storybook@8.4.7(prettier@3.4.2))(vite@5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0))': dependencies: '@storybook/csf-plugin': 8.4.7(storybook@8.4.7(prettier@3.4.2)) browser-assert: 1.2.1 storybook: 8.4.7(prettier@3.4.2) ts-dedent: 2.2.0 - vite: 5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0) + vite: 5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0) '@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2))': dependencies: @@ -13361,9 +13387,9 @@ snapshots: dependencies: storybook: 8.4.7(prettier@3.4.2) - '@storybook/web-components-vite@8.4.7(lit@3.2.1)(storybook@8.4.7(prettier@3.4.2))(vite@5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0))': + '@storybook/web-components-vite@8.4.7(lit@3.2.1)(storybook@8.4.7(prettier@3.4.2))(vite@5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0))': dependencies: - '@storybook/builder-vite': 8.4.7(storybook@8.4.7(prettier@3.4.2))(vite@5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0)) + '@storybook/builder-vite': 8.4.7(storybook@8.4.7(prettier@3.4.2))(vite@5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0)) '@storybook/web-components': 8.4.7(lit@3.2.1)(storybook@8.4.7(prettier@3.4.2)) magic-string: 0.30.17 storybook: 8.4.7(prettier@3.4.2) @@ -13875,6 +13901,10 @@ snapshots: - rollup - supports-color + '@vitejs/plugin-basic-ssl@1.1.0(vite@5.4.11(@types/node@20.17.11)(less@4.2.0)(sass@1.80.7)(terser@5.36.0))': + dependencies: + vite: 5.4.11(@types/node@20.17.11)(less@4.2.0)(sass@1.80.7)(terser@5.36.0) + '@vitejs/plugin-basic-ssl@1.1.0(vite@5.4.11(@types/node@20.17.11)(less@4.2.0)(sass@1.80.7)(terser@5.37.0))': dependencies: vite: 5.4.11(@types/node@20.17.11)(less@4.2.0)(sass@1.80.7)(terser@5.37.0) @@ -14273,7 +14303,7 @@ snapshots: astring@1.9.0: {} - astro@4.16.18(@types/node@22.10.3)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4): + astro@4.16.18(@types/node@20.17.11)(less@4.2.1)(rollup@4.29.1)(sass@1.83.0)(terser@5.37.0)(typescript@5.5.4): dependencies: '@astrojs/compiler': 2.10.3 '@astrojs/internal-helpers': 0.4.1 @@ -14329,8 +14359,8 @@ snapshots: tsconfck: 3.1.4(typescript@5.5.4) unist-util-visit: 5.0.0 vfile: 6.0.3 - vite: 5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0) - vitefu: 1.0.5(vite@5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0)) + vite: 5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0) + vitefu: 1.0.5(vite@5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0)) which-pm: 3.0.0 xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 @@ -15717,7 +15747,7 @@ snapshots: - bluebird - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -15739,7 +15769,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16282,6 +16312,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@7.1.7: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -18532,6 +18571,11 @@ snapshots: os-tmpdir@1.0.2: {} + out-of-character@1.2.2: + dependencies: + colorette: 2.0.20 + glob: 7.1.7 + own-keys@1.0.1: dependencies: get-intrinsic: 1.2.6 @@ -19042,6 +19086,11 @@ snapshots: prettier: 3.4.2 sass-formatter: 0.7.9 + prettier-plugin-motoko@0.10.2(prettier@3.4.2): + dependencies: + out-of-character: 1.2.2 + prettier: 3.4.2 + prettier@2.8.7: optional: true @@ -20349,7 +20398,7 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - terser-webpack-plugin@5.3.11(esbuild@0.24.0)(webpack@5.96.1(esbuild@0.24.0)): + terser-webpack-plugin@5.3.11(esbuild@0.24.2)(webpack@5.96.1(esbuild@0.24.0)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -20358,7 +20407,7 @@ snapshots: terser: 5.37.0 webpack: 5.96.1(esbuild@0.24.2) optionalDependencies: - esbuild: 0.24.0 + esbuild: 0.24.2 terser-webpack-plugin@5.3.11(esbuild@0.24.2)(webpack@5.97.1(esbuild@0.24.2)): dependencies: @@ -20837,21 +20886,21 @@ snapshots: sass: 1.80.7 terser: 5.37.0 - vite@5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0): + vite@5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.26.0 optionalDependencies: - '@types/node': 22.10.3 + '@types/node': 20.17.11 fsevents: 2.3.3 less: 4.2.1 sass: 1.83.0 terser: 5.37.0 - vitefu@1.0.5(vite@5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0)): + vitefu@1.0.5(vite@5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0)): optionalDependencies: - vite: 5.4.11(@types/node@22.10.3)(less@4.2.1)(sass@1.83.0)(terser@5.37.0) + vite: 5.4.11(@types/node@20.17.11)(less@4.2.1)(sass@1.83.0)(terser@5.37.0) void-elements@2.0.1: {} @@ -21148,7 +21197,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(esbuild@0.24.0)(webpack@5.96.1(esbuild@0.24.0)) + terser-webpack-plugin: 5.3.11(esbuild@0.24.2)(webpack@5.96.1(esbuild@0.24.0)) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/prettier.config.cjs b/prettier.config.cjs index f21081d0..7867b192 100644 --- a/prettier.config.cjs +++ b/prettier.config.cjs @@ -4,7 +4,7 @@ module.exports = { trailingComma: 'all', arrowParens: 'avoid', htmlWhitespaceSensitivity: 'strict', - plugins: ['prettier-plugin-astro'], + plugins: ['prettier-plugin-astro', 'prettier-plugin-motoko'], overrides: [ { files: '*.astro', diff --git a/scripts/canisters/ledger.did b/scripts/canisters/ledger.did index 9e49cb4b..37fdc3ad 100644 --- a/scripts/canisters/ledger.did +++ b/scripts/canisters/ledger.did @@ -348,7 +348,6 @@ type Value = variant { }; type UpgradeArgs = record { - maximum_number_of_accounts : opt nat64; icrc1_minting_account : opt Account; feature_flags : opt FeatureFlags; }; @@ -423,6 +422,63 @@ type TransferFromError = variant { GenericError : record { error_code : nat; message : text }; }; +type icrc21_consent_message_metadata = record { + language : text; + utc_offset_minutes : opt int16; +}; + +type icrc21_consent_message_spec = record { + metadata : icrc21_consent_message_metadata; + device_spec : opt variant { + GenericDisplay; + LineDisplay : record { + characters_per_line : nat16; + lines_per_page : nat16; + }; + }; +}; + +type icrc21_consent_message_request = record { + method : text; + arg : blob; + user_preferences : icrc21_consent_message_spec; +}; + +type icrc21_consent_message = variant { + GenericDisplayMessage : text; + LineDisplayMessage : record { + pages : vec record { + lines : vec text; + }; + }; +}; + +type icrc21_consent_info = record { + consent_message : icrc21_consent_message; + metadata : icrc21_consent_message_metadata; +}; + +type icrc21_error_info = record { + description : text; +}; + +type icrc21_error = variant { + UnsupportedCanisterCall : icrc21_error_info; + ConsentMessageUnavailable : icrc21_error_info; + InsufficientPayment : icrc21_error_info; + + // Any error not covered by the above variants. + GenericError : record { + error_code : nat; + description : text; + }; +}; + +type icrc21_consent_message_response = variant { + Ok : icrc21_consent_info; + Err : icrc21_error; +}; + service : (LedgerCanisterPayload) -> { // Transfers tokens from a subaccount of the caller to the destination address. // The source address is computed from the principal of the caller and the specified subaccount. @@ -474,4 +530,9 @@ service : (LedgerCanisterPayload) -> { icrc2_approve : (ApproveArgs) -> (ApproveResult); icrc2_allowance : (AllowanceArgs) -> (Allowance) query; icrc2_transfer_from : (TransferFromArgs) -> (TransferFromResult); + + icrc21_canister_call_consent_message : (icrc21_consent_message_request) -> (icrc21_consent_message_response); + icrc10_supported_standards : () -> (vec record { name : text; url : text }) query; + + is_ledger_ready : () -> (bool) query; }; diff --git a/scripts/list-open-ic-os-version-election-proposals.sh b/scripts/list-open-ic-os-version-election-proposals.sh new file mode 100755 index 00000000..99a4eb57 --- /dev/null +++ b/scripts/list-open-ic-os-version-election-proposals.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +IC_ARG="" +if [[ " $@ " =~ " --ic " ]]; then + IC_ARG="--ic" +fi + +echo "IC_ARGS: $IC_ARG" + +# Regularly check if new topics need to be added to the `exclude_topic` list +# https://github.com/dfinity/ic/blob/master/rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs +dfx canister call $IC_ARG \ + --candid ./scripts/canisters/governance.did \ + rrkah-fqaaa-aaaaa-aaaaq-cai \ + list_proposals \ + '( + record { + include_reward_status = vec {}; + limit = 20; + exclude_topic = vec { 0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 12; 14; 15; 16; 17; 18 }; + include_status = vec { 1; }; + } + )' diff --git a/scripts/list-open-rvm-proposals.sh b/scripts/list-open-rvm-proposals.sh deleted file mode 100755 index 05b82e4a..00000000 --- a/scripts/list-open-rvm-proposals.sh +++ /dev/null @@ -1,12 +0,0 @@ -dfx canister call \ - --candid ./scripts/canisters/governance.did \ - rrkah-fqaaa-aaaaa-aaaaq-cai \ - list_proposals \ - '( - record { - include_reward_status = vec {}; - limit = 20; - exclude_topic = vec { 0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 14; 15 }; - include_status = vec { 1; }; - } - )' diff --git a/scripts/system-setup.sh b/scripts/system-setup.sh index 7cb023c4..7a852a6a 100755 --- a/scripts/system-setup.sh +++ b/scripts/system-setup.sh @@ -1,7 +1,7 @@ #!/bin/bash QUILL_VERSION="0.4.3" -DIDC_VERSION="2024-01-30" +DIDC_VERSION="2024-07-29" # Assumes that ~/bin is already added to PATH BIN="$HOME/bin" diff --git a/scripts/update-external-canisters.sh b/scripts/update-external-canisters.sh new file mode 100755 index 00000000..0a986e90 --- /dev/null +++ b/scripts/update-external-canisters.sh @@ -0,0 +1,13 @@ +# make sure this release matches what is used by `ic-nns-governance` and `ic-nns-common` in `Cargo.toml`. +RELEASE=release-2025-01-09_03-19-base + +wget https://raw.githubusercontent.com/dfinity/ic/refs/tags/$RELEASE/rs/nns/governance/canister/governance.did -O ./scripts/canisters/governance.did +wget https://raw.githubusercontent.com/dfinity/ic/refs/tags/$RELEASE/rs/ledger_suite/icp/ledger.did -O ./scripts/canisters/ledger.did + +didc bind --target js ./scripts/canisters/governance.did > ./lib/nns-utils/src/canisters/governance.js +didc bind --target ts ./scripts/canisters/governance.did > ./lib/nns-utils/src/canisters/governance.d.ts + +didc bind --target js ./scripts/canisters/ledger.did > ./lib/nns-utils/src/canisters/ledger.js +didc bind --target ts ./scripts/canisters/ledger.did > ./lib/nns-utils/src/canisters/ledger.d.ts + +pnpm run format diff --git a/src/backend/integration/package.json b/src/backend/integration/package.json index 2fdeffe5..93c903af 100644 --- a/src/backend/integration/package.json +++ b/src/backend/integration/package.json @@ -7,6 +7,7 @@ "devDependencies": { "@cg/backend": "workspace:*", "@cg/nns-utils": "workspace:*", + "@cg/utils": "workspace:*", "@dfinity/response-verification": "^2.4.0", "date-fns-tz": "^3.1.3" } diff --git a/src/backend/integration/src/support/common.ts b/src/backend/integration/src/support/common.ts index e4c02926..5e480577 100644 --- a/src/backend/integration/src/support/common.ts +++ b/src/backend/integration/src/support/common.ts @@ -15,7 +15,7 @@ import { Identity } from '@dfinity/agent'; type BackendActorService = Actor<_SERVICE>; /** - * Creates an RVM proposal and syncs the proposals on the backend canister. + * Creates an IcOsVersionElection proposal and syncs the proposals on the backend canister. * * @returns {Promise} the backend canister id of created proposal */ @@ -29,7 +29,7 @@ export async function createProposal( }> { const neuronId = await governance.createNeuron(nnsProposerIdentity); - const rvmProposalId = await governance.createRvmProposal( + const proposalId = await governance.createIcOsVersionElectionProposal( nnsProposerIdentity, { neuronId, @@ -49,17 +49,14 @@ export async function createProposal( const proposal = proposals.find( p => - p.proposal.nervous_system.network.proposal_info.id[0]!.id === - rvmProposalId, + p.proposal.nervous_system.network.proposal_info.id[0]!.id === proposalId, ); if (!proposal) { - throw new Error( - `Could not find proposal with id ${rvmProposalId.toString()}`, - ); + throw new Error(`Could not find proposal with id ${proposalId.toString()}`); } return { - nnsProposalId: rvmProposalId, + nnsProposalId: proposalId, proposalId: proposal.id, }; } @@ -94,10 +91,10 @@ export async function completeProposal( } /** - * Creates an RVM proposal using the {@link createProposal} function + * Creates an IcOsVersionElection proposal using the {@link createProposal} function * and creates a review for that proposal. * - * Skips creating the RVM proposal if a proposal id is specified as last parameter. + * Skips creating the IcOsVersionElection proposal if a proposal id is specified as last parameter. * * @returns the backend canister id of created proposal and the backend canister id of created review */ @@ -146,7 +143,7 @@ export const VALID_IMAGE_BYTES = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); * Same as {@link createProposalReview} function, * but uploads the provided image too. * - * Skips creating the RVM proposal if a proposal id is specified as last parameter. + * Skips creating the IcOsVersionElection proposal if a proposal id is specified as last parameter. * * @returns the backend canister id of created proposal, the backend canister id of created review * and the image path. diff --git a/src/backend/integration/src/support/governance.ts b/src/backend/integration/src/support/governance.ts index 33d4d2e5..e71dfb0d 100644 --- a/src/backend/integration/src/support/governance.ts +++ b/src/backend/integration/src/support/governance.ts @@ -15,7 +15,7 @@ import { type GovernanceService = GovernanceDeclarations._SERVICE; const governanceIdlFactory = GovernanceDeclarations.idlFactory; -export interface CreateRvmProposalRequest { +export interface CreateIcOsVersionElectionProposalRequest { neuronId: bigint; title: string; summary: string; @@ -109,11 +109,11 @@ export class Governance { } } - public async createRvmProposal( + public async createIcOsVersionElectionProposal( identity: Identity, - payload: CreateRvmProposalRequest, + payload: CreateIcOsVersionElectionProposalRequest, ): Promise { - const rvmPayload = encodeUpdateElectedReplicaVersionsPayload({ + const proposalPayload = encodeUpdateElectedReplicaVersionsPayload({ release_package_urls: [ `https://download.dfinity.systems/ic/${payload.replicaVersion}/guest-os/update-img/update-img.tar.gz`, `https://download.dfinity.network/ic/${payload.replicaVersion}/guest-os/update-img/update-img.tar.gz`, @@ -138,7 +138,7 @@ export class Governance { action: optional({ ExecuteNnsFunction: { nns_function: 38, - payload: new Uint8Array(rvmPayload), + payload: new Uint8Array(proposalPayload), }, }), }, diff --git a/src/backend/integration/src/tests/proposal.spec.ts b/src/backend/integration/src/tests/proposal.spec.ts index 9c9f4b1a..326ac7cf 100644 --- a/src/backend/integration/src/tests/proposal.spec.ts +++ b/src/backend/integration/src/tests/proposal.spec.ts @@ -19,8 +19,8 @@ const FETCH_PROPOSALS_LIMIT = 50; // same as on the canister describe('Proposal', () => { let driver: TestDriver; let governance: Governance; - const rvmProposalsCount = 10; - let rvmProposalIds: bigint[] = []; + const proposalsCount = 10; + let proposalIds: bigint[] = []; const advanceTimeToSyncProposals = async (): Promise => { const advanceMillis = PROPOSAL_SYNC_INTERVAL_MS + 1; @@ -34,13 +34,13 @@ describe('Proposal', () => { await driver.advanceTime(REVIEW_PERIOD_MS + 1); }; - const createRvmProposalsBatch = async (count: number): Promise => { + const createProposalsBatch = async (count: number): Promise => { const ids = []; for (let i = 0; i < count; i++) { // Create a neuron for each proposal to avoid incurring in neuron's lack of funds. // Not an optimal solution, but increasing the staked ICPs doesn't seem to work const neuronId = await governance.createNeuron(nnsProposerIdentity); - const rvmProposalId = await governance.createRvmProposal( + const proposalId = await governance.createIcOsVersionElectionProposal( nnsProposerIdentity, { neuronId, @@ -49,7 +49,7 @@ describe('Proposal', () => { replicaVersion: 'd19fa446ab35780b2c6d8b82ea32d808cca558d5', }, ); - ids.push(rvmProposalId); + ids.push(proposalId); } return ids; }; @@ -59,11 +59,11 @@ describe('Proposal', () => { expectedCount?: number, ) => { const { proposals } = extractOkResponse(res); - expect(proposals.length).toEqual(expectedCount ?? rvmProposalsCount); - for (const rvmProposalId of rvmProposalIds) { + expect(proposals.length).toEqual(expectedCount ?? proposalsCount); + for (const proposalId of proposalIds) { expect( proposals.findIndex( - p => p.proposal.nervous_system.network.id === rvmProposalId, + p => p.proposal.nervous_system.network.id === proposalId, ), ).toBeGreaterThan(-1); } @@ -72,7 +72,7 @@ describe('Proposal', () => { beforeEach(async () => { driver = await TestDriver.createWithNnsState(); governance = new Governance(driver.pic); - rvmProposalIds = await createRvmProposalsBatch(rvmProposalsCount); + proposalIds = await createProposalsBatch(proposalsCount); }); afterEach(async () => { @@ -158,10 +158,10 @@ describe('Proposal', () => { it('should sync proposals recursively', async () => { // create more proposals than FETCH_PROPOSALS_LIMIT const totalProposals = FETCH_PROPOSALS_LIMIT + 5; - const createdIds = await createRvmProposalsBatch( - totalProposals - rvmProposalsCount, + const createdIds = await createProposalsBatch( + totalProposals - proposalsCount, ); - rvmProposalIds = rvmProposalIds.concat(createdIds); + proposalIds = proposalIds.concat(createdIds); await advanceTimeToSyncProposals(); @@ -241,7 +241,7 @@ describe('Proposal', () => { for (let i = 0; i < proposalsCount; i++) { await driver.advanceTime(60 * MS_IN_MINUTE); // 1 hour - const proposalId = await governance.createRvmProposal( + const proposalId = await governance.createIcOsVersionElectionProposal( nnsProposerIdentity, { neuronId, @@ -279,20 +279,17 @@ describe('Proposal', () => { await advanceTimeToSyncProposals(); // only these proposals will be in progress - const lastRvmProposalIds = await createProposalsEveryHour(5); - lastRvmProposalIds.reverse(); + const lastProposalIds = await createProposalsEveryHour(5); + lastProposalIds.reverse(); await advanceTimeToSyncProposals(); const res = await driver.actor.list_proposals({ state: [] }); - expectListProposalsResult( - res, - rvmProposalsCount + lastRvmProposalIds.length, - ); + expectListProposalsResult(res, proposalsCount + lastProposalIds.length); const { proposals } = extractOkResponse(res); expectProposalsSortedByTimestampDesc( proposals.filter(p => - lastRvmProposalIds.includes(p.proposal.nervous_system.network.id), + lastProposalIds.includes(p.proposal.nervous_system.network.id), ), ); }); @@ -303,8 +300,8 @@ describe('Proposal', () => { await advanceTimeToCompleteProposals(); // only these proposals will be in progress - const lastRvmProposalIds = await createProposalsEveryHour(5); - lastRvmProposalIds.reverse(); + const lastProposalIds = await createProposalsEveryHour(5); + lastProposalIds.reverse(); await advanceTimeToSyncProposals(); @@ -315,7 +312,7 @@ describe('Proposal', () => { extractOkResponse(resInProgress); expectProposalsSortedByTimestampDesc( proposalsInProgress.filter(p => - lastRvmProposalIds.includes(p.proposal.nervous_system.network.id), + lastProposalIds.includes(p.proposal.nervous_system.network.id), ), ); @@ -327,7 +324,7 @@ describe('Proposal', () => { const { proposals: proposalsCompleted } = extractOkResponse(resCompleted); expectProposalsSortedByTimestampDesc( proposalsCompleted.filter(p => - lastRvmProposalIds.includes(p.proposal.nervous_system.network.id), + lastProposalIds.includes(p.proposal.nervous_system.network.id), ), ); }); diff --git a/src/backend/integration/src/tests/user_profile.spec.ts b/src/backend/integration/src/tests/user_profile.spec.ts index eac7b2c9..5f754036 100644 --- a/src/backend/integration/src/tests/user_profile.spec.ts +++ b/src/backend/integration/src/tests/user_profile.spec.ts @@ -1,5 +1,6 @@ import { describe, beforeAll, afterAll, it, expect } from 'bun:test'; import { SocialLink } from '@cg/backend'; +import { addDays } from '@cg/utils'; import { generateRandomIdentity } from '@hadronous/pic'; import { BACKEND_WASM_PATH, @@ -13,12 +14,6 @@ import { const initialDate = new Date(); -function addDays(date: Date, days: number): Date { - const newDate = new Date(date); - newDate.setDate(date.getDate() + days); - return newDate; -} - describe('User Profile', () => { let driver: TestDriver; diff --git a/src/docs/docusaurus.config.ts b/src/docs/docusaurus.config.ts index c0c13a1c..4ba3df1a 100644 --- a/src/docs/docusaurus.config.ts +++ b/src/docs/docusaurus.config.ts @@ -4,7 +4,7 @@ import type * as Preset from '@docusaurus/preset-classic'; const config: Config = { title: 'CodeGov', - tagline: 'Catalyzing participation in ICP replica version management', + tagline: 'Catalyzing participation in ICP proposal review management', favicon: 'img/codegov-logo.png', url: 'https://n5otb-3yaaa-aaaal-qcxzq-cai.icp0.io', baseUrl: '/', diff --git a/src/frontend/package.json b/src/frontend/package.json index c51b4f43..0dc788ef 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -12,6 +12,7 @@ "dependencies": { "@cg/angular-ui": "workspace:*", "@cg/backend": "workspace:*", - "@cg/styles": "workspace:*" + "@cg/styles": "workspace:*", + "@cg/utils": "workspace:*" } } diff --git a/src/frontend/src/app/core/api/proposal/proposal-api.mapper.ts b/src/frontend/src/app/core/api/proposal/proposal-api.mapper.ts index d396d05b..6e6f0b29 100644 --- a/src/frontend/src/app/core/api/proposal/proposal-api.mapper.ts +++ b/src/frontend/src/app/core/api/proposal/proposal-api.mapper.ts @@ -3,6 +3,7 @@ import { ProposalResponse, ReviewPeriodState, } from '@cg/backend'; +import { addDays } from '@cg/utils'; import { fromCandidDate, fromCandidOpt, @@ -12,7 +13,7 @@ import { import { GetProposalResponse, ListProposalsRequest, - ProposalLinkBaseUrl, + BaseUrl, ProposalState, ProposalTopic, ProposalVotingLinkType, @@ -61,13 +62,11 @@ export function mapGetProposalResponse( proposalLinks: [ { type: ProposalVotingLinkType.NNSDApp, - link: - ProposalLinkBaseUrl.NNSDApp + res.proposal.nervous_system.network.id, + link: BaseUrl.NNSDApp + res.proposal.nervous_system.network.id, }, { type: ProposalVotingLinkType.ICLight, - link: - ProposalLinkBaseUrl.ICLight + res.proposal.nervous_system.network.id, + link: BaseUrl.ICLight + res.proposal.nervous_system.network.id, }, ], }; @@ -75,22 +74,24 @@ export function mapGetProposalResponse( function getProposalTopic(nnsProposalTopic: number): ProposalTopic { if (nnsProposalTopic === 13) { - return ProposalTopic.RVM; - } else if (nnsProposalTopic === 8) { - return ProposalTopic.SCM; - } else throw new Error('Unknown proposal topic'); + return ProposalTopic.IcOsVersionElection; + } + + if (nnsProposalTopic === 8) { + return ProposalTopic.NetworkCanisterManagement; + } + + throw new Error('Unknown proposal topic'); } function getProposalState(proposalState: ReviewPeriodState): ProposalState { if ('in_progress' in proposalState) { return ProposalState.InProgress; - } else if ('completed' in proposalState) { + } + + if ('completed' in proposalState) { return ProposalState.Completed; - } else throw new Error('Unknown proposal state'); -} + } -function addDays(date: Date, days: number): Date { - const result = new Date(date); - result.setDate(date.getDate() + days); - return result; + throw new Error('Unknown proposal state'); } diff --git a/src/frontend/src/app/core/api/proposal/proposal-api.model.ts b/src/frontend/src/app/core/api/proposal/proposal-api.model.ts index f22904ca..ebc13dac 100644 --- a/src/frontend/src/app/core/api/proposal/proposal-api.model.ts +++ b/src/frontend/src/app/core/api/proposal/proposal-api.model.ts @@ -26,8 +26,8 @@ export interface GetProposalResponse { } export enum ProposalTopic { - SCM = 'SystemCanisterManagement', - RVM = 'ReplicaVersionManagement', + NetworkCanisterManagement = 'NetworkCanisterManagement', + IcOsVersionElection = 'IcOsVersionElection', } export interface ProposalVotingLink { @@ -40,11 +40,9 @@ export enum ProposalVotingLinkType { ICLight = 'ICLight', } -export enum ProposalLinkBaseUrl { +export enum BaseUrl { NNSDApp = 'https://nns.ic0.app/proposal/?u=qoctq-giaaa-aaaaa-aaaea-cai&proposal=', Proposal = 'https://dashboard.internetcomputer.org/proposal/', Neuron = 'https://dashboard.internetcomputer.org/neuron/', ICLight = 'https://iclight.io/nns/proposals/', } - -export type ProposalCodeGovVote = 'ADOPT' | 'REJECT' | 'NO VOTE'; diff --git a/src/frontend/src/app/core/state/proposal/proposal.service.mock.ts b/src/frontend/src/app/core/state/proposal/proposal.service.mock.ts index 6cabf076..d6029769 100644 --- a/src/frontend/src/app/core/state/proposal/proposal.service.mock.ts +++ b/src/frontend/src/app/core/state/proposal/proposal.service.mock.ts @@ -4,7 +4,7 @@ export type ProposalServiceMock = jasmine.SpyObj; export function proposalServiceMockFactory(): ProposalServiceMock { return jasmine.createSpyObj('ProposalService', [ - 'loadProposalList', + 'loadProposals', 'setCurrentProposalId', ]); } diff --git a/src/frontend/src/app/core/state/proposal/proposal.service.ts b/src/frontend/src/app/core/state/proposal/proposal.service.ts index f3fad9f6..79ae0294 100644 --- a/src/frontend/src/app/core/state/proposal/proposal.service.ts +++ b/src/frontend/src/app/core/state/proposal/proposal.service.ts @@ -8,21 +8,17 @@ import { ProposalState } from '~core/api'; providedIn: 'root', }) export class ProposalService { - private readonly proposalApiService = inject(ProposalApiService); + readonly #proposalApiService = inject(ProposalApiService); - private readonly currentProposalListSubject = new BehaviorSubject< - GetProposalResponse[] - >([]); - public readonly currentProposalList$ = - this.currentProposalListSubject.asObservable(); + readonly #currentProposals = new BehaviorSubject([]); + public readonly currentProposals$ = this.#currentProposals.asObservable(); - private currentProposalIdSubject = new BehaviorSubject(null); - public readonly currentProposalId$ = - this.currentProposalIdSubject.asObservable(); + readonly #currentProposalId = new BehaviorSubject(null); + public readonly currentProposalId$ = this.#currentProposalId.asObservable(); public readonly currentProposal$ = this.currentProposalId$.pipe( switchMap(proposalId => - this.currentProposalList$.pipe( + this.currentProposals$.pipe( map( proposals => proposals.find(proposal => proposal.id === proposalId) ?? null, @@ -31,7 +27,7 @@ export class ProposalService { ), ); - public async loadProposalList(state: ProposalState): Promise { + public async loadProposals(state: ProposalState): Promise { switch (state) { case ProposalState.InProgress: return await this.loadOpenProposals(); @@ -43,24 +39,24 @@ export class ProposalService { } public async setCurrentProposalId(proposalId: string): Promise { - this.currentProposalIdSubject.next(proposalId); + this.#currentProposalId.next(proposalId); } private async loadOpenProposals(): Promise { - const getResponse = await this.proposalApiService.listOpenProposals(); + const getResponse = await this.#proposalApiService.listOpenProposals(); - this.currentProposalListSubject.next(getResponse); + this.#currentProposals.next(getResponse); } private async loadClosedProposals(): Promise { - const getResponse = await this.proposalApiService.listClosedProposals(); + const getResponse = await this.#proposalApiService.listClosedProposals(); - this.currentProposalListSubject.next(getResponse); + this.#currentProposals.next(getResponse); } private async loadAllProposals(): Promise { - const getResponse = await this.proposalApiService.listAllProposals(); + const getResponse = await this.#proposalApiService.listAllProposals(); - this.currentProposalListSubject.next(getResponse); + this.#currentProposals.next(getResponse); } } diff --git a/src/frontend/src/app/core/state/review/review.service.ts b/src/frontend/src/app/core/state/review/review.service.ts index d71ff8a4..61a6ae70 100644 --- a/src/frontend/src/app/core/state/review/review.service.ts +++ b/src/frontend/src/app/core/state/review/review.service.ts @@ -11,56 +11,54 @@ import { providedIn: 'root', }) export class ReviewService { - private readonly reviewApiService = inject(ReviewApiService); + readonly #reviewApiService = inject(ReviewApiService); - private readonly proposalReviewListSubject = new BehaviorSubject< - GetProposalReviewResponse[] - >([]); - public readonly reviews$ = this.proposalReviewListSubject.asObservable(); + readonly #reviews = new BehaviorSubject([]); + public readonly reviews$ = this.#reviews.asObservable(); - private readonly currentReviewSubject = + readonly #currentReview = new BehaviorSubject(null); - public readonly currentReview$ = this.currentReviewSubject.asObservable(); + public readonly currentReview$ = this.#currentReview.asObservable(); - private readonly userReviewsSubject = new BehaviorSubject< - GetProposalReviewResponse[] - >([]); - public readonly currentUserReviews$ = this.userReviewsSubject.asObservable(); + readonly #userReviews = new BehaviorSubject([]); + public readonly currentUserReviews$ = this.#userReviews.asObservable(); - private readonly currentUserReviewSummarySubject = + readonly #currentUserReviewSummary = new BehaviorSubject(null); public readonly currentUserReviewSummary$ = - this.currentUserReviewSummarySubject.asObservable(); + this.#currentUserReviewSummary.asObservable(); public async loadReviewsByProposalId(proposalId: string): Promise { - const getResponse = await this.reviewApiService.listProposalReviews({ + const getResponse = await this.#reviewApiService.listProposalReviews({ proposalId, }); - this.proposalReviewListSubject.next(getResponse); + this.#reviews.next(getResponse); } public async loadReviewsByReviewerId(userId: string): Promise { - const getResponse = await this.reviewApiService.listProposalReviews({ + const getResponse = await this.#reviewApiService.listProposalReviews({ userId, }); - this.userReviewsSubject.next(getResponse); + this.#userReviews.next(getResponse); } public async loadReview(proposalReviewId: string): Promise { - const getResponse = await this.reviewApiService.getProposalReview({ + const getResponse = await this.#reviewApiService.getProposalReview({ proposalReviewId, }); - this.currentReviewSubject.next(getResponse); + this.#currentReview.next(getResponse); } public async loadReviewSummary(proposalId: string): Promise { - const getResponse = await this.reviewApiService.getMyProposalReviewSummary({ - proposalId, - }); + const getResponse = await this.#reviewApiService.getMyProposalReviewSummary( + { + proposalId, + }, + ); - this.currentUserReviewSummarySubject.next(getResponse); + this.#currentUserReviewSummary.next(getResponse); } } diff --git a/src/frontend/src/app/pages/proposal-details/proposal-details.component.spec.ts b/src/frontend/src/app/pages/proposal-details/proposal-details.component.spec.ts index dee3431c..21b30231 100644 --- a/src/frontend/src/app/pages/proposal-details/proposal-details.component.spec.ts +++ b/src/frontend/src/app/pages/proposal-details/proposal-details.component.spec.ts @@ -10,7 +10,7 @@ import { ProposalTopic, ProposalState, ProposalVotingLinkType, - ProposalLinkBaseUrl, + BaseUrl, } from '~core/api'; import { ProfileService, ReviewService, ProposalService } from '~core/state'; import { @@ -49,7 +49,7 @@ describe('ProposalDetailsComponent', () => { id: '1', nsProposalId: 1n, title: 'title', - topic: ProposalTopic.RVM, + topic: ProposalTopic.IcOsVersionElection, type: 'unknown', state: ProposalState.InProgress, reviewPeriodEnd: new Date(2024, 1, 17, 1, 1, 25), @@ -63,7 +63,7 @@ describe('ProposalDetailsComponent', () => { proposalLinks: [ { type: ProposalVotingLinkType.NNSDApp, - link: ProposalLinkBaseUrl.NNSDApp + 1, + link: BaseUrl.NNSDApp + 1, }, ], }), diff --git a/src/frontend/src/app/pages/proposal-details/proposal-details.component.ts b/src/frontend/src/app/pages/proposal-details/proposal-details.component.ts index afff90d3..255e0094 100644 --- a/src/frontend/src/app/pages/proposal-details/proposal-details.component.ts +++ b/src/frontend/src/app/pages/proposal-details/proposal-details.component.ts @@ -19,7 +19,7 @@ import { } from '@cg/angular-ui'; import { GetProposalReviewResponse, - ProposalLinkBaseUrl, + BaseUrl, ProposalReviewStatus, ProposalState, } from '~core/api'; @@ -219,7 +219,7 @@ import { ClosedProposalSummaryComponent } from './closed-proposal-summary'; export class ProposalDetailsComponent implements OnInit { public readonly ProposalReviewStatus = signal(ProposalReviewStatus); public readonly ProposalState = signal(ProposalState); - public readonly LinkBaseUrl = signal(ProposalLinkBaseUrl); + public readonly LinkBaseUrl = signal(BaseUrl); public readonly currentProposal = toSyncSignal( this.proposalService.currentProposal$, @@ -281,7 +281,7 @@ export class ProposalDetailsComponent implements OnInit { } public ngOnInit(): void { - this.proposalService.loadProposalList(ProposalState.Any); + this.proposalService.loadProposals(ProposalState.Any); } public onToggleSummary(): void { diff --git a/src/frontend/src/app/pages/proposal-list/proposal-list.component.spec.ts b/src/frontend/src/app/pages/proposal-list/proposal-list.component.spec.ts index 545e1538..04172395 100644 --- a/src/frontend/src/app/pages/proposal-list/proposal-list.component.spec.ts +++ b/src/frontend/src/app/pages/proposal-list/proposal-list.component.spec.ts @@ -27,7 +27,7 @@ describe('ProposalListComponent', () => { beforeEach(async () => { proposalServiceMock = proposalServiceMockFactory(); - defineProp(proposalServiceMock, 'currentProposalList$', of([])); + defineProp(proposalServiceMock, 'currentProposals$', of([])); profileServiceMock = profileServiceMockFactory(); defineProp(profileServiceMock, 'isCurrentUserReviewer$', of(true)); diff --git a/src/frontend/src/app/pages/proposal-list/proposal-list.component.ts b/src/frontend/src/app/pages/proposal-list/proposal-list.component.ts index 2ab2c27a..0141d707 100644 --- a/src/frontend/src/app/pages/proposal-list/proposal-list.component.ts +++ b/src/frontend/src/app/pages/proposal-list/proposal-list.component.ts @@ -4,9 +4,11 @@ import { Component, computed, effect, + inject, + OnInit, signal, } from '@angular/core'; -import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; @@ -15,7 +17,7 @@ import { LinkTextBtnComponent, RadioInputComponent, } from '@cg/angular-ui'; -import { ProposalLinkBaseUrl, ProposalReviewStatus } from '~core/api'; +import { BaseUrl, ProposalReviewStatus, ProposalTopic } from '~core/api'; import { ProposalState } from '~core/api'; import { FormatDatePipe } from '~core/pipes'; import { ProfileService, ProposalService, ReviewService } from '~core/state'; @@ -26,22 +28,22 @@ import { FormFieldComponent, InputDirective, } from '~core/ui'; -import { isNotNil } from '~core/utils'; +import { isNotNil, toSyncSignal } from '~core/utils'; -enum ReviewPeriodStateFilter { +enum ReviewPeriodFilter { InReview = 'InReview', Reviewed = 'Reviewed', All = 'All', } -const reviewPeriodStateFilter = { - [ReviewPeriodStateFilter.InReview]: 'In Review', - [ReviewPeriodStateFilter.Reviewed]: 'Reviewed', - [ReviewPeriodStateFilter.All]: 'All', +const reviewPeriodFilters = { + [ReviewPeriodFilter.InReview]: 'In Review', + [ReviewPeriodFilter.Reviewed]: 'Reviewed', + [ReviewPeriodFilter.All]: 'All', }; interface FilterForm { - reviewPeriodState: FormControl; + reviewPeriod: FormControl; } @Component({ @@ -115,21 +117,30 @@ interface FilterForm { } `, template: ` + @let filterForm = this.filterForm(); + @let listFilters = this.listFilters(); + @let proposals = this.proposalsWithReviewIds(); + @let isReviewer = this.isReviewer(); + + @let ProposalState = this.ProposalState(); + @let ProposalReviewStatus = this.ProposalReviewStatus(); + @let BaseUrl = this.BaseUrl(); +

Proposals

-
+
Select code review status:
- @for (property of listFilter() | keyvalue; track property.key) { + @for (property of listFilters | keyvalue; track property.key) { {{ property.value }} @@ -139,11 +150,7 @@ interface FilterForm {
- @for ( - proposal of proposalListWithReviewIds(); - track proposal.id; - let i = $index - ) { + @for (proposal of proposals; track proposal.id; let i = $index) {

{{ proposal.title }} @@ -154,7 +161,7 @@ interface FilterForm { ID @@ -200,7 +207,7 @@ interface FilterForm { class="proposal__proposer" > @@ -237,31 +244,31 @@ interface FilterForm {
- @if ( - proposal.state === proposalState().InProgress && isReviewer() - ) { + @if (proposal.state === ProposalState.InProgress && isReviewer) { @if (proposal.reviewState === undefined) { Create review - } @else if ( - proposal.reviewState === ProposalReviewStatus().Draft - ) { + } @else if (proposal.reviewState === ProposalReviewStatus.Draft) { Edit review } @else if ( - proposal.reviewState === ProposalReviewStatus().Published + proposal.reviewState === ProposalReviewStatus.Published ) { - - My review - + @if (proposal.reviewId) { + + My review + + } @else { + No review found for this proposal. + } } } @@ -274,83 +281,96 @@ interface FilterForm { } `, }) -export class ProposalListComponent { - public readonly ProposalReviewStatus = signal(ProposalReviewStatus); +export class ProposalListComponent implements OnInit { + readonly #proposalService = inject(ProposalService); + readonly #profileService = inject(ProfileService); + readonly #reviewService = inject(ReviewService); - public readonly proposalList = toSignal( - this.proposalService.currentProposalList$, + public readonly proposals = toSyncSignal( + this.#proposalService.currentProposals$, ); - - public readonly isReviewer = toSignal( - this.profileService.isCurrentUserReviewer$, + public readonly isReviewer = toSyncSignal( + this.#profileService.isCurrentUserReviewer$, ); - public readonly userProfile = toSignal(this.profileService.currentUser$); - public readonly userReviewList = toSignal( - this.reviewService.currentUserReviews$, + + readonly #userProfile = toSyncSignal(this.#profileService.currentUser$); + readonly #currentUserReviews = toSyncSignal( + this.#reviewService.currentUserReviews$, ); - public readonly proposalListWithReviewIds = computed(() => { - return this.proposalList()?.map(proposal => { - const review = this.userReviewList()?.find( - review => review.proposalId === proposal.id, - ); - return { - ...proposal, - reviewId: review?.id, - reviewState: review?.status, - }; - }); + public readonly proposalsWithReviewIds = computed(() => { + const proposals = this.proposals(); + const userReviews = this.#currentUserReviews(); + + return ( + proposals + // [TODO]: Temporarily filter proposal to only IcOsVersionElection + // until we support other proposal types + .filter( + proposal => proposal.topic === ProposalTopic.IcOsVersionElection, + ) + .map(proposal => { + const review = userReviews.find( + review => review.proposalId === proposal.id, + ); + + return { + ...proposal, + reviewId: review?.id, + reviewState: review?.status, + }; + }) + ); }); public readonly filterForm = signal( new FormGroup({ - reviewPeriodState: new FormControl(ReviewPeriodStateFilter.InReview, { + reviewPeriod: new FormControl(ReviewPeriodFilter.InReview, { nonNullable: true, }), }), ); - public readonly proposalState = signal(ProposalState); - public readonly listFilter = signal(reviewPeriodStateFilter); - public readonly linkBaseUrl = signal(ProposalLinkBaseUrl); - - constructor( - private readonly proposalService: ProposalService, - private readonly profileService: ProfileService, - private readonly reviewService: ReviewService, - ) { - this.proposalService.loadProposalList(ProposalState.InProgress); + public readonly ProposalReviewStatus = signal(ProposalReviewStatus); + public readonly ProposalState = signal(ProposalState); + public readonly BaseUrl = signal(BaseUrl); + public readonly listFilters = signal(reviewPeriodFilters); + constructor() { effect(() => { - const userProfile = this.userProfile(); + const userProfile = this.#userProfile(); if (isNotNil(userProfile)) { - this.reviewService.loadReviewsByReviewerId(userProfile.id); + this.#reviewService.loadReviewsByReviewerId(userProfile.id); } }); this.filterForm() .valueChanges.pipe(takeUntilDestroyed()) .subscribe(formValue => { - this.onFilterFormUpdated(formValue.reviewPeriodState); + this.onFilterFormUpdated(formValue.reviewPeriod); }); } + public ngOnInit(): void { + this.#proposalService.loadProposals(ProposalState.InProgress); + } + private async onFilterFormUpdated( - formValue: ReviewPeriodStateFilter | undefined, + formValue: ReviewPeriodFilter | undefined, ): Promise { - let inputParam: ProposalState = ProposalState.Any; - switch (formValue) { - case ReviewPeriodStateFilter.InReview: - inputParam = ProposalState.InProgress; + case ReviewPeriodFilter.InReview: + await this.#proposalService.loadProposals(ProposalState.InProgress); break; - case ReviewPeriodStateFilter.Reviewed: - inputParam = ProposalState.Completed; + case ReviewPeriodFilter.Reviewed: + await this.#proposalService.loadProposals(ProposalState.Completed); break; - } - await this.proposalService.loadProposalList(inputParam); + default: + await this.#proposalService.loadProposals(ProposalState.Any); + break; + } } } diff --git a/src/frontend/src/app/pages/proposal-review-edit/proposal-review-edit.component.ts b/src/frontend/src/app/pages/proposal-review-edit/proposal-review-edit.component.ts index add940ff..3d4084b5 100644 --- a/src/frontend/src/app/pages/proposal-review-edit/proposal-review-edit.component.ts +++ b/src/frontend/src/app/pages/proposal-review-edit/proposal-review-edit.component.ts @@ -53,73 +53,75 @@ import { ReviewDetailsFormComponent } from './review-details-form'; } `, template: ` - @if (currentReview(); as review) { - @if (currentProposal(); as proposal) { - @let ProposalReviewStatus = proposalReviewStatus(); - -
-

- Submit review for proposal {{ proposal.nsProposalId }} -

- - @if (review.status === ProposalReviewStatus.Published) { - Published - } @else { - Draft - } + @let review = this.currentReview(); + @let proposal = this.currentProposal(); + @let isStatusChanging = this.isStatusChanging(); + + @let ProposalReviewStatus = this.ProposalReviewStatus(); + + @if (review && proposal) { +
+

+ Submit review for proposal {{ proposal.nsProposalId }} +

+ + @if (review.status === ProposalReviewStatus.Published) { + Published + } @else { + Draft + } +
+

{{ proposal.title }}

+ +

Commits

+ + +

Review details

+ +
+
-

{{ proposal.title }}

- -

Commits

- - -

Review details

- -
- -
-
- -
- - View review - - - @if (review.status === ProposalReviewStatus.Published) { - - Unpublish - - } @else { - - Publish - - } -
- } +
+ +
+ + View review + + + @if (review.status === ProposalReviewStatus.Published) { + + Unpublish + + } @else { + + Publish + + } +
} `, }) export class ProposalReviewEditComponent implements OnInit { - private readonly router = inject(Router); - private readonly proposalService = inject(ProposalService); - private readonly reviewSubmissionService = inject(ReviewSubmissionService); + readonly #router = inject(Router); + readonly #proposalService = inject(ProposalService); + readonly #reviewSubmissionService = inject(ReviewSubmissionService); - public readonly proposalReviewStatus = signal(ProposalReviewStatus); + public readonly ProposalReviewStatus = signal(ProposalReviewStatus); public readonly currentProposalId = routeParamSignal('proposalId'); public readonly currentProposal = toSyncSignal( - this.proposalService.currentProposal$, + this.#proposalService.currentProposal$, ); public readonly currentReview = toSyncSignal( - this.reviewSubmissionService.review$, + this.#reviewSubmissionService.review$, ); public readonly isStatusChanging = signal(false); @@ -129,8 +131,8 @@ export class ProposalReviewEditComponent implements OnInit { const proposalId = this.currentProposalId(); if (isNotNil(proposalId)) { - this.proposalService.setCurrentProposalId(proposalId); - this.reviewSubmissionService.loadOrCreateReview(proposalId); + this.#proposalService.setCurrentProposalId(proposalId); + this.#reviewSubmissionService.loadOrCreateReview(proposalId); } }); @@ -138,24 +140,24 @@ export class ProposalReviewEditComponent implements OnInit { const proposal = this.currentProposal(); if (isNotNil(proposal) && proposal.state === ProposalState.Completed) { - this.router.navigate(['review', 'view', { id: proposal.id }]); + this.#router.navigate(['review', 'view', { id: proposal.id }]); } }); } public ngOnInit(): void { - this.proposalService.loadProposalList(ProposalState.InProgress); + this.#proposalService.loadProposals(ProposalState.InProgress); } public async publishReview(): Promise { this.isStatusChanging.set(true); - await this.reviewSubmissionService.publishReview(); + await this.#reviewSubmissionService.publishReview(); this.isStatusChanging.set(false); } public async editReview(): Promise { this.isStatusChanging.set(true); - await this.reviewSubmissionService.editReview(); + await this.#reviewSubmissionService.editReview(); this.isStatusChanging.set(false); } } diff --git a/src/frontend/src/app/pages/proposal-review/proposal-review.component.ts b/src/frontend/src/app/pages/proposal-review/proposal-review.component.ts index f9d6d5da..c18f5c3e 100644 --- a/src/frontend/src/app/pages/proposal-review/proposal-review.component.ts +++ b/src/frontend/src/app/pages/proposal-review/proposal-review.component.ts @@ -60,13 +60,14 @@ import { isNil, isNotNil, routeParamSignal, toSyncSignal } from '~core/utils'; } `, template: ` - @let review = this.currentReview(); + @let review = this.review(); @let proposal = this.currentProposal(); @let reviewer = this.currentReviewer(); + @let isReviewOwner = this.isReviewOwner(); + @let reviewSummary = this.reviewSummary(); + @let ProposalReviewStatus = this.ProposalReviewStatus(); @let ProposalState = this.ProposalState(); - @let isReviewOwner = this.isReviewOwner(); - @let currentReviewSummary = this.currentReviewSummary(); @if (review && proposal && reviewer) {
@@ -194,44 +195,40 @@ import { isNil, isNotNil, routeParamSignal, toSyncSignal } from '~core/utils'; } - @if (isReviewOwner && currentReviewSummary) { + @if (isReviewOwner && reviewSummary) {

Proposal summary markdown

} } `, }) export class ProposalReviewComponent implements OnInit { - private readonly reviewService = inject(ReviewService); - private readonly proposalService = inject(ProposalService); - private readonly profileService = inject(ProfileService); + readonly #reviewService = inject(ReviewService); + readonly #proposalService = inject(ProposalService); + readonly #profileService = inject(ProfileService); public readonly ProposalReviewStatus = signal(ProposalReviewStatus); public readonly ProposalState = signal(ProposalState); - public readonly currentReviewId = routeParamSignal('reviewId'); + readonly #reviewId = routeParamSignal('reviewId'); + readonly #currentUser = toSyncSignal(this.#profileService.currentUser$); + readonly #reviewers = toSyncSignal(this.#profileService.reviewers$); - public readonly currentReview = toSyncSignal( - this.reviewService.currentReview$, - ); - public readonly currentReviewSummary = toSyncSignal( - this.reviewService.currentUserReviewSummary$, - ); - private readonly currentUser$ = toSyncSignal( - this.profileService.currentUser$, + public readonly review = toSyncSignal(this.#reviewService.currentReview$); + public readonly reviewSummary = toSyncSignal( + this.#reviewService.currentUserReviewSummary$, ); public readonly currentProposal = toSyncSignal( - this.proposalService.currentProposal$, + this.#proposalService.currentProposal$, ); - private readonly reviewers = toSyncSignal(this.profileService.reviewers$); public readonly currentReviewer = computed(() => { - const reviewerId = this.reviewOwnerId(); - const reviewers = this.reviewers(); + const reviewerId = this.#reviewOwnerId(); + const reviewers = this.#reviewers(); if (isNil(reviewerId) || isNil(reviewers)) { return null; } @@ -240,8 +237,8 @@ export class ProposalReviewComponent implements OnInit { }); public readonly isReviewOwner = computed(() => { - const reviewOwnerId = this.reviewOwnerId(); - const currentUserId = this.currentUserId(); + const reviewOwnerId = this.#reviewOwnerId(); + const currentUserId = this.#currentUserId(); if (isNil(reviewOwnerId) || isNil(currentUserId)) { return false; @@ -250,8 +247,8 @@ export class ProposalReviewComponent implements OnInit { return reviewOwnerId === currentUserId; }); - private readonly reviewOwnerId = computed(() => { - const review = this.currentReview(); + readonly #reviewOwnerId = computed(() => { + const review = this.review(); if (isNil(review)) { return null; } @@ -259,8 +256,8 @@ export class ProposalReviewComponent implements OnInit { return review.userId; }); - private readonly currentUserId = computed(() => { - const user = this.currentUser$(); + readonly #currentUserId = computed(() => { + const user = this.#currentUser(); if (isNil(user)) { return null; } @@ -268,8 +265,8 @@ export class ProposalReviewComponent implements OnInit { return user.id; }); - private readonly currentProposalId = computed(() => { - const currentReview = this.currentReview(); + readonly #currentProposalId = computed(() => { + const currentReview = this.review(); if (isNil(currentReview)) { return null; } @@ -279,33 +276,33 @@ export class ProposalReviewComponent implements OnInit { constructor() { effect(() => { - const reviewId = this.currentReviewId(); + const reviewId = this.#reviewId(); if (isNotNil(reviewId)) { - this.reviewService.loadReview(reviewId); + this.#reviewService.loadReview(reviewId); } }); effect(() => { const isReviewOwner = this.isReviewOwner(); - const currentProposalId = this.currentProposalId(); + const currentProposalId = this.#currentProposalId(); if (isReviewOwner && isNotNil(currentProposalId)) { - this.reviewService.loadReviewSummary(currentProposalId); + this.#reviewService.loadReviewSummary(currentProposalId); } }); effect(() => { - const proposalId = this.currentProposalId(); + const proposalId = this.#currentProposalId(); if (isNotNil(proposalId)) { - this.proposalService.setCurrentProposalId(proposalId); + this.#proposalService.setCurrentProposalId(proposalId); } }); } public ngOnInit(): void { - this.proposalService.loadProposalList(ProposalState.Any); - this.profileService.loadReviewerProfiles(); + this.#proposalService.loadProposals(ProposalState.Any); + this.#profileService.loadReviewerProfiles(); } } diff --git a/src/nns-testing/package.json b/src/nns-testing/package.json index 7cb492a3..3b6e3485 100644 --- a/src/nns-testing/package.json +++ b/src/nns-testing/package.json @@ -5,6 +5,7 @@ "start": "bun run ./src/index.ts" }, "dependencies": { - "@cg/nns-utils": "workspace:*" + "@cg/nns-utils": "workspace:*", + "@cg/utils": "workspace:*" } } diff --git a/src/nns-testing/src/governance/governance.ts b/src/nns-testing/src/governance/governance.ts index d5618a22..32715676 100644 --- a/src/nns-testing/src/governance/governance.ts +++ b/src/nns-testing/src/governance/governance.ts @@ -1,4 +1,10 @@ -import { AnonymousIdentity, HttpAgent, Identity } from '@dfinity/agent'; +import { + Actor, + ActorSubclass, + AnonymousIdentity, + HttpAgent, + Identity, +} from '@dfinity/agent'; import { AccountIdentifier } from '@dfinity/ledger-icp'; import { GovernanceCanister, @@ -6,6 +12,7 @@ import { GovernanceError, } from '@dfinity/nns'; import { isNullish } from '@dfinity/utils'; +import { isNil } from '@cg/utils'; import { Ledger } from '../icp-ledger'; import { CreateAgentOptions, createAgent } from '../agent'; @@ -15,21 +22,37 @@ import { generateNonce, getNeuronSubaccount, optional, + GovernanceDeclarations, } from '@cg/nns-utils'; -import { CreateRvmProposalRequest } from './types'; +import { + CreateIcOsVersionElectionProposalRequest, + SyncMainnetProposalRequest, +} from './types'; import { Ora } from 'ora'; export class Governance { - private readonly governanceCanister: GovernanceCanister; - private readonly defaultIdentity = new AnonymousIdentity(); + readonly #governanceCanister: GovernanceCanister; + readonly #defaultIdentity = new AnonymousIdentity(); + + readonly #mainnetGovernanceActor: ActorSubclass; private constructor( private readonly agent: HttpAgent, + mainnetAgent: HttpAgent, private readonly ledger: Ledger, ) { - agent.replaceIdentity(this.defaultIdentity); + agent.replaceIdentity(this.#defaultIdentity); + mainnetAgent.replaceIdentity(this.#defaultIdentity); + + this.#mainnetGovernanceActor = Actor.createActor( + GovernanceDeclarations.idlFactory, + { + canisterId: GOVERNANCE_CANISTER_ID, + agent: mainnetAgent, + }, + ); - this.governanceCanister = GovernanceCanister.create({ + this.#governanceCanister = GovernanceCanister.create({ agent, canisterId: GOVERNANCE_CANISTER_ID, }); @@ -39,9 +62,13 @@ export class Governance { agentOptions: CreateAgentOptions, ): Promise { const agent = await createAgent(agentOptions); + const mainnetAgent = new HttpAgent({ + ...agentOptions, + host: 'https://icp-api.io', + }); const ledger = await Ledger.create(agentOptions); - return new Governance(agent, ledger); + return new Governance(agent, mainnetAgent, ledger); } public async createNeuron(identity: Identity, spinner: Ora): Promise { @@ -70,7 +97,7 @@ export class Governance { spinner.text = 'Creating neuron...'; const neuronId = - await this.governanceCanister.claimOrRefreshNeuronFromAccount({ + await this.#governanceCanister.claimOrRefreshNeuronFromAccount({ controller: ownerPrincipal, memo: nonce, }); @@ -80,7 +107,7 @@ export class Governance { } spinner.text = 'Increasing dissolve delay...'; - await this.governanceCanister.increaseDissolveDelay({ + await this.#governanceCanister.increaseDissolveDelay({ neuronId, additionalDissolveDelaySeconds: 60 * 60 * 24 * 7 * 52 * 1, // 1 year }); @@ -93,15 +120,15 @@ export class Governance { throw error; } finally { - this.agent.replaceIdentity(this.defaultIdentity); + this.agent.replaceIdentity(this.#defaultIdentity); } } - public async createRvmProposal( + public async createIcOsVersionElectionProposal( identity: Identity, - payload: CreateRvmProposalRequest, + payload: CreateIcOsVersionElectionProposalRequest, ): Promise { - const rvmPayload = encodeUpdateElectedReplicaVersionsPayload({ + const payloadBytes = encodeUpdateElectedReplicaVersionsPayload({ release_package_urls: [ `https://download.dfinity.systems/ic/${payload.replicaVersion}/guest-os/update-img/update-img.tar.gz`, `https://download.dfinity.network/ic/${payload.replicaVersion}/guest-os/update-img/update-img.tar.gz`, @@ -112,7 +139,7 @@ export class Governance { replica_versions_to_unelect: [], }); - const args: MakeProposalRequest = { + return await this.makeProposal(identity, { neuronId: payload.neuronId, summary: payload.summary, title: payload.title, @@ -120,14 +147,71 @@ export class Governance { action: { ExecuteNnsFunction: { nnsFunctionId: 38, - payloadBytes: rvmPayload, + payloadBytes, }, }, - }; + }); + } + public async syncMainnetProposal( + identity: Identity, + payload: SyncMainnetProposalRequest, + spinner: Ora, + ): Promise { + const { neuronId, proposalId } = payload; + + spinner.text = `Fetching proposal with ID ${proposalId} from mainnet...`; + const [proposalInfo] = + await this.#mainnetGovernanceActor.get_proposal_info(proposalId); + if (isNil(proposalInfo)) { + throw new Error(`Proposal with ID ${proposalId} not found on mainnet.`); + } + + const [proposal] = proposalInfo.proposal; + if (isNil(proposal)) { + throw new Error( + `Proposal with ID ${proposalId} was found on mainnet but has no payload.`, + ); + } + + const [action] = proposal.action; + if (isNil(action)) { + throw new Error( + `Proposal with ID ${proposalId} was found on mainnet but has no action.`, + ); + } + + const [title] = proposal.title; + if ('ExecuteNnsFunction' in action) { + return await this.makeProposal(identity, { + action: { + ExecuteNnsFunction: { + nnsFunctionId: action.ExecuteNnsFunction.nns_function, + payloadBytes: + action.ExecuteNnsFunction.payload instanceof Uint8Array + ? (action.ExecuteNnsFunction.payload.buffer as ArrayBuffer) + : new Uint8Array(action.ExecuteNnsFunction.payload).buffer, + }, + }, + neuronId, + summary: proposal.summary, + title, + url: proposal.url, + }); + } + + throw new Error( + 'Only proposals that use the `ExecuteNnsFunction` action can be synced.', + ); + } + + private async makeProposal( + identity: Identity, + req: MakeProposalRequest, + ): Promise { try { this.agent.replaceIdentity(identity); - await this.governanceCanister.makeProposal(args); + await this.#governanceCanister.makeProposal(req); } catch (error) { if (error instanceof GovernanceError) { throw error.detail; @@ -135,7 +219,7 @@ export class Governance { throw error; } finally { - this.agent.replaceIdentity(this.defaultIdentity); + this.agent.replaceIdentity(this.#defaultIdentity); } } } diff --git a/src/nns-testing/src/governance/types.ts b/src/nns-testing/src/governance/types.ts index 6d607dfc..d094c0bc 100644 --- a/src/nns-testing/src/governance/types.ts +++ b/src/nns-testing/src/governance/types.ts @@ -1,6 +1,11 @@ -export interface CreateRvmProposalRequest { +export interface CreateIcOsVersionElectionProposalRequest { neuronId: bigint; title: string; summary: string; replicaVersion: string; } + +export interface SyncMainnetProposalRequest { + neuronId: bigint; + proposalId: bigint; +} diff --git a/src/nns-testing/src/index.ts b/src/nns-testing/src/index.ts index 42648edf..d12bfb44 100644 --- a/src/nns-testing/src/index.ts +++ b/src/nns-testing/src/index.ts @@ -1,19 +1,20 @@ import { Separator, editor, input, select, confirm } from '@inquirer/prompts'; -import { isNullish, nonNullish } from '@dfinity/utils'; import { Ed25519KeyIdentity } from '@dfinity/identity'; -import ora from 'ora'; +import ora, { Ora } from 'ora'; import { Store } from './util'; import { checkDfxRunning, getReplicaPort } from './dfx'; import { Governance } from './governance'; +import { isNil, isNotNil } from '@cg/utils'; const store = await Store.create(); enum Command { GenerateIdentity = 'generateIdentity', CreateNeuron = 'createNeuron', - CreateRvmProposal = 'createRvmProposal', - CreateRvmProposalWithDefaults = 'createRvmProposalWithDefaults', + SyncMainnetProposal = 'syncMainnetProposal', + CreateIcOsVersionElectionProposal = 'createIcOsVersionElectionProposal', + CreateIcOsVersionElectionProposalWithDefaults = 'createIcOsVersionElectionProposalWithDefaults', DeleteIdentity = 'deleteIdentity', DeleteNeuron = 'deleteNeuron', Exit = 'exit', @@ -24,7 +25,7 @@ while (shouldRun) { const isDfxRunning = await checkDfxRunning(); if (!isDfxRunning) { - console.log('DFX is not running, please start it and try again.'); + console.info('DFX is not running, please start it and try again.'); shouldRun = false; break; } @@ -40,10 +41,17 @@ while (shouldRun) { { name: 'Generate an identity', value: Command.GenerateIdentity }, { name: 'Create a neuron', value: Command.CreateNeuron }, new Separator(), - { name: 'Create RVM proposal', value: Command.CreateRvmProposal }, { - name: 'Create RVM proposal (with defaults)', - value: Command.CreateRvmProposalWithDefaults, + name: 'Sync mainnet proposal', + value: Command.SyncMainnetProposal, + }, + { + name: 'Create IcOsVersionElection proposal', + value: Command.CreateIcOsVersionElectionProposal, + }, + { + name: 'Create IcOsVersionElection proposal (with defaults)', + value: Command.CreateIcOsVersionElectionProposalWithDefaults, }, new Separator(), { name: 'Delete identity', value: Command.DeleteIdentity }, @@ -63,12 +71,16 @@ while (shouldRun) { await createNeuron(config); break; - case Command.CreateRvmProposal: - await createRvmProposal(config); + case Command.SyncMainnetProposal: + await syncMainnetProposal(config); break; - case Command.CreateRvmProposalWithDefaults: - await createRvmProposal(config, true); + case Command.CreateIcOsVersionElectionProposal: + await createIcOsVersionElectionProposal(config); + break; + + case Command.CreateIcOsVersionElectionProposalWithDefaults: + await createIcOsVersionElectionProposal(config, true); break; case Command.DeleteIdentity: @@ -131,21 +143,21 @@ function printConfig({ neuronId, replicaPort, host, identity }: Config): void { Replica port: ${replicaPort} Replica host: ${host}`; - if (nonNullish(identity)) { + if (isNotNil(identity)) { const principal = identity.getPrincipal().toText(); configString += `\n Principal: ${principal}`; } else { configString += '\n No identity found'; } - if (nonNullish(neuronId)) { + if (isNotNil(neuronId)) { configString += `\n Neuron ID: ${neuronId}`; } else { configString += '\n No neuron ID found'; } configString += '\n'; - console.log(configString); + console.info(configString); } async function generateIdentity(): Promise { @@ -157,52 +169,60 @@ async function generateIdentity(): Promise { return identity; } -async function createNeuron({ identity, host }: Config): Promise { - if (isNullish(identity)) { - console.log('No identity found, generating one now...'); +async function createNeuron(config: Config): Promise { + const spinner = ora().start(); - identity = await generateIdentity(); + try { + const identity = await getOrCreateIdentity(config.identity, spinner); + const governance = await Governance.create({ host: config.host }); + const neuronId = await governance.createNeuron(identity, spinner); + + await store.setNeuronId(neuronId.toString()); + return neuronId; + } finally { + spinner.stop(); } +} +async function syncMainnetProposal(config: Config): Promise { const spinner = ora().start(); - const governance = await Governance.create({ host: host }); + const identity = await getOrCreateIdentity(config.identity, spinner); + const neuronId = await getOrCreateNeuron(identity, config, spinner); + spinner.stop(); + + const governance = await Governance.create({ host: config.host }); + const proposalId = await input({ + message: 'Enter the proposal ID to sync:', + }); - const neuronId = await governance.createNeuron(identity, spinner); + spinner.start('Syncing mainnet proposal...'); try { - await store.setNeuronId(neuronId.toString()); + await governance.syncMainnetProposal( + identity, + { + neuronId, + proposalId: BigInt(proposalId), + }, + spinner, + ); } finally { spinner.stop(); } - - return neuronId; } -async function createRvmProposal( +async function createIcOsVersionElectionProposal( config: Config, withDefaults = false, ): Promise { - let { identity } = config; - let neuronId = 0n; - - if (isNullish(identity)) { - console.log('No identity found, generating one now...'); - - identity = await generateIdentity(); - } - - if (isNullish(config.neuronId)) { - console.log('No neuron ID found, creating a neuron now...'); - neuronId = await createNeuron({ - ...config, - identity, - }); - } else { - neuronId = BigInt(config.neuronId); - } + const spinner = ora().start(); + const identity = await getOrCreateIdentity(config.identity, spinner); + const neuronId = await getOrCreateNeuron(identity, config, spinner); const governance = await Governance.create({ host: config.host }); + spinner.stop(); + const title = await input({ message: 'Enter the proposal title:', default: withDefaults @@ -225,10 +245,9 @@ async function createRvmProposal( : undefined, }); - const spinner = ora('Creating RVM proposal...').start(); - + spinner.start('Creating IcOsVersionElection proposal...'); try { - await governance.createRvmProposal(identity, { + await governance.createIcOsVersionElectionProposal(identity, { neuronId, title, summary, @@ -247,3 +266,35 @@ async function deleteIdentity(): Promise { async function deleteNeuron(): Promise { await store.deleteNeuronId(); } + +async function getOrCreateIdentity( + identity: Ed25519KeyIdentity | undefined, + spinner: Ora, +): Promise { + if (isNil(identity)) { + spinner.text = 'No identity found, generating one now...'; + identity = await generateIdentity(); + } + + return identity; +} + +async function getOrCreateNeuron( + identity: Ed25519KeyIdentity, + config: Config, + spinner: Ora, +): Promise { + let neuronId = 0n; + + if (isNil(config.neuronId)) { + spinner.text = 'No neuron ID found, creating a neuron now...'; + neuronId = await createNeuron({ + ...config, + identity, + }); + } else { + neuronId = BigInt(config.neuronId); + } + + return neuronId; +}