diff --git a/CHANGELOG-Nns-Dapp-unreleased.md b/CHANGELOG-Nns-Dapp-unreleased.md
index 5d887a5f3fa..c3e62df3013 100644
--- a/CHANGELOG-Nns-Dapp-unreleased.md
+++ b/CHANGELOG-Nns-Dapp-unreleased.md
@@ -15,6 +15,7 @@ proposal is successful, the changes it released will be moved from this file to
#### Added
- Show USD values of tokens.
+- Reporting page for NNS Neurons and ICP transactions.
#### Changed
diff --git a/dfx.json b/dfx.json
index 5078934a873..f8160b0cc23 100644
--- a/dfx.json
+++ b/dfx.json
@@ -424,7 +424,7 @@
"ENABLE_CKTESTBTC": false,
"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING": false,
"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION": false,
- "ENABLE_EXPORT_NEURONS_REPORT": false,
+ "ENABLE_EXPORT_NEURONS_REPORT": true,
"ENABLE_USD_VALUES": true,
"ENABLE_USD_VALUES_FOR_NEURONS": false
}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 911c56d5233..8167e9e6895 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -4269,9 +4269,9 @@
"dev": true
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{
@@ -9361,9 +9361,9 @@
"dev": true
},
"nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true
},
"napi-build-utils": {
diff --git a/frontend/src/lib/components/ic/AmountWithUsd.svelte b/frontend/src/lib/components/ic/AmountWithUsd.svelte
new file mode 100644
index 00000000000..1eaf800d095
--- /dev/null
+++ b/frontend/src/lib/components/ic/AmountWithUsd.svelte
@@ -0,0 +1,35 @@
+
+
+
+
+
+ {#if nonNullish(amountInUsd)}
+ ${formatNumber(amountInUsd)}
+ {:else}
+ $-/-
+ {/if}
+
+
+
+
diff --git a/frontend/src/lib/components/neuron-detail/NnsNeuronRewardStatusAction.svelte b/frontend/src/lib/components/neuron-detail/NnsNeuronRewardStatusAction.svelte
index 865627374fc..decad5d3ed3 100644
--- a/frontend/src/lib/components/neuron-detail/NnsNeuronRewardStatusAction.svelte
+++ b/frontend/src/lib/components/neuron-detail/NnsNeuronRewardStatusAction.svelte
@@ -1,12 +1,13 @@
-
+
+ {#if isFollowingReset}
+
+ {/if}
diff --git a/frontend/src/lib/components/neuron-detail/actions/ConfirmFollowingButton.svelte b/frontend/src/lib/components/neuron-detail/actions/ConfirmFollowingButton.svelte
new file mode 100644
index 00000000000..e91a035eb57
--- /dev/null
+++ b/frontend/src/lib/components/neuron-detail/actions/ConfirmFollowingButton.svelte
@@ -0,0 +1,32 @@
+
+
+
diff --git a/frontend/src/lib/components/reporting/ReportingNeuronsButton.svelte b/frontend/src/lib/components/reporting/ReportingNeuronsButton.svelte
index 6dd78d80168..382b8570c08 100644
--- a/frontend/src/lib/components/reporting/ReportingNeuronsButton.svelte
+++ b/frontend/src/lib/components/reporting/ReportingNeuronsButton.svelte
@@ -6,7 +6,7 @@
CsvGenerationError,
FileSystemAccessError,
generateCsvFileToSave,
- } from "$lib/utils/export-to-csv.utils";
+ } from "$lib/utils/reporting.utils";
import { toastsError } from "$lib/stores/toasts.store";
import { formatDateCompact } from "$lib/utils/date.utils";
import type { NeuronInfo } from "@dfinity/nns";
@@ -33,7 +33,10 @@
const exportNeurons = async () => {
try {
loading = true;
- startBusy({ initiator: "reporting-neurons" });
+ startBusy({
+ initiator: "reporting-neurons",
+ labelKey: "reporting.busy_screen",
+ });
// we are logged in to be able to interact with the button
const signIdentity = await getAuthenticatedIdentity();
@@ -50,51 +53,51 @@
headers: [
{
id: "neuronId",
- label: $i18n.export_csv_neurons.neuron_id,
+ label: $i18n.reporting.neuron_id,
},
{
id: "project",
- label: $i18n.export_csv_neurons.project,
+ label: $i18n.reporting.project,
},
{
id: "symbol",
- label: $i18n.export_csv_neurons.symbol,
+ label: $i18n.reporting.symbol,
},
{
id: "neuronAccountId",
- label: $i18n.export_csv_neurons.neuron_account_id,
+ label: $i18n.reporting.neuron_account_id,
},
{
id: "controllerId",
- label: $i18n.export_csv_neurons.controller_id,
+ label: $i18n.reporting.controller_id,
},
{
id: "stake",
- label: $i18n.export_csv_neurons.stake,
+ label: $i18n.reporting.stake,
},
{
id: "availableMaturity",
- label: $i18n.export_csv_neurons.available_maturity,
+ label: $i18n.reporting.available_maturity,
},
{
id: "stakedMaturity",
- label: $i18n.export_csv_neurons.staked_maturity,
+ label: $i18n.reporting.staked_maturity,
},
{
id: "dissolveDelaySeconds",
- label: $i18n.export_csv_neurons.dissolve_delay,
+ label: $i18n.reporting.dissolve_delay,
},
{
id: "dissolveDate",
- label: $i18n.export_csv_neurons.dissolve_date,
+ label: $i18n.reporting.dissolve_date,
},
{
id: "creationDate",
- label: $i18n.export_csv_neurons.creation_date,
+ label: $i18n.reporting.creation_date,
},
{
id: "state",
- label: $i18n.export_csv_neurons.state,
+ label: $i18n.reporting.state,
},
],
fileName,
@@ -104,15 +107,15 @@
if (error instanceof FileSystemAccessError) {
toastsError({
- labelKey: "export_error.file_system_access",
+ labelKey: "reporting.error_file_system_access",
});
} else if (error instanceof CsvGenerationError) {
toastsError({
- labelKey: "export_error.csv_generation",
+ labelKey: "reporting.error_csv_generation",
});
} else {
toastsError({
- labelKey: "export_error.neurons",
+ labelKey: "reporting.error_neurons",
});
}
} finally {
diff --git a/frontend/src/lib/components/reporting/ReportingTransactionsButton.svelte b/frontend/src/lib/components/reporting/ReportingTransactionsButton.svelte
index 4387b566286..2264c4b2a2e 100644
--- a/frontend/src/lib/components/reporting/ReportingTransactionsButton.svelte
+++ b/frontend/src/lib/components/reporting/ReportingTransactionsButton.svelte
@@ -9,11 +9,11 @@
generateCsvFileToSave,
type CsvHeader,
type TransactionsCsvData,
- } from "$lib/utils/export-to-csv.utils";
+ } from "$lib/utils/reporting.utils";
import { toastsError } from "$lib/stores/toasts.store";
import { formatDateCompact } from "$lib/utils/date.utils";
import { authStore } from "$lib/stores/auth.store";
- import { getAccountTransactionsConcurrently } from "$lib/services/export-data.services";
+ import { getAccountTransactionsConcurrently } from "$lib/services/reporting.services";
import { SignIdentity, type Identity } from "@dfinity/agent";
import { createSwapCanisterAccountsStore } from "$lib/derived/sns-swap-canisters-accounts.derived";
import { replacePlaceholders } from "$lib/utils/i18n.utils";
@@ -53,7 +53,10 @@
const exportIcpTransactions = async () => {
try {
loading = true;
- startBusy({ initiator: "reporting-transactions" });
+ startBusy({
+ initiator: "reporting-transactions",
+ labelKey: "reporting.busy_screen",
+ });
// we are logged in to be able to interact with the button
const signIdentity = identity as SignIdentity;
@@ -81,37 +84,37 @@
const headers: CsvHeader[] = [
{
id: "id",
- label: $i18n.export_csv_neurons.transaction_id,
+ label: $i18n.reporting.transaction_id,
},
{
id: "project",
- label: $i18n.export_csv_neurons.project,
+ label: $i18n.reporting.project,
},
{
id: "symbol",
- label: $i18n.export_csv_neurons.symbol,
+ label: $i18n.reporting.symbol,
},
{
id: "to",
- label: $i18n.export_csv_neurons.to,
+ label: $i18n.reporting.to,
},
{
id: "from",
- label: $i18n.export_csv_neurons.from,
+ label: $i18n.reporting.from,
},
{
id: "type",
- label: $i18n.export_csv_neurons.transaction_type,
+ label: $i18n.reporting.transaction_type,
},
{
id: "amount",
- label: replacePlaceholders($i18n.export_csv_neurons.amount, {
+ label: replacePlaceholders($i18n.reporting.amount, {
$tokenSymbol: ICPToken.symbol,
}),
},
{
id: "timestamp",
- label: $i18n.export_csv_neurons.timestamp,
+ label: $i18n.reporting.timestamp,
},
];
const fileName = `icp_transactions_export_${formatDateCompact(new Date())}`;
@@ -126,15 +129,15 @@
if (error instanceof FileSystemAccessError) {
toastsError({
- labelKey: "export_error.file_system_access",
+ labelKey: "reporting.error_file_system_access",
});
} else if (error instanceof CsvGenerationError) {
toastsError({
- labelKey: "export_error.csv_generation",
+ labelKey: "reporting.error_csv_generation",
});
} else {
toastsError({
- labelKey: "export_error.neurons",
+ labelKey: "reporting.error_transactions",
});
}
} finally {
diff --git a/frontend/src/lib/components/staking/ProjectStakeCell.svelte b/frontend/src/lib/components/staking/ProjectStakeCell.svelte
index 8039f947a52..bcf75d6c8b4 100644
--- a/frontend/src/lib/components/staking/ProjectStakeCell.svelte
+++ b/frontend/src/lib/components/staking/ProjectStakeCell.svelte
@@ -1,5 +1,7 @@
@@ -22,18 +21,12 @@
>
{:else if isUserTokenData(rowData)}
-
+ {#if $ENABLE_USD_VALUES}
+
+ {:else}
- {#if $ENABLE_USD_VALUES}
-
- {#if nonNullish(rowData.balanceInUsd)}
- ${formatNumber(rowData.balanceInUsd)}
- {:else}
- $-/-
- {/if}
-
- {/if}
-
+ {/if}
{:else}
-/-
{/if}
@@ -43,16 +36,4 @@
display: flex;
align-items: center;
}
-
- .values {
- display: flex;
- flex-direction: column;
- gap: var(--padding-0_5x);
- align-items: flex-end;
-
- .usd-value {
- color: var(--text-description);
- font-size: var(--font-size-small);
- }
- }
diff --git a/frontend/src/lib/constants/neurons.constants.ts b/frontend/src/lib/constants/neurons.constants.ts
index 2e82ef133fd..59872107bbc 100644
--- a/frontend/src/lib/constants/neurons.constants.ts
+++ b/frontend/src/lib/constants/neurons.constants.ts
@@ -1,6 +1,7 @@
import {
SECONDS_IN_7_DAYS,
SECONDS_IN_HALF_YEAR,
+ SECONDS_IN_MONTH,
} from "$lib/constants/constants";
import { enumValues } from "$lib/utils/enum.utils";
import { Topic } from "@dfinity/nns";
@@ -42,5 +43,12 @@ export const TOPICS_TO_FOLLOW_NNS = [
// TODO(mstr): replace with the actual ic link.
export const START_REDUCING_VOTING_POWER_AFTER_SECONDS = SECONDS_IN_HALF_YEAR;
+// After a neuron has experienced voting power reduction for this amount of time,
+// a couple of things happen:
+// 1. Deciding voting power reaches 0.
+// 2. Its following on topics other than NeuronManagement are cleared.
+// https://github.com/dfinity/ic/blob/c2da5aca97a07bae4fcbf5d72a8c0448b40599d7/rs/nns/governance/canister/governance.did#L584
+export const CLEAR_FOLLOWING_AFTER_SECONDS = SECONDS_IN_MONTH;
+
// To notify users that their rewards will start decreasing in 30 days.
export const NOTIFICATION_PERIOD_BEFORE_REWARD_LOSS_STARTS_DAYS = 30;
diff --git a/frontend/src/lib/derived/icp-tokens-list-user.derived.ts b/frontend/src/lib/derived/icp-tokens-list-user.derived.ts
index 119f9956d44..e9111fe98e0 100644
--- a/frontend/src/lib/derived/icp-tokens-list-user.derived.ts
+++ b/frontend/src/lib/derived/icp-tokens-list-user.derived.ts
@@ -13,29 +13,12 @@ import type { Account, AccountType } from "$lib/types/account";
import { UserTokenAction, type UserToken } from "$lib/types/tokens-page";
import type { Universe } from "$lib/types/universe";
import { buildWalletUrl } from "$lib/utils/navigation.utils";
-import { sortUserTokens } from "$lib/utils/token.utils";
+import { getUsdValue, sortUserTokens } from "$lib/utils/token.utils";
import { Principal } from "@dfinity/principal";
import { isNullish, TokenAmountV2 } from "@dfinity/utils";
import { derived, type Readable } from "svelte/store";
import { nnsUniverseStore } from "./nns-universe.derived";
-const getUsdValue = ({
- balance,
- icpPrice,
-}: {
- balance: TokenAmountV2;
- icpPrice?: number;
-}): number | undefined => {
- const balanceE8s = Number(balance.toE8s());
- if (balanceE8s === 0) {
- return 0;
- }
- if (isNullish(icpPrice)) {
- return undefined;
- }
- return (balanceE8s * icpPrice) / 100_000_000;
-};
-
const convertAccountToUserTokenData = ({
nnsUniverse,
i18nObj: i18nObj,
@@ -85,8 +68,8 @@ const convertAccountToUserTokenData = ({
subtitle: subtitleMap[account.type],
balance,
balanceInUsd: getUsdValue({
- balance,
- icpPrice,
+ amount: balance,
+ tokenPrice: icpPrice,
}),
logo: nnsUniverse.logo,
token: NNS_TOKEN_DATA,
diff --git a/frontend/src/lib/derived/tokens-list-base.derived.ts b/frontend/src/lib/derived/tokens-list-base.derived.ts
index 8b41510ae6c..b179eb5c9b7 100644
--- a/frontend/src/lib/derived/tokens-list-base.derived.ts
+++ b/frontend/src/lib/derived/tokens-list-base.derived.ts
@@ -1,24 +1,10 @@
-import {
- LEDGER_CANISTER_ID,
- OWN_CANISTER_ID_TEXT,
-} from "$lib/constants/canister-ids.constants";
import type { UserTokenBase } from "$lib/types/tokens-page";
import type { Universe } from "$lib/types/universe";
+import { getLedgerCanisterIdFromUniverse } from "$lib/utils/universe.utils";
import { Principal } from "@dfinity/principal";
-import { nonNullish } from "@dfinity/utils";
import { derived, type Readable } from "svelte/store";
import { universesStore } from "./universes.derived";
-const getLedgerCanisterIdFromUniverse = (universe: Universe): Principal => {
- if (universe.canisterId === OWN_CANISTER_ID_TEXT) {
- return LEDGER_CANISTER_ID;
- }
- if (nonNullish(universe.summary)) {
- return universe.summary.ledgerCanisterId;
- }
- return Principal.fromText(universe.canisterId);
-};
-
const convertUniverseToBaseTokenData = (universe: Universe): UserTokenBase => ({
universeId: Principal.fromText(universe.canisterId),
ledgerCanisterId: getLedgerCanisterIdFromUniverse(universe),
diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json
index 6a2d4528a1d..67a1061465d 100644
--- a/frontend/src/lib/i18n/en.json
+++ b/frontend/src/lib/i18n/en.json
@@ -157,44 +157,42 @@
"main_icp_account_id": "Main ICP Account ID",
"account_id_tooltip": "You can send ICP both to your principal ID and account ID, however some exchanges or wallets may not support transactions using a principal ID."
},
- "export_csv_neurons": {
- "account_id": "Account ID",
- "account_id_label": "NNS Account Principal ID",
- "account_name": "Account Name",
- "amount": "Amount($tokenSymbol)",
- "available_maturity": "Available Maturity",
- "balance": "Balance($tokenSymbol)",
- "controller_id": "Controller Principal ID",
- "creation_date": "Creation Date",
+ "reporting": {
+ "neurons_title": "NNS Neuron Portfolio",
+ "neurons_description": "Download a detailed CSV report of your NNS neuron portfolio, which includes the stake, maturity, and neuron account IDs for manual tracking and analysis.",
+ "neurons_download": "Export Neuron Portfolio",
+ "transactions_title": "ICP Transactions",
+ "transactions_description": "Download a detailed CSV report of your ICP transaction history, including both regular ICP accounts and NNS neurons showing staking and minting transactions for manual tracking and analysis.",
+ "transactions_download": "Export Transactions",
+ "busy_screen": "Generating report... This may take a moment",
+ "principal_account_id": "NNS Account Principal ID",
"date_label": "Export Date Time",
- "dissolve_date": "Dissolve Date",
- "dissolve_delay": "Dissolve Delay",
- "from": "From",
- "neuron_account_id": "Neuron Account ID",
"neuron_id": "Neuron ID",
- "numer_of_transactions": "Transactions",
"project": "Project Name",
+ "symbol": "Symbol",
+ "neuron_account_id": "Neuron Account ID",
+ "controller_id": "Controller Principal ID",
"stake": "Stake",
+ "available_maturity": "Available Maturity",
"staked_maturity": "Staked Maturity",
+ "dissolve_delay": "Dissolve Delay",
+ "dissolve_date": "Dissolve Date",
+ "creation_date": "Creation Date",
"state": "State",
- "symbol": "Symbol",
+ "account_id": "Account ID",
+ "account_name": "Account Name",
+ "amount": "Amount($tokenSymbol)",
+ "balance": "Balance($tokenSymbol)",
+ "from": "From",
+ "numer_of_transactions": "Transactions",
"timestamp": "Date Time",
"to": "To",
"transaction_id": "TX ID",
- "transaction_type": "TX Type"
- },
- "export_error": {
- "csv_generation": "Failed to generate CSV file",
- "file_system_access": "Unable to save file. Please check your permissions.",
- "neurons": "There was an error exporting the neurons. Please try again."
- },
- "reporting": {
- "neurons_title": "Neurons Portfolio",
- "neurons_description": "Download a detailed CSV report of your neuron portfolio, which includes the stake, maturity, and neuron account ID for manual tracking and analysis.",
- "neurons_download": "Export Neurons Portfolio",
- "transactions_title": "ICP Transactions",
- "transactions_description": "Download a detailed CSV report of your token transaction history, including both regular token accounts and neurons that will show neuron staking and minting transactions for manual tracking and analysis.",
- "transactions_download": "Export Transactions"
+ "transaction_type": "TX Type",
+ "error_csv_generation": "Failed to generate CSV file",
+ "error_file_system_access": "Unable to save file. Please check your permissions.",
+ "error_neurons": "There was an error exporting the neurons. Please try again.",
+ "error_transactions": "There was an error exporting the transactions. Please try again."
},
"auth": {
"login": "Sign in with Internet Identity",
@@ -388,7 +386,8 @@
"create_as_public_neuron_failure": "Making your neuron public has failed, so it was created as a private neuron. You can change neuron visibility at any time under \"Advanced Details & Settings\""
},
"losing_rewards": {
- "description": "ICP neurons that are inactive for $period start losing voting rewards. In order to avoid losing rewards, vote manually, edit or confirm your following.",
+ "description": "ICP neurons that are inactive for $period start missing voting rewards. To avoid missing rewards, vote manually, edit, or confirm your following.",
+ "confirming": "Confirming following. This may take a moment.",
"confirm": "Confirm Following"
},
"losing_rewards_banner": {
@@ -399,7 +398,7 @@
"goto_neuron": "Go to neuron details",
"title": "Review your following for neurons",
"label": "Neurons",
- "no_following": "This neuron has no following configured."
+ "no_following": "This neuron has no following configured. You need to vote manually to earn voting rewards."
},
"new_followee": {
"title": "Enter New Followee",
@@ -773,6 +772,7 @@
"reward_status_losing_soon_description": "$time to confirm following",
"reward_status_inactive": "Inactive neuron",
"reward_status_inactive_description": "Confirm following or vote manually to continue receiving rewards",
+ "reward_status_inactive_reset_description": "Following has been reset. Confirm following or vote manually to continue receiving rewards",
"neuron_state_tooltip": "Your neuron can be locked, unlocked or dissolving. In a locked state, it is accruing age bonus, while its dissolve delay stays constant. If the neuron is in a dissolving state, its age bonus is set to 0, while dissolve delay decreases with time. After dissolve delay reaches 0, the neuron is unlocked, and $token held in it can be sent to any $token account.",
"dissolve_delay_tooltip": "Dissolve delay is the minimum amount of time you have to wait for the neuron to unlock, and $token to be available again. If your neuron is dissolving, your $token will be available in $duration.",
"neuron_stake_refreshed": "Your neuron's stake was refreshed successfully.",
diff --git a/frontend/src/lib/services/export-data.services.ts b/frontend/src/lib/services/reporting.services.ts
similarity index 100%
rename from frontend/src/lib/services/export-data.services.ts
rename to frontend/src/lib/services/reporting.services.ts
diff --git a/frontend/src/lib/stores/busy.store.ts b/frontend/src/lib/stores/busy.store.ts
index df40d31fb26..d480afba808 100644
--- a/frontend/src/lib/stores/busy.store.ts
+++ b/frontend/src/lib/stores/busy.store.ts
@@ -51,6 +51,7 @@ export type BusyStateInitiatorType =
| "import-token-importing"
| "import-token-removing"
| "import-token-updating"
+ | "refresh-voting-power"
| "change-neuron-visibility"
| "reporting-neurons"
| "reporting-transactions";
diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts
index b5c50f9dc37..97a3cb2a116 100644
--- a/frontend/src/lib/types/i18n.d.ts
+++ b/frontend/src/lib/types/i18n.d.ts
@@ -165,46 +165,42 @@ interface I18nHeader {
account_id_tooltip: string;
}
-interface I18nExport_csv_neurons {
- account_id: string;
- account_id_label: string;
- account_name: string;
- amount: string;
- available_maturity: string;
- balance: string;
- controller_id: string;
- creation_date: string;
+interface I18nReporting {
+ neurons_title: string;
+ neurons_description: string;
+ neurons_download: string;
+ transactions_title: string;
+ transactions_description: string;
+ transactions_download: string;
+ busy_screen: string;
+ principal_account_id: string;
date_label: string;
- dissolve_date: string;
- dissolve_delay: string;
- from: string;
- neuron_account_id: string;
neuron_id: string;
- numer_of_transactions: string;
project: string;
+ symbol: string;
+ neuron_account_id: string;
+ controller_id: string;
stake: string;
+ available_maturity: string;
staked_maturity: string;
+ dissolve_delay: string;
+ dissolve_date: string;
+ creation_date: string;
state: string;
- symbol: string;
+ account_id: string;
+ account_name: string;
+ amount: string;
+ balance: string;
+ from: string;
+ numer_of_transactions: string;
timestamp: string;
to: string;
transaction_id: string;
transaction_type: string;
-}
-
-interface I18nExport_error {
- csv_generation: string;
- file_system_access: string;
- neurons: string;
-}
-
-interface I18nReporting {
- neurons_title: string;
- neurons_description: string;
- neurons_download: string;
- transactions_title: string;
- transactions_description: string;
- transactions_download: string;
+ error_csv_generation: string;
+ error_file_system_access: string;
+ error_neurons: string;
+ error_transactions: string;
}
interface I18nAuth {
@@ -405,6 +401,7 @@ interface I18nNeurons {
interface I18nLosing_rewards {
description: string;
+ confirming: string;
confirm: string;
}
@@ -805,6 +802,7 @@ interface I18nNeuron_detail {
reward_status_losing_soon_description: string;
reward_status_inactive: string;
reward_status_inactive_description: string;
+ reward_status_inactive_reset_description: string;
neuron_state_tooltip: string;
dissolve_delay_tooltip: string;
neuron_stake_refreshed: string;
@@ -1479,8 +1477,6 @@ interface I18n {
warning: I18nWarning;
navigation: I18nNavigation;
header: I18nHeader;
- export_csv_neurons: I18nExport_csv_neurons;
- export_error: I18nExport_error;
reporting: I18nReporting;
auth: I18nAuth;
accounts: I18nAccounts;
diff --git a/frontend/src/lib/types/staking.ts b/frontend/src/lib/types/staking.ts
index b902c86155a..354b53a34fc 100644
--- a/frontend/src/lib/types/staking.ts
+++ b/frontend/src/lib/types/staking.ts
@@ -11,6 +11,7 @@ export type TableProject = {
tokenSymbol: string;
neuronCount: number | undefined;
stake: TokenAmountV2 | UnavailableTokenAmount;
+ stakeInUsd: number | undefined;
availableMaturity: bigint | undefined;
stakedMaturity: bigint | undefined;
};
diff --git a/frontend/src/lib/utils/neuron.utils.ts b/frontend/src/lib/utils/neuron.utils.ts
index bc4710e1700..a6aa258b260 100644
--- a/frontend/src/lib/utils/neuron.utils.ts
+++ b/frontend/src/lib/utils/neuron.utils.ts
@@ -10,6 +10,7 @@ import {
E8S_PER_ICP,
} from "$lib/constants/icp.constants";
import {
+ CLEAR_FOLLOWING_AFTER_SECONDS,
MATURITY_MODULATION_VARIANCE_PERCENTAGE,
MAX_AGE_BONUS,
MAX_DISSOLVE_DELAY_BONUS,
@@ -1217,6 +1218,14 @@ export const secondsUntilLosingRewards = (neuron: NeuronInfo): number => {
return rewardLossStart - nowInSeconds();
};
+export const isNeuronFollowingReset = (neuron: NeuronInfo): boolean => {
+ const neuronFollowingResetTimestampSeconds =
+ Number(getVotingPowerRefreshedTimestampSeconds(neuron)) +
+ START_REDUCING_VOTING_POWER_AFTER_SECONDS +
+ CLEAR_FOLLOWING_AFTER_SECONDS;
+ return nowInSeconds() >= neuronFollowingResetTimestampSeconds;
+};
+
export const isNeuronLosingRewards = (neuron: NeuronInfo): boolean =>
secondsUntilLosingRewards(neuron) <= 0;
diff --git a/frontend/src/lib/utils/export-to-csv.utils.ts b/frontend/src/lib/utils/reporting.utils.ts
similarity index 95%
rename from frontend/src/lib/utils/export-to-csv.utils.ts
rename to frontend/src/lib/utils/reporting.utils.ts
index 5a40ca62595..8459291a5fd 100644
--- a/frontend/src/lib/utils/export-to-csv.utils.ts
+++ b/frontend/src/lib/utils/reporting.utils.ts
@@ -1,4 +1,4 @@
-import type { TransactionResults } from "$lib/services/export-data.services";
+import type { TransactionResults } from "$lib/services/reporting.services";
import {
getFutureDateFromDelayInSeconds,
nanoSecondsToDateTime,
@@ -304,27 +304,27 @@ export const buildTransactionsDatasets = ({
const metadata = [
{
- label: i18n.export_csv_neurons.account_id,
+ label: i18n.reporting.account_id,
value: accountIdentifier,
},
];
if (entity.type === "account") {
metadata.push({
- label: i18n.export_csv_neurons.account_name,
+ label: i18n.reporting.account_name,
value: entity.originalData.name ?? "Main",
});
}
if (entity.type === "neuron") {
metadata.push({
- label: i18n.export_csv_neurons.neuron_id,
+ label: i18n.reporting.neuron_id,
value: entity.originalData.neuronId.toString(),
});
}
metadata.push({
- label: replacePlaceholders(i18n.export_csv_neurons.balance, {
+ label: replacePlaceholders(i18n.reporting.balance, {
$tokenSymbol: ICPToken.symbol,
}),
value: formatTokenV2({
@@ -335,15 +335,15 @@ export const buildTransactionsDatasets = ({
metadata.push(
{
- label: i18n.export_csv_neurons.controller_id,
+ label: i18n.reporting.controller_id,
value: principal.toText() ?? i18n.core.not_applicable,
},
{
- label: i18n.export_csv_neurons.numer_of_transactions,
+ label: i18n.reporting.numer_of_transactions,
value: transactions.length.toString(),
},
{
- label: i18n.export_csv_neurons.date_label,
+ label: i18n.reporting.date_label,
value: nanoSecondsToDateTime(nowInBigIntNanoSeconds()),
}
);
@@ -413,11 +413,11 @@ export const buildNeuronsDatasets = ({
const metadataDate = nanoSecondsToDateTime(nowInBigIntNanoSeconds());
const metadata = [
{
- label: i18n.export_csv_neurons.account_id_label,
+ label: i18n.reporting.principal_account_id,
value: nnsAccountPrincipal.toText(),
},
{
- label: i18n.export_csv_neurons.date_label,
+ label: i18n.reporting.date_label,
value: metadataDate,
},
];
diff --git a/frontend/src/lib/utils/staking.utils.ts b/frontend/src/lib/utils/staking.utils.ts
index ca613a1314b..a73f5363fd0 100644
--- a/frontend/src/lib/utils/staking.utils.ts
+++ b/frontend/src/lib/utils/staking.utils.ts
@@ -1,4 +1,5 @@
import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
+import { type IcpSwapUsdPricesStoreData } from "$lib/derived/icp-swap.derived";
import type { TableProject } from "$lib/types/staking";
import type { Universe } from "$lib/types/universe";
import { buildNeuronsUrl } from "$lib/utils/navigation.utils";
@@ -19,7 +20,8 @@ import {
createDescendingComparator,
mergeComparators,
} from "$lib/utils/sort.utils";
-import { UnavailableTokenAmount } from "$lib/utils/token.utils";
+import { UnavailableTokenAmount, getUsdValue } from "$lib/utils/token.utils";
+import { getLedgerCanisterIdFromUniverse } from "$lib/utils/universe.utils";
import type { NeuronInfo } from "@dfinity/nns";
import type { SnsNeuron } from "@dfinity/sns";
import {
@@ -140,11 +142,13 @@ export const getTableProjects = ({
isSignedIn,
nnsNeurons,
snsNeurons,
+ icpSwapUsdPrices,
}: {
universes: Universe[];
isSignedIn: boolean;
nnsNeurons: NeuronInfo[] | undefined;
snsNeurons: { [rootCanisterId: string]: { neurons: SnsNeuron[] } };
+ icpSwapUsdPrices: IcpSwapUsdPricesStoreData;
}): TableProject[] => {
return universes.map((universe) => {
const token =
@@ -165,6 +169,18 @@ export const getTableProjects = ({
? buildNeuronsUrl({ universe: universe.canisterId })
: undefined;
const universeId = universe.canisterId;
+ const ledgerCanisterId = getLedgerCanisterIdFromUniverse(universe);
+ const tokenPrice =
+ isNullish(icpSwapUsdPrices) || icpSwapUsdPrices === "error"
+ ? undefined
+ : icpSwapUsdPrices[ledgerCanisterId.toText()];
+ const stakeInUsd =
+ stake instanceof TokenAmountV2
+ ? getUsdValue({
+ amount: stake,
+ tokenPrice,
+ })
+ : undefined;
return {
rowHref,
domKey: universeId,
@@ -174,6 +190,7 @@ export const getTableProjects = ({
tokenSymbol: token.symbol,
neuronCount,
stake,
+ stakeInUsd,
availableMaturity,
stakedMaturity,
};
diff --git a/frontend/src/lib/utils/token.utils.ts b/frontend/src/lib/utils/token.utils.ts
index 4b7b87dd524..b5b51120133 100644
--- a/frontend/src/lib/utils/token.utils.ts
+++ b/frontend/src/lib/utils/token.utils.ts
@@ -336,3 +336,20 @@ const getUsdBalance = (token: UserToken) => {
export const getTotalBalanceInUsd = (tokens: UserToken[]): number =>
tokens.reduce((acc, token) => acc + getUsdBalance(token), 0);
+
+export const getUsdValue = ({
+ amount,
+ tokenPrice,
+}: {
+ amount: TokenAmountV2;
+ tokenPrice?: number;
+}): number | undefined => {
+ const amountE8s = Number(amount.toE8s());
+ if (amountE8s === 0) {
+ return 0;
+ }
+ if (isNullish(tokenPrice)) {
+ return undefined;
+ }
+ return (amountE8s * tokenPrice) / 100_000_000;
+};
diff --git a/frontend/src/lib/utils/universe.utils.ts b/frontend/src/lib/utils/universe.utils.ts
index 814d46a00ac..d1d3a6c133b 100644
--- a/frontend/src/lib/utils/universe.utils.ts
+++ b/frontend/src/lib/utils/universe.utils.ts
@@ -1,4 +1,8 @@
-import { OWN_CANISTER_ID } from "$lib/constants/canister-ids.constants";
+import {
+ LEDGER_CANISTER_ID,
+ OWN_CANISTER_ID,
+ OWN_CANISTER_ID_TEXT,
+} from "$lib/constants/canister-ids.constants";
import {
CKBTC_UNIVERSE_CANISTER_ID,
CKTESTBTC_UNIVERSE_CANISTER_ID,
@@ -11,7 +15,7 @@ import type { SnsSummary } from "$lib/types/sns";
import type { Universe } from "$lib/types/universe";
import { replacePlaceholders } from "$lib/utils/i18n.utils";
import { isSelectedPath } from "$lib/utils/navigation.utils";
-import type { Principal } from "@dfinity/principal";
+import { Principal } from "@dfinity/principal";
import { nonNullish } from "@dfinity/utils";
import { get } from "svelte/store";
@@ -83,3 +87,15 @@ export const createUniverse = (summary: SnsSummary): Universe => ({
title: summary.metadata.name,
logo: summary.metadata.logo,
});
+
+export const getLedgerCanisterIdFromUniverse = (
+ universe: Universe
+): Principal => {
+ if (universe.canisterId === OWN_CANISTER_ID_TEXT) {
+ return LEDGER_CANISTER_ID;
+ }
+ if (nonNullish(universe.summary)) {
+ return universe.summary.ledgerCanisterId;
+ }
+ return Principal.fromText(universe.canisterId);
+};
diff --git a/frontend/src/tests/lib/components/ic/AmountWithUsd.spec.ts b/frontend/src/tests/lib/components/ic/AmountWithUsd.spec.ts
new file mode 100644
index 00000000000..8b520f76e55
--- /dev/null
+++ b/frontend/src/tests/lib/components/ic/AmountWithUsd.spec.ts
@@ -0,0 +1,47 @@
+import AmountWithUsd from "$lib/components/ic/AmountWithUsd.svelte";
+import { UnavailableTokenAmount } from "$lib/utils/token.utils";
+import { AmountWithUsdPo } from "$tests/page-objects/AmountWithUsd.page-object";
+import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
+import { ICPToken, TokenAmount } from "@dfinity/utils";
+import { render } from "@testing-library/svelte";
+
+describe("AmountWithUsd", () => {
+ const tokenAmount = TokenAmount.fromE8s({
+ amount: 123_456_789_010_000n,
+ token: ICPToken,
+ });
+
+ const renderComponent = (props) => {
+ const { container } = render(AmountWithUsd, props);
+ return AmountWithUsdPo.under(new JestPageObjectElement(container));
+ };
+
+ it("should render a token amount", async () => {
+ const po = renderComponent({ amount: tokenAmount, amountInUsd: undefined });
+ expect(await po.getAmountDisplayPo().getText()).toEqual("1'234'567.89 ICP");
+ });
+
+ it("should render an unavailable token amount", async () => {
+ const po = renderComponent({
+ amount: new UnavailableTokenAmount(ICPToken),
+ amountInUsd: undefined,
+ });
+ expect(await po.getAmountDisplayPo().getText()).toEqual("-/- ICP");
+ });
+
+ it("should render an amount in USD", async () => {
+ const po = renderComponent({
+ amount: tokenAmount,
+ amountInUsd: "12345.678",
+ });
+ expect(await po.getAmountInUsd()).toEqual("$12’345.68");
+ });
+
+ it("should render an absent amount in USD", async () => {
+ const po = renderComponent({
+ amount: tokenAmount,
+ amountInUsd: undefined,
+ });
+ expect(await po.getAmountInUsd()).toEqual("$-/-");
+ });
+});
diff --git a/frontend/src/tests/lib/components/neuron-detail/NnsNeuronRewardStatusAction.spec.ts b/frontend/src/tests/lib/components/neuron-detail/NnsNeuronRewardStatusAction.spec.ts
index 623bff8f5c1..e738445ad56 100644
--- a/frontend/src/tests/lib/components/neuron-detail/NnsNeuronRewardStatusAction.spec.ts
+++ b/frontend/src/tests/lib/components/neuron-detail/NnsNeuronRewardStatusAction.spec.ts
@@ -1,15 +1,19 @@
-import NnsNeuronRewardStatusAction from "$lib/components/neuron-detail/NnsNeuronRewardStatusAction.svelte";
-import { SECONDS_IN_DAY, SECONDS_IN_HALF_YEAR } from "$lib/constants/constants";
+import {
+ SECONDS_IN_DAY,
+ SECONDS_IN_HALF_YEAR,
+ SECONDS_IN_MONTH,
+} from "$lib/constants/constants";
import { nowInSeconds } from "$lib/utils/date.utils";
import { mockFullNeuron, mockNeuron } from "$tests/mocks/neurons.mock";
import { NnsNeuronRewardStatusActionPo } from "$tests/page-objects/NnsNeuronRewardStatusAction.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
import { type NeuronInfo } from "@dfinity/nns";
import { render } from "@testing-library/svelte";
+import NnsNeuronRewardStatusActionTest from "./NnsNeuronRewardStatusActionTest.svelte";
describe("NnsNeuronRewardStatusAction", () => {
const renderComponent = (neuron: NeuronInfo) => {
- const { container } = render(NnsNeuronRewardStatusAction, {
+ const { container } = render(NnsNeuronRewardStatusActionTest, {
props: {
neuron,
},
@@ -38,6 +42,7 @@ describe("NnsNeuronRewardStatusAction", () => {
expect(await po.getDescription()).toBe(
"182 days, 15 hours to confirm following"
);
+ expect(await po.getFollowNeuronsButtonPo().isPresent()).toBe(false);
});
it("should render losing soon neuron state", async () => {
@@ -57,9 +62,10 @@ describe("NnsNeuronRewardStatusAction", () => {
expect(await po.getDescription()).toBe(
`${tenDays} days to confirm following`
);
+ expect(await po.getFollowNeuronsButtonPo().isPresent()).toBe(false);
});
- it("should render active neuron reward state", async () => {
+ it("should render inactive neuron reward state", async () => {
const testNeuron = {
...mockNeuron,
fullNeuron: {
@@ -75,5 +81,25 @@ describe("NnsNeuronRewardStatusAction", () => {
expect(await po.getDescription()).toBe(
"Confirm following or vote manually to continue receiving rewards"
);
+ expect(await po.getFollowNeuronsButtonPo().isPresent()).toBe(false);
+ });
+
+ it("should render inactive/reset following neuron reward state", async () => {
+ const testNeuron = {
+ ...mockNeuron,
+ fullNeuron: {
+ ...mockFullNeuron,
+ votingPowerRefreshedTimestampSeconds: BigInt(
+ nowInSeconds() - SECONDS_IN_HALF_YEAR - SECONDS_IN_MONTH
+ ),
+ },
+ };
+ const po = renderComponent(testNeuron);
+
+ expect(await po.getTitle()).toBe("Inactive neuron");
+ expect(await po.getDescription()).toBe(
+ "Following has been reset. Confirm following or vote manually to continue receiving rewards"
+ );
+ expect(await po.getFollowNeuronsButtonPo().isPresent()).toBe(true);
});
});
diff --git a/frontend/src/tests/lib/components/neuron-detail/NnsNeuronRewardStatusActionTest.svelte b/frontend/src/tests/lib/components/neuron-detail/NnsNeuronRewardStatusActionTest.svelte
new file mode 100644
index 00000000000..4d344094e48
--- /dev/null
+++ b/frontend/src/tests/lib/components/neuron-detail/NnsNeuronRewardStatusActionTest.svelte
@@ -0,0 +1,23 @@
+
+
+
diff --git a/frontend/src/tests/lib/components/neuron-detail/actions/ConfirmFollowingButton.spec.ts b/frontend/src/tests/lib/components/neuron-detail/actions/ConfirmFollowingButton.spec.ts
new file mode 100644
index 00000000000..30823adec8b
--- /dev/null
+++ b/frontend/src/tests/lib/components/neuron-detail/actions/ConfirmFollowingButton.spec.ts
@@ -0,0 +1,109 @@
+import * as api from "$lib/api/governance.api";
+import ConfirmFollowingButton from "$lib/components/neuron-detail/actions/ConfirmFollowingButton.svelte";
+import { neuronsStore } from "$lib/stores/neurons.store";
+import { mockIdentity, resetIdentity } from "$tests/mocks/auth.store.mock";
+import { mockFullNeuron, mockNeuron } from "$tests/mocks/neurons.mock";
+import { ConfirmFollowingButtonPo } from "$tests/page-objects/ConfirmFollowingButton.page-object";
+import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
+import { allowLoggingInOneTestForDebugging } from "$tests/utils/console.test-utils";
+import { runResolvedPromises } from "$tests/utils/timers.test-utils";
+import { busyStore } from "@dfinity/gix-components";
+import { nonNullish } from "@dfinity/utils";
+import { render } from "@testing-library/svelte";
+import { get } from "svelte/store";
+
+describe("ConfirmFollowingButton", () => {
+ const neuronId1 = 1n;
+ const neuronId2 = 22n;
+ const neuronIds = [neuronId1, neuronId2];
+ const neurons = neuronIds.map((neuronId) => ({
+ ...mockNeuron,
+ neuronId,
+ fullNeuron: {
+ ...mockFullNeuron,
+ neuronId,
+ controller: mockIdentity.getPrincipal().toText(),
+ },
+ }));
+
+ let spyQueryNeurons;
+
+ beforeEach(() => {
+ resetIdentity();
+ allowLoggingInOneTestForDebugging();
+
+ neuronsStore.pushNeurons({ neurons, certified: true });
+ spyQueryNeurons = vi.spyOn(api, "queryNeurons").mockResolvedValue(neurons);
+ vi.spyOn(api, "refreshVotingPower").mockResolvedValue();
+ });
+
+ const renderComponent = async ({ nnsComplete = undefined, ...props }) => {
+ const { container, component } = render(ConfirmFollowingButton, props);
+
+ if (nonNullish(nnsComplete)) component.$on("nnsComplete", nnsComplete);
+
+ return ConfirmFollowingButtonPo.under(new JestPageObjectElement(container));
+ };
+
+ it("displays the button", async () => {
+ const po = await renderComponent({
+ neuronIds: [neuronId1, neuronId2],
+ });
+
+ expect(await po.getText()).toBe("Confirm Following");
+ });
+
+ it("returns the refreshed neuron count", async () => {
+ vi.spyOn(console, "error").mockReturnValue();
+
+ neuronsStore.pushNeurons({ neurons, certified: true });
+ let resolveRefreshVotingPower;
+ const spyRefreshVotingPower = vi
+ .spyOn(api, "refreshVotingPower")
+ .mockRejectedValueOnce(new Error())
+ .mockImplementationOnce(
+ () => new Promise((resolve) => (resolveRefreshVotingPower = resolve))
+ );
+
+ expect(spyQueryNeurons).toBeCalledTimes(0);
+ expect(spyRefreshVotingPower).toBeCalledTimes(0);
+
+ const nnsComplete = vi.fn();
+ const po = await renderComponent({
+ neuronIds: [neuronId1, neuronId2],
+ nnsComplete,
+ });
+
+ expect(get(busyStore)).toEqual([]);
+
+ await po.click();
+
+ // Wait for busy screen to show
+ await runResolvedPromises();
+ expect(get(busyStore)).toEqual([
+ {
+ initiator: "refresh-voting-power",
+ text: "Confirming following. This may take a moment.",
+ },
+ ]);
+
+ resolveRefreshVotingPower();
+
+ // Wait for busy screen to hide
+ await runResolvedPromises();
+ expect(get(busyStore)).toEqual([]);
+
+ expect(spyQueryNeurons).toBeCalledTimes(2);
+ expect(spyRefreshVotingPower).toBeCalledTimes(2);
+
+ expect(nnsComplete).toBeCalledTimes(1);
+ expect(nnsComplete).toBeCalledWith(
+ expect.objectContaining({
+ detail: {
+ successCount: 1,
+ totalCount: 2,
+ },
+ })
+ );
+ });
+});
diff --git a/frontend/src/tests/lib/components/neurons/LosingRewardsBanner.spec.ts b/frontend/src/tests/lib/components/neurons/LosingRewardsBanner.spec.ts
index 124e4c460c9..16411d5e0d8 100644
--- a/frontend/src/tests/lib/components/neurons/LosingRewardsBanner.spec.ts
+++ b/frontend/src/tests/lib/components/neurons/LosingRewardsBanner.spec.ts
@@ -94,7 +94,7 @@ describe("LosingRewardsBanner", () => {
const po = await renderComponent();
expect(await po.getText()).toBe(
- "ICP neurons that are inactive for 6 months start losing voting rewards. In order to avoid losing rewards, vote manually, edit or confirm your following."
+ "ICP neurons that are inactive for 6 months start missing voting rewards. To avoid missing rewards, vote manually, edit, or confirm your following."
);
});
diff --git a/frontend/src/tests/lib/components/reporting/ReportingNeuronsButton.spec.ts b/frontend/src/tests/lib/components/reporting/ReportingNeuronsButton.spec.ts
index dc45125a260..239a2cdede9 100644
--- a/frontend/src/tests/lib/components/reporting/ReportingNeuronsButton.spec.ts
+++ b/frontend/src/tests/lib/components/reporting/ReportingNeuronsButton.spec.ts
@@ -2,9 +2,8 @@ import * as gobernanceApi from "$lib/api/governance.api";
import ReportingNeuronsButton from "$lib/components/reporting/ReportingNeuronsButton.svelte";
import * as toastsStore from "$lib/stores/toasts.store";
import { toastsError } from "$lib/stores/toasts.store";
-import * as exportToCsv from "$lib/utils/export-to-csv.utils";
-import * as exportToCsvUtils from "$lib/utils/export-to-csv.utils";
-import { generateCsvFileToSave } from "$lib/utils/export-to-csv.utils";
+import * as exportToCsvUtils from "$lib/utils/reporting.utils";
+import { generateCsvFileToSave } from "$lib/utils/reporting.utils";
import { resetIdentity } from "$tests/mocks/auth.store.mock";
import { mockNeuron } from "$tests/mocks/neurons.mock";
import { ReportingNeuronsButtonPo } from "$tests/page-objects/ReportingNeuronsButon.page-object";
@@ -25,7 +24,9 @@ describe("ReportingNeuronsButton", () => {
vi.clearAllTimers();
resetIdentity();
- vi.spyOn(exportToCsv, "generateCsvFileToSave").mockImplementation(vi.fn());
+ vi.spyOn(exportToCsvUtils, "generateCsvFileToSave").mockImplementation(
+ vi.fn()
+ );
vi.spyOn(toastsStore, "toastsError");
vi.spyOn(console, "error").mockImplementation(() => {});
spyBuildNeuronsDatasets = vi.spyOn(
@@ -158,8 +159,8 @@ describe("ReportingNeuronsButton", () => {
});
it("should show error toast when file system access fails", async () => {
- vi.spyOn(exportToCsv, "generateCsvFileToSave").mockRejectedValueOnce(
- new exportToCsv.FileSystemAccessError("File system access denied")
+ vi.spyOn(exportToCsvUtils, "generateCsvFileToSave").mockRejectedValueOnce(
+ new exportToCsvUtils.FileSystemAccessError("File system access denied")
);
const po = renderComponent();
@@ -170,14 +171,14 @@ describe("ReportingNeuronsButton", () => {
await runResolvedPromises();
expect(toastsError).toBeCalledWith({
- labelKey: "export_error.file_system_access",
+ labelKey: "reporting.error_file_system_access",
});
expect(toastsError).toBeCalledTimes(1);
});
it("should show error toast when Csv generation fails", async () => {
- vi.spyOn(exportToCsv, "generateCsvFileToSave").mockRejectedValueOnce(
- new exportToCsv.CsvGenerationError("Csv generation failed")
+ vi.spyOn(exportToCsvUtils, "generateCsvFileToSave").mockRejectedValueOnce(
+ new exportToCsvUtils.CsvGenerationError("Csv generation failed")
);
const po = renderComponent();
@@ -188,13 +189,13 @@ describe("ReportingNeuronsButton", () => {
await runResolvedPromises();
expect(toastsError).toBeCalledWith({
- labelKey: "export_error.csv_generation",
+ labelKey: "reporting.error_csv_generation",
});
expect(toastsError).toBeCalledTimes(1);
});
it("should show error toast when file saving fails", async () => {
- vi.spyOn(exportToCsv, "generateCsvFileToSave").mockRejectedValueOnce(
+ vi.spyOn(exportToCsvUtils, "generateCsvFileToSave").mockRejectedValueOnce(
new Error("Something wrong happened")
);
@@ -206,7 +207,7 @@ describe("ReportingNeuronsButton", () => {
await runResolvedPromises();
expect(toastsError).toBeCalledWith({
- labelKey: "export_error.neurons",
+ labelKey: "reporting.error_neurons",
});
expect(toastsError).toBeCalledTimes(1);
});
@@ -234,7 +235,7 @@ describe("ReportingNeuronsButton", () => {
expect(get(busyStore)).toEqual([
{
initiator: "reporting-neurons",
- text: undefined,
+ text: "Generating report... This may take a moment",
},
]);
diff --git a/frontend/src/tests/lib/components/reporting/ReportingTransactionsButton.spec.ts b/frontend/src/tests/lib/components/reporting/ReportingTransactionsButton.spec.ts
index dfe6965dc7b..1375582d22f 100644
--- a/frontend/src/tests/lib/components/reporting/ReportingTransactionsButton.spec.ts
+++ b/frontend/src/tests/lib/components/reporting/ReportingTransactionsButton.spec.ts
@@ -1,9 +1,9 @@
import * as governanceApi from "$lib/api/governance.api";
import * as icpIndexApi from "$lib/api/icp-index.api";
import ReportingTransactionsButton from "$lib/components/reporting/ReportingTransactionsButton.svelte";
-import * as exportDataService from "$lib/services/export-data.services";
+import * as exportDataService from "$lib/services/reporting.services";
import * as toastsStore from "$lib/stores/toasts.store";
-import * as exportToCsv from "$lib/utils/export-to-csv.utils";
+import * as exportToCsv from "$lib/utils/reporting.utils";
import { mockIdentity, resetIdentity } from "$tests/mocks/auth.store.mock";
import {
mockAccountsStoreData,
@@ -242,7 +242,7 @@ describe("ReportingTransactionsButton", () => {
await runResolvedPromises();
expect(spyToastError).toBeCalledWith({
- labelKey: "export_error.file_system_access",
+ labelKey: "reporting.error_file_system_access",
});
expect(spyToastError).toBeCalledTimes(1);
});
@@ -260,7 +260,7 @@ describe("ReportingTransactionsButton", () => {
await runResolvedPromises();
expect(spyToastError).toBeCalledWith({
- labelKey: "export_error.csv_generation",
+ labelKey: "reporting.error_csv_generation",
});
expect(spyToastError).toBeCalledTimes(1);
});
@@ -277,7 +277,7 @@ describe("ReportingTransactionsButton", () => {
await runResolvedPromises();
expect(spyToastError).toBeCalledWith({
- labelKey: "export_error.neurons",
+ labelKey: "reporting.error_transactions",
});
expect(spyToastError).toBeCalledTimes(1);
});
@@ -305,7 +305,7 @@ describe("ReportingTransactionsButton", () => {
expect(get(busyStore)).toEqual([
{
initiator: "reporting-transactions",
- text: undefined,
+ text: "Generating report... This may take a moment",
},
]);
diff --git a/frontend/src/tests/lib/components/staking/ProjectsTable.spec.ts b/frontend/src/tests/lib/components/staking/ProjectsTable.spec.ts
index e13161f4ad6..e99182ed116 100644
--- a/frontend/src/tests/lib/components/staking/ProjectsTable.spec.ts
+++ b/frontend/src/tests/lib/components/staking/ProjectsTable.spec.ts
@@ -1,10 +1,15 @@
+import * as icpSwapApi from "$lib/api/icp-swap.api";
import ProjectsTable from "$lib/components/staking/ProjectsTable.svelte";
import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
+import { CKUSDC_UNIVERSE_CANISTER_ID } from "$lib/constants/ckusdc-canister-ids.constants";
import { AppPath } from "$lib/constants/routes.constants";
+import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
+import { icpSwapTickersStore } from "$lib/stores/icp-swap.store";
import { neuronsStore } from "$lib/stores/neurons.store";
import { snsNeuronsStore } from "$lib/stores/sns-neurons.store";
import { page } from "$mocks/$app/stores";
import { resetIdentity, setNoIdentity } from "$tests/mocks/auth.store.mock";
+import { mockIcpSwapTicker } from "$tests/mocks/icp-swap.mock";
import { mockNeuron } from "$tests/mocks/neurons.mock";
import { createMockSnsNeuron } from "$tests/mocks/sns-neurons.mock";
import { mockSnsToken, principal } from "$tests/mocks/sns-projects.mock";
@@ -14,6 +19,7 @@ import { resetSnsProjects, setSnsProjects } from "$tests/utils/sns.test-utils";
import { render } from "$tests/utils/svelte.test-utils";
import { runResolvedPromises } from "$tests/utils/timers.test-utils";
import { nonNullish } from "@dfinity/utils";
+import { get } from "svelte/store";
describe("ProjectsTable", () => {
const snsTitle = "SNS-1";
@@ -444,6 +450,87 @@ describe("ProjectsTable", () => {
await rowPos[1].click();
expect(onNnsStakeTokens).not.toBeCalled();
});
+
+ it("should display stake in USD", async () => {
+ overrideFeatureFlagsStore.setFlag("ENABLE_USD_VALUES_FOR_NEURONS", true);
+
+ const tickers = [
+ {
+ ...mockIcpSwapTicker,
+ base_id: CKUSDC_UNIVERSE_CANISTER_ID.toText(),
+ last_price: "10.00",
+ },
+ ];
+ vi.spyOn(icpSwapApi, "queryIcpSwapTickers").mockResolvedValue(tickers);
+
+ neuronsStore.setNeurons({
+ neurons: [nnsNeuronWithStake],
+ certified: true,
+ });
+
+ expect(get(icpSwapTickersStore)).toBeUndefined();
+ expect(icpSwapApi.queryIcpSwapTickers).toBeCalledTimes(0);
+
+ const po = renderComponent();
+ await runResolvedPromises();
+
+ expect(get(icpSwapTickersStore)).toEqual(tickers);
+ expect(icpSwapApi.queryIcpSwapTickers).toBeCalledTimes(1);
+
+ const rowPos = await po.getProjectsTableRowPos();
+ expect(await rowPos[0].getStake()).toBe("1.00 ICP");
+ expect(await rowPos[0].getStakeInUsd()).toBe("$10.00");
+ expect(await rowPos[0].hasStakeInUsd()).toBe(true);
+ });
+
+ it("should not load ICP Swap tickers without feature flag", async () => {
+ overrideFeatureFlagsStore.setFlag("ENABLE_USD_VALUES_FOR_NEURONS", false);
+
+ vi.spyOn(icpSwapApi, "queryIcpSwapTickers").mockResolvedValue([]);
+
+ neuronsStore.setNeurons({
+ neurons: [nnsNeuronWithStake],
+ certified: true,
+ });
+
+ expect(get(icpSwapTickersStore)).toBeUndefined();
+ expect(icpSwapApi.queryIcpSwapTickers).toBeCalledTimes(0);
+
+ const po = renderComponent();
+ await runResolvedPromises();
+
+ expect(get(icpSwapTickersStore)).toBeUndefined();
+ expect(icpSwapApi.queryIcpSwapTickers).toBeCalledTimes(0);
+
+ const rowPos = await po.getProjectsTableRowPos();
+ expect(await rowPos[0].getStake()).toBe("1.00 ICP");
+ expect(await rowPos[0].hasStakeInUsd()).toBe(false);
+ });
+
+ it("should not load ICP Swap tickers when not signed in", async () => {
+ setNoIdentity();
+ overrideFeatureFlagsStore.setFlag("ENABLE_USD_VALUES_FOR_NEURONS", true);
+
+ vi.spyOn(icpSwapApi, "queryIcpSwapTickers").mockResolvedValue([]);
+
+ neuronsStore.setNeurons({
+ neurons: [nnsNeuronWithStake],
+ certified: true,
+ });
+
+ expect(get(icpSwapTickersStore)).toBeUndefined();
+ expect(icpSwapApi.queryIcpSwapTickers).toBeCalledTimes(0);
+
+ const po = renderComponent();
+ await runResolvedPromises();
+
+ expect(get(icpSwapTickersStore)).toBeUndefined();
+ expect(icpSwapApi.queryIcpSwapTickers).toBeCalledTimes(0);
+
+ const rowPos = await po.getProjectsTableRowPos();
+ expect(await rowPos[0].getStake()).toBe("-/- ICP");
+ expect(await rowPos[0].getStakeInUsd()).toBe("$-/-");
+ });
});
it("should update table when universes store changes", async () => {
diff --git a/frontend/src/tests/lib/services/export-data.services.spec.ts b/frontend/src/tests/lib/services/reporting.services.spec.ts
similarity index 99%
rename from frontend/src/tests/lib/services/export-data.services.spec.ts
rename to frontend/src/tests/lib/services/reporting.services.spec.ts
index 9fd52874452..8c48f147d0c 100644
--- a/frontend/src/tests/lib/services/export-data.services.spec.ts
+++ b/frontend/src/tests/lib/services/reporting.services.spec.ts
@@ -3,7 +3,7 @@ import {
getAccountTransactionsConcurrently,
getAllTransactionsFromAccountAndIdentity,
mapAccountOrNeuronToTransactionEntity,
-} from "$lib/services/export-data.services";
+} from "$lib/services/reporting.services";
import { mockSignInIdentity } from "$tests/mocks/auth.store.mock";
import {
mockMainAccount,
@@ -18,7 +18,7 @@ import type { SignIdentity } from "@dfinity/agent";
vi.mock("$lib/api/icp-ledger.api");
-describe("export-data service", () => {
+describe("reporting service", () => {
const mockAccountId = "test-account-id";
let spyGetTransactions;
diff --git a/frontend/src/tests/lib/utils/neuron.utils.spec.ts b/frontend/src/tests/lib/utils/neuron.utils.spec.ts
index 01eaec4e1f6..a61069968ff 100644
--- a/frontend/src/tests/lib/utils/neuron.utils.spec.ts
+++ b/frontend/src/tests/lib/utils/neuron.utils.spec.ts
@@ -4,6 +4,7 @@ import {
SECONDS_IN_FOUR_YEARS,
SECONDS_IN_HALF_YEAR,
SECONDS_IN_HOUR,
+ SECONDS_IN_MONTH,
SECONDS_IN_YEAR,
} from "$lib/constants/constants";
import { DEFAULT_TRANSACTION_FEE_E8S } from "$lib/constants/icp.constants";
@@ -51,6 +52,7 @@ import {
isNeuronControllable,
isNeuronControllableByUser,
isNeuronControlledByHardwareWallet,
+ isNeuronFollowingReset,
isNeuronLosingRewards,
isPublicNeuron,
isSpawning,
@@ -3461,6 +3463,47 @@ describe("neuron-utils", () => {
});
});
+ describe("isNeuronFollowingReset", () => {
+ it("should return false by default", () => {
+ expect(
+ isNeuronFollowingReset({
+ ...mockNeuron,
+ fullNeuron: undefined,
+ })
+ ).toBe(false);
+ });
+
+ it("should return true after the followings have been reset", () => {
+ expect(
+ isNeuronFollowingReset(
+ neuronWithRefreshedTimestamp({
+ votingPowerRefreshedTimestampAgeSecs:
+ losingRewardsPeriod + SECONDS_IN_MONTH,
+ })
+ )
+ ).toBe(true);
+ expect(
+ isNeuronFollowingReset(
+ neuronWithRefreshedTimestamp({
+ votingPowerRefreshedTimestampAgeSecs:
+ losingRewardsPeriod + 2 * SECONDS_IN_MONTH,
+ })
+ )
+ ).toBe(true);
+ });
+
+ it("should return false", () => {
+ expect(
+ isNeuronFollowingReset(
+ neuronWithRefreshedTimestamp({
+ votingPowerRefreshedTimestampAgeSecs:
+ losingRewardsPeriod + SECONDS_IN_MONTH - 1,
+ })
+ )
+ ).toBe(false);
+ });
+ });
+
describe("shouldDisplayRewardLossNotification", () => {
it("should return false by default", () => {
expect(
diff --git a/frontend/src/tests/lib/utils/export-to-csv.utils.spec.ts b/frontend/src/tests/lib/utils/reporting.utils.spec.ts
similarity index 99%
rename from frontend/src/tests/lib/utils/export-to-csv.utils.spec.ts
rename to frontend/src/tests/lib/utils/reporting.utils.spec.ts
index 3f6de8510d2..b332d1accd9 100644
--- a/frontend/src/tests/lib/utils/export-to-csv.utils.spec.ts
+++ b/frontend/src/tests/lib/utils/reporting.utils.spec.ts
@@ -6,7 +6,7 @@ import {
convertToCsv,
generateCsvFileToSave,
type CsvHeader,
-} from "$lib/utils/export-to-csv.utils";
+} from "$lib/utils/reporting.utils";
import { mockPrincipal } from "$tests/mocks/auth.store.mock";
import en from "$tests/mocks/i18n.mock";
import { mockMainAccount } from "$tests/mocks/icp-accounts.store.mock";
@@ -17,7 +17,7 @@ import { NeuronState, type NeuronInfo } from "@dfinity/nns";
type TestPersonData = { name: string; age: number };
type TestFormulaData = { formula: string; value: number };
-describe("Export to Csv", () => {
+describe("reporting utils", () => {
beforeEach(() => {
const mockDate = new Date("2023-10-14T00:00:00Z");
vi.useFakeTimers();
diff --git a/frontend/src/tests/lib/utils/staking.utils.spec.ts b/frontend/src/tests/lib/utils/staking.utils.spec.ts
index c59aebbb83a..30c7aaa5964 100644
--- a/frontend/src/tests/lib/utils/staking.utils.spec.ts
+++ b/frontend/src/tests/lib/utils/staking.utils.spec.ts
@@ -1,5 +1,8 @@
import IC_LOGO_ROUNDED from "$lib/assets/icp-rounded.svg";
-import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
+import {
+ LEDGER_CANISTER_ID,
+ OWN_CANISTER_ID_TEXT,
+} from "$lib/constants/canister-ids.constants";
import type { Universe } from "$lib/types/universe";
import { getTableProjects, sortTableProjects } from "$lib/utils/staking.utils";
import { UnavailableTokenAmount } from "$lib/utils/token.utils";
@@ -52,6 +55,7 @@ describe("staking.utils", () => {
amount: 0n,
token: ICPToken,
}),
+ stakeInUsd: 0.0,
tokenSymbol: "ICP",
availableMaturity: 0n,
stakedMaturity: 0n,
@@ -68,6 +72,7 @@ describe("staking.utils", () => {
amount: 0n,
token: snsToken,
}),
+ stakeInUsd: 0.0,
tokenSymbol: snsTokenSymbol,
availableMaturity: 0n,
stakedMaturity: 0n,
@@ -117,6 +122,7 @@ describe("staking.utils", () => {
snsNeurons: {
[universeId2]: { neurons: [] },
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -141,6 +147,7 @@ describe("staking.utils", () => {
snsNeurons: {
[snsUniverseId]: { neurons: [] },
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -174,6 +181,7 @@ describe("staking.utils", () => {
snsNeurons: {
[universeId2]: { neurons: [] },
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -198,6 +206,7 @@ describe("staking.utils", () => {
nnsNeuronWithStake,
],
snsNeurons: {},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -209,6 +218,7 @@ describe("staking.utils", () => {
amount: 3n * nnsNeuronWithStake.fullNeuron.cachedNeuronStake,
token: ICPToken,
}),
+ stakeInUsd: undefined,
},
]);
});
@@ -237,6 +247,7 @@ describe("staking.utils", () => {
isSignedIn: true,
nnsNeurons: [neuron1, neuron2],
snsNeurons: {},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -276,6 +287,7 @@ describe("staking.utils", () => {
isSignedIn: true,
nnsNeurons: [neuron1, neuron2],
snsNeurons: {},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -287,6 +299,7 @@ describe("staking.utils", () => {
amount: 2n * stake,
token: ICPToken,
}),
+ stakeInUsd: undefined,
stakedMaturity: maturity1 + maturity2,
},
]);
@@ -302,6 +315,7 @@ describe("staking.utils", () => {
neurons: [snsNeuronWithStake, snsNeuronWithStake],
},
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -313,6 +327,7 @@ describe("staking.utils", () => {
amount: 2n * snsNeuronWithStake.cached_neuron_stake_e8s,
token: snsToken,
}),
+ stakeInUsd: undefined,
},
]);
});
@@ -341,6 +356,7 @@ describe("staking.utils", () => {
neurons: [neuron1, neuron2],
},
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -378,6 +394,7 @@ describe("staking.utils", () => {
neurons: [neuron1, neuron2],
},
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -389,11 +406,87 @@ describe("staking.utils", () => {
amount: 2n * stake,
token: snsToken,
}),
+ stakeInUsd: undefined,
stakedMaturity: maturity1 + maturity2,
},
]);
});
+ it("should have stake in USD for NNS neuron", () => {
+ const stake = 200_000_000n;
+ const icpPrice = 10.0;
+ const expectedStakeInUsd = 20.0;
+
+ const nnsNeuron = {
+ ...nnsNeuronWithStake,
+ fullNeuron: {
+ ...nnsNeuronWithStake.fullNeuron,
+ cachedNeuronStake: stake,
+ },
+ };
+ const tableProjects = getTableProjects({
+ universes: [nnsUniverse],
+ isSignedIn: true,
+ nnsNeurons: [nnsNeuron],
+ snsNeurons: {},
+ icpSwapUsdPrices: {
+ [LEDGER_CANISTER_ID.toText()]: icpPrice,
+ },
+ });
+
+ expect(tableProjects).toEqual([
+ {
+ ...defaultExpectedNnsTableProject,
+ rowHref: nnsHref,
+ neuronCount: 1,
+ stake: TokenAmountV2.fromUlps({
+ amount: stake,
+ token: ICPToken,
+ }),
+ stakeInUsd: expectedStakeInUsd,
+ },
+ ]);
+ });
+
+ it("should have stake in USD for SNS neuron", () => {
+ const stake = 300_000_000n;
+ const tokenPrice = 0.1;
+ const expectedStakeInUsd = 0.3;
+
+ const snsNeuron = createMockSnsNeuron({
+ stake: stake,
+ maturity: 0n,
+ stakedMaturity: 0n,
+ id: [1],
+ });
+ const tableProjects = getTableProjects({
+ universes: [snsUniverse],
+ isSignedIn: true,
+ nnsNeurons: [],
+ snsNeurons: {
+ [universeId2]: {
+ neurons: [snsNeuron],
+ },
+ },
+ icpSwapUsdPrices: {
+ [snsUniverse.summary.ledgerCanisterId.toText()]: tokenPrice,
+ },
+ });
+
+ expect(tableProjects).toEqual([
+ {
+ ...defaultExpectedSnsTableProject,
+ rowHref: snsHref,
+ neuronCount: 1,
+ stake: TokenAmountV2.fromUlps({
+ amount: stake,
+ token: snsToken,
+ }),
+ stakeInUsd: expectedStakeInUsd,
+ },
+ ]);
+ });
+
it("should filter NNS neurons without stake", () => {
const tableProjects = getTableProjects({
universes: [nnsUniverse],
@@ -404,6 +497,7 @@ describe("staking.utils", () => {
nnsNeuronWithoutStake,
],
snsNeurons: {},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -415,6 +509,7 @@ describe("staking.utils", () => {
amount: nnsNeuronWithStake.fullNeuron.cachedNeuronStake,
token: ICPToken,
}),
+ stakeInUsd: undefined,
},
]);
});
@@ -434,6 +529,7 @@ describe("staking.utils", () => {
],
},
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -445,6 +541,7 @@ describe("staking.utils", () => {
amount: snsNeuronWithStake.cached_neuron_stake_e8s,
token: snsToken,
}),
+ stakeInUsd: undefined,
},
]);
});
@@ -459,6 +556,7 @@ describe("staking.utils", () => {
neurons: [],
},
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -466,6 +564,7 @@ describe("staking.utils", () => {
...defaultExpectedNnsTableProject,
neuronCount: undefined,
stake: new UnavailableTokenAmount(ICPToken),
+ stakeInUsd: undefined,
availableMaturity: undefined,
stakedMaturity: undefined,
},
@@ -473,6 +572,7 @@ describe("staking.utils", () => {
...defaultExpectedSnsTableProject,
neuronCount: undefined,
stake: new UnavailableTokenAmount(snsToken),
+ stakeInUsd: undefined,
availableMaturity: undefined,
stakedMaturity: undefined,
},
@@ -487,6 +587,7 @@ describe("staking.utils", () => {
snsNeurons: {
[universeId2]: undefined,
},
+ icpSwapUsdPrices: undefined,
});
expect(tableProjects).toEqual([
@@ -494,6 +595,7 @@ describe("staking.utils", () => {
...defaultExpectedNnsTableProject,
neuronCount: undefined,
stake: new UnavailableTokenAmount(ICPToken),
+ stakeInUsd: undefined,
availableMaturity: undefined,
stakedMaturity: undefined,
},
@@ -501,6 +603,7 @@ describe("staking.utils", () => {
...defaultExpectedSnsTableProject,
neuronCount: undefined,
stake: new UnavailableTokenAmount(snsToken),
+ stakeInUsd: undefined,
availableMaturity: undefined,
stakedMaturity: undefined,
},
diff --git a/frontend/src/tests/lib/utils/token.utils.spec.ts b/frontend/src/tests/lib/utils/token.utils.spec.ts
index 220cb0e3eb4..3e8b06fe008 100644
--- a/frontend/src/tests/lib/utils/token.utils.spec.ts
+++ b/frontend/src/tests/lib/utils/token.utils.spec.ts
@@ -14,6 +14,7 @@ import {
formattedTransactionFeeICP,
getMaxTransactionAmount,
getTotalBalanceInUsd,
+ getUsdValue,
numberToE8s,
numberToUlps,
sortUserTokens,
@@ -916,4 +917,33 @@ describe("token-utils", () => {
expect(getTotalBalanceInUsd([token1, token2, token3])).toBe(8);
});
});
+
+ describe("getUsdValue", () => {
+ it("should multiply the amount with the price", () => {
+ const amount = TokenAmountV2.fromNumber({
+ amount: 2,
+ token: ICPToken,
+ });
+ const tokenPrice = 3;
+ expect(getUsdValue({ amount, tokenPrice })).toBe(6);
+ });
+
+ it("should return undefined if tokenPrice is undefined", () => {
+ const amount = TokenAmountV2.fromNumber({
+ amount: 2,
+ token: ICPToken,
+ });
+ const tokenPrice = undefined;
+ expect(getUsdValue({ amount, tokenPrice })).toBe(undefined);
+ });
+
+ it("should return 0 if amount is 0, even if tokenPrice is undefined", () => {
+ const amount = TokenAmountV2.fromNumber({
+ amount: 0,
+ token: ICPToken,
+ });
+ const tokenPrice = undefined;
+ expect(getUsdValue({ amount, tokenPrice })).toBe(0);
+ });
+ });
});
diff --git a/frontend/src/tests/lib/utils/universe.utils.spec.ts b/frontend/src/tests/lib/utils/universe.utils.spec.ts
new file mode 100644
index 00000000000..b55d73f4c50
--- /dev/null
+++ b/frontend/src/tests/lib/utils/universe.utils.spec.ts
@@ -0,0 +1,48 @@
+import { LEDGER_CANISTER_ID } from "$lib/constants/canister-ids.constants";
+import { CKBTC_LEDGER_CANISTER_ID } from "$lib/constants/ckbtc-canister-ids.constants";
+import { nnsUniverseStore } from "$lib/derived//nns-universe.derived";
+import { ckBTCUniverseStore } from "$lib/derived/ckbtc-universe.derived";
+import { snsSummariesStore } from "$lib/stores/sns.store";
+import {
+ createUniverse,
+ getLedgerCanisterIdFromUniverse,
+} from "$lib/utils/universe.utils";
+import { principal } from "$tests/mocks/sns-projects.mock";
+import { setSnsProjects } from "$tests/utils/sns.test-utils";
+import { get } from "svelte/store";
+
+describe("universe.utils", () => {
+ describe("getLedgerCanisterIdFromUniverse", () => {
+ const nnsUniverse = get(nnsUniverseStore);
+ const ckBtcUniverse = get(ckBTCUniverseStore);
+
+ it("should return the ledger canister ID for the NNS universe", () => {
+ expect(getLedgerCanisterIdFromUniverse(nnsUniverse)).toBe(
+ LEDGER_CANISTER_ID
+ );
+ });
+
+ it("should return the ledger canister ID for the ckBTC universe", () => {
+ expect(getLedgerCanisterIdFromUniverse(ckBtcUniverse).toText()).toBe(
+ CKBTC_LEDGER_CANISTER_ID.toText()
+ );
+ });
+
+ it("should return the ledger canister ID for an SNS universe", () => {
+ const rootCanisterId = principal(0);
+ const ledgerCanisterId = principal(1);
+ setSnsProjects([
+ {
+ rootCanisterId,
+ ledgerCanisterId,
+ },
+ ]);
+ const snsSummaries = get(snsSummariesStore);
+ expect(snsSummaries).toHaveLength(1);
+ const snsUniverse = createUniverse(snsSummaries[0]);
+ expect(getLedgerCanisterIdFromUniverse(snsUniverse).toText()).toBe(
+ ledgerCanisterId.toText()
+ );
+ });
+ });
+});
diff --git a/frontend/src/tests/mocks/staking.mock.ts b/frontend/src/tests/mocks/staking.mock.ts
index e2facdd40b1..c602980fb99 100644
--- a/frontend/src/tests/mocks/staking.mock.ts
+++ b/frontend/src/tests/mocks/staking.mock.ts
@@ -14,6 +14,7 @@ export const mockTableProject: TableProject = {
amount: 100_000_000n,
token: ICPToken,
}),
+ stakeInUsd: 10.0,
tokenSymbol: ICPToken.symbol,
availableMaturity: 0n,
stakedMaturity: 0n,
diff --git a/frontend/src/tests/page-objects/AmountWithUsd.page-object.ts b/frontend/src/tests/page-objects/AmountWithUsd.page-object.ts
new file mode 100644
index 00000000000..8e6026fb67c
--- /dev/null
+++ b/frontend/src/tests/page-objects/AmountWithUsd.page-object.ts
@@ -0,0 +1,23 @@
+import { BasePageObject } from "$tests/page-objects/base.page-object";
+import type { PageObjectElement } from "$tests/types/page-object.types";
+import { AmountDisplayPo } from "./AmountDisplay.page-object";
+
+export class AmountWithUsdPo extends BasePageObject {
+ private static readonly TID = "amount-with-usd-component";
+
+ static under(element: PageObjectElement): AmountWithUsdPo {
+ return new AmountWithUsdPo(element.byTestId(AmountWithUsdPo.TID));
+ }
+
+ getAmountDisplayPo(): AmountDisplayPo {
+ return AmountDisplayPo.under(this.root);
+ }
+
+ async getAmount(): Promise {
+ return this.getAmountDisplayPo().getAmount();
+ }
+
+ async getAmountInUsd(): Promise {
+ return this.getText("usd-value");
+ }
+}
diff --git a/frontend/src/tests/page-objects/ConfirmFollowingButton.page-object.ts b/frontend/src/tests/page-objects/ConfirmFollowingButton.page-object.ts
new file mode 100644
index 00000000000..ebfd986b228
--- /dev/null
+++ b/frontend/src/tests/page-objects/ConfirmFollowingButton.page-object.ts
@@ -0,0 +1,12 @@
+import { BasePageObject } from "$tests/page-objects/base.page-object";
+import type { PageObjectElement } from "$tests/types/page-object.types";
+
+export class ConfirmFollowingButtonPo extends BasePageObject {
+ private static readonly TID = "confirm-following-button-component";
+
+ static under(element: PageObjectElement): ConfirmFollowingButtonPo {
+ return new ConfirmFollowingButtonPo(
+ element.byTestId(ConfirmFollowingButtonPo.TID)
+ );
+ }
+}
diff --git a/frontend/src/tests/page-objects/FollowNeuronsButton.page-object.ts b/frontend/src/tests/page-objects/FollowNeuronsButton.page-object.ts
new file mode 100644
index 00000000000..1eb24470bab
--- /dev/null
+++ b/frontend/src/tests/page-objects/FollowNeuronsButton.page-object.ts
@@ -0,0 +1,16 @@
+import { ButtonPo } from "$tests/page-objects/Button.page-object";
+import type { PageObjectElement } from "$tests/types/page-object.types";
+
+export class FollowNeuronsButtonPo extends ButtonPo {
+ private static readonly TID = "follow-neurons-button-component";
+
+ static under({
+ element,
+ }: {
+ element: PageObjectElement;
+ }): FollowNeuronsButtonPo {
+ return new FollowNeuronsButtonPo(
+ element.byTestId(FollowNeuronsButtonPo.TID)
+ );
+ }
+}
diff --git a/frontend/src/tests/page-objects/NnsNeuronRewardStatusAction.page-object.ts b/frontend/src/tests/page-objects/NnsNeuronRewardStatusAction.page-object.ts
index a64755af547..38ec2e8f0c7 100644
--- a/frontend/src/tests/page-objects/NnsNeuronRewardStatusAction.page-object.ts
+++ b/frontend/src/tests/page-objects/NnsNeuronRewardStatusAction.page-object.ts
@@ -1,5 +1,6 @@
import { BasePageObject } from "$tests/page-objects/base.page-object";
import type { PageObjectElement } from "$tests/types/page-object.types";
+import { FollowNeuronsButtonPo } from "./FollowNeuronsButton.page-object";
export class NnsNeuronRewardStatusActionPo extends BasePageObject {
private static readonly TID = "nns-neuron-reward-status-action-component";
@@ -17,4 +18,10 @@ export class NnsNeuronRewardStatusActionPo extends BasePageObject {
getDescription(): Promise {
return this.getText("state-description");
}
+
+ getFollowNeuronsButtonPo(): FollowNeuronsButtonPo {
+ return FollowNeuronsButtonPo.under({
+ element: this.root,
+ });
+ }
}
diff --git a/frontend/src/tests/page-objects/ProjectStakeCell.page-object.ts b/frontend/src/tests/page-objects/ProjectStakeCell.page-object.ts
index baa93cb86b2..57144e712ba 100644
--- a/frontend/src/tests/page-objects/ProjectStakeCell.page-object.ts
+++ b/frontend/src/tests/page-objects/ProjectStakeCell.page-object.ts
@@ -1,3 +1,5 @@
+import { AmountDisplayPo } from "$tests/page-objects/AmountDisplay.page-object";
+import { AmountWithUsdPo } from "$tests/page-objects/AmountWithUsd.page-object";
import { BasePageObject } from "$tests/page-objects/base.page-object";
import type { PageObjectElement } from "$tests/types/page-object.types";
@@ -8,7 +10,23 @@ export class ProjectStakeCellPo extends BasePageObject {
return new ProjectStakeCellPo(element.byTestId(ProjectStakeCellPo.TID));
}
+ getAmountWithUsdPo(): AmountWithUsdPo {
+ return AmountWithUsdPo.under(this.root);
+ }
+
+ getAmountDisplayPo(): AmountDisplayPo {
+ return AmountDisplayPo.under(this.root);
+ }
+
async getStake(): Promise {
- return await this.getText();
+ return (await this.getAmountDisplayPo().getText()) ?? "";
+ }
+
+ async getStakeInUsd(): Promise {
+ return await this.getAmountWithUsdPo().getAmountInUsd();
+ }
+
+ async hasStakeInUsd(): Promise {
+ return await this.getAmountWithUsdPo().isPresent();
}
}
diff --git a/frontend/src/tests/page-objects/ProjectsTableRow.page-object.ts b/frontend/src/tests/page-objects/ProjectsTableRow.page-object.ts
index 6ff63907e04..cc103c4a0a2 100644
--- a/frontend/src/tests/page-objects/ProjectsTableRow.page-object.ts
+++ b/frontend/src/tests/page-objects/ProjectsTableRow.page-object.ts
@@ -47,6 +47,14 @@ export class ProjectsTableRowPo extends ResponsiveTableRowPo {
return this.getProjectStakeCellPo().getStake();
}
+ getStakeInUsd(): Promise {
+ return this.getProjectStakeCellPo().getStakeInUsd();
+ }
+
+ hasStakeInUsd(): Promise {
+ return this.getProjectStakeCellPo().hasStakeInUsd();
+ }
+
getNeuronCount(): Promise {
return this.getProjectNeuronsCellPo().getNeuronCount();
}
diff --git a/frontend/src/tests/workflows/Launchpad/sns-agg-page-0.json b/frontend/src/tests/workflows/Launchpad/sns-agg-page-0.json
index 9aab22adb31..c83aa8b0f10 100644
--- a/frontend/src/tests/workflows/Launchpad/sns-agg-page-0.json
+++ b/frontend/src/tests/workflows/Launchpad/sns-agg-page-0.json
@@ -290,7 +290,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 799999974702594000,
+ "icrc1_total_supply": 799999973787494000,
"swap_params": {
"params": {
"min_participant_icp_e8s": 10000000,
@@ -1102,7 +1102,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 10123995617739948,
+ "icrc1_total_supply": 10124927902157952,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -1832,7 +1832,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 610589962133969,
+ "icrc1_total_supply": 610720932668740,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -2237,7 +2237,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 102895037362856290,
+ "icrc1_total_supply": 102964869212728060,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -2735,6 +2735,14 @@
"NativeNervousSystemFunction": {}
}
},
+ {
+ "id": 15,
+ "name": "Advance SNS target version",
+ "description": "Proposal to advance the target version of this SNS.",
+ "function_type": {
+ "NativeNervousSystemFunction": {}
+ }
+ },
{
"id": 1000,
"name": "burn",
@@ -2886,7 +2894,7 @@
]
],
"icrc1_fee": [100000000],
- "icrc1_total_supply": 926912399675751300,
+ "icrc1_total_supply": 927526487328286700,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
diff --git a/frontend/src/tests/workflows/Launchpad/sns-agg-page-1.json b/frontend/src/tests/workflows/Launchpad/sns-agg-page-1.json
index bedb0e415da..8c11d9b1e29 100644
--- a/frontend/src/tests/workflows/Launchpad/sns-agg-page-1.json
+++ b/frontend/src/tests/workflows/Launchpad/sns-agg-page-1.json
@@ -320,7 +320,7 @@
]
],
"icrc1_fee": [10000],
- "icrc1_total_supply": 98205625621905220,
+ "icrc1_total_supply": 98205897256766300,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000278,
@@ -843,7 +843,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 100900495246482000,
+ "icrc1_total_supply": 100916340486692510,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -1546,7 +1546,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 50390095680116200,
+ "icrc1_total_supply": 50391001230537090,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -1943,7 +1943,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 9961349260568336,
+ "icrc1_total_supply": 9961351087932912,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -2336,7 +2336,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 10095855101312652,
+ "icrc1_total_supply": 10095947312877124,
"swap_params": {
"params": {
"min_participant_icp_e8s": 1000000000,
@@ -2725,7 +2725,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 12647472446219800,
+ "icrc1_total_supply": 12650726185265496,
"swap_params": {
"params": {
"min_participant_icp_e8s": 500000000,
@@ -4160,7 +4160,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 99715900534972980,
+ "icrc1_total_supply": 99704696326645550,
"swap_params": {
"params": {
"min_participant_icp_e8s": 800000000,
diff --git a/frontend/src/tests/workflows/Launchpad/sns-agg-page-2.json b/frontend/src/tests/workflows/Launchpad/sns-agg-page-2.json
index eb2b9e6020a..70a6f3fa80c 100644
--- a/frontend/src/tests/workflows/Launchpad/sns-agg-page-2.json
+++ b/frontend/src/tests/workflows/Launchpad/sns-agg-page-2.json
@@ -152,6 +152,32 @@
"function_type": {
"NativeNervousSystemFunction": {}
}
+ },
+ {
+ "id": 5001,
+ "name": "Generic Function to Dissolve NTN Neurons owned by TRAX DAO",
+ "description": "Start dissolving NTN DAO neurons held by our DAO.",
+ "function_type": {
+ "GenericNervousSystemFunction": {
+ "validator_canister_id": "eqsml-lyaaa-aaaaq-aacdq-cai",
+ "target_canister_id": "eqsml-lyaaa-aaaaq-aacdq-cai",
+ "validator_method_name": "validate_manage_neuron",
+ "target_method_name": "manage_neuron"
+ }
+ }
+ },
+ {
+ "id": 5002,
+ "name": "Generic Function to Dissolve NTN Neurons owned by TRAX DAO",
+ "description": "Start dissolving NTN DAO neurons held by our DAO.",
+ "function_type": {
+ "GenericNervousSystemFunction": {
+ "validator_canister_id": "2kusx-raaaa-aaaal-qjaxq-cai",
+ "target_canister_id": "eqsml-lyaaa-aaaaq-aacdq-cai",
+ "validator_method_name": "validate_manage_neuron",
+ "target_method_name": "manage_neuron"
+ }
+ }
}
]
},
@@ -1473,7 +1499,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 100940325398041180,
+ "icrc1_total_supply": 100996551636270740,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -3512,7 +3538,7 @@
]
],
"icrc1_fee": [10000],
- "icrc1_total_supply": 100741009397969,
+ "icrc1_total_supply": 100741516352634,
"swap_params": {
"params": {
"min_participant_icp_e8s": 500000000,
@@ -4137,6 +4163,19 @@
"target_method_name": "transfer_icpex_lp_position"
}
}
+ },
+ {
+ "id": 3000,
+ "name": "Neuron Pylon: ICRC55 Command",
+ "description": "Manage vectors in the neuron pylon.",
+ "function_type": {
+ "GenericNervousSystemFunction": {
+ "validator_canister_id": "6jvpj-sqaaa-aaaaj-azwnq-cai",
+ "target_canister_id": "6jvpj-sqaaa-aaaaj-azwnq-cai",
+ "validator_method_name": "icrc55_command_validate",
+ "target_method_name": "icrc55_command"
+ }
+ }
}
]
},
@@ -4274,7 +4313,7 @@
]
],
"icrc1_fee": [1000],
- "icrc1_total_supply": 995570070980,
+ "icrc1_total_supply": 995569306980,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -4500,6 +4539,14 @@
"NativeNervousSystemFunction": {}
}
},
+ {
+ "id": 15,
+ "name": "Advance SNS target version",
+ "description": "Proposal to advance the target version of this SNS.",
+ "function_type": {
+ "NativeNervousSystemFunction": {}
+ }
+ },
{
"id": 1000,
"name": "sys_config",
@@ -7172,7 +7219,7 @@
]
],
"icrc1_fee": [1000000],
- "icrc1_total_supply": 20196590148632064,
+ "icrc1_total_supply": 20198359506098944,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -8552,7 +8599,8 @@
"bpsjh-6yaaa-aaaah-adyjq-cai",
"gichg-2iaaa-aaaah-adtia-cai",
"wm3tr-wyaaa-aaaah-adxyq-cai",
- "a32nk-lqaaa-aaaah-aeaba-cai"
+ "a32nk-lqaaa-aaaah-aeaba-cai",
+ "uoow7-vyaaa-aaaah-aq5cq-cai"
],
"archives": ["dyxt2-fqaaa-aaaaq-aacsa-cai"]
},
@@ -8684,6 +8732,14 @@
"function_type": {
"NativeNervousSystemFunction": {}
}
+ },
+ {
+ "id": 15,
+ "name": "Advance SNS target version",
+ "description": "Proposal to advance the target version of this SNS.",
+ "function_type": {
+ "NativeNervousSystemFunction": {}
+ }
}
]
},
@@ -9172,7 +9228,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 25202470868927560,
+ "icrc1_total_supply": 25206693834804576,
"swap_params": {
"params": {
"min_participant_icp_e8s": 500000000,
@@ -10635,7 +10691,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 10043309077251156,
+ "icrc1_total_supply": 10044164211068284,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -11860,7 +11916,7 @@
]
],
"icrc1_fee": [10000],
- "icrc1_total_supply": 103027274894831020,
+ "icrc1_total_supply": 103121990792922610,
"swap_params": {
"params": {
"min_participant_icp_e8s": 1000000000,
@@ -12366,6 +12422,19 @@
}
}
},
+ {
+ "id": 1701,
+ "name": "Transfer",
+ "description": null,
+ "function_type": {
+ "GenericNervousSystemFunction": {
+ "validator_canister_id": "7eikv-2iaaa-aaaag-qdgwa-cai",
+ "target_canister_id": "7eikv-2iaaa-aaaag-qdgwa-cai",
+ "validator_method_name": "transferValidate",
+ "target_method_name": "transfer"
+ }
+ }
+ },
{
"id": 2001,
"name": "SetFarmAdmins",
@@ -13000,7 +13069,7 @@
]
],
"icrc1_fee": [1000000],
- "icrc1_total_supply": 98691452623220830,
+ "icrc1_total_supply": 98712522969426190,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -13550,7 +13619,7 @@
{
"id": 4,
"name": "Add nervous system function",
- "description": "Proposal to add a new, user-defined, nervous system function:a canister call which can then be executed by proposal.",
+ "description": "Proposal to add a new, user-defined, nervous system function: a canister call which can then be executed by proposal.",
"function_type": {
"NativeNervousSystemFunction": {}
}
@@ -13558,7 +13627,7 @@
{
"id": 5,
"name": "Remove nervous system function",
- "description": "Proposal to remove a user-defined nervous system function,which will be no longer executable by proposal.",
+ "description": "Proposal to remove a user-defined nervous system function, which will be no longer executable by proposal.",
"function_type": {
"NativeNervousSystemFunction": {}
}
@@ -13566,7 +13635,7 @@
{
"id": 6,
"name": "Execute nervous system function",
- "description": "Proposal to execute a user-defined nervous system function,previously added by an AddNervousSystemFunction proposal. A canister call will be made when executed.",
+ "description": "Proposal to execute a user-defined nervous system function, previously added by an AddNervousSystemFunction proposal. A canister call will be made when executed.",
"function_type": {
"NativeNervousSystemFunction": {}
}
@@ -13613,7 +13682,7 @@
},
{
"id": 12,
- "name": "Mint SNS Tokens",
+ "name": "Mint SNS tokens",
"description": "Proposal to mint SNS tokens to a specified recipient.",
"function_type": {
"NativeNervousSystemFunction": {}
@@ -13634,6 +13703,14 @@
"function_type": {
"NativeNervousSystemFunction": {}
}
+ },
+ {
+ "id": 15,
+ "name": "Advance SNS target version",
+ "description": "Proposal to advance the target version of this SNS.",
+ "function_type": {
+ "NativeNervousSystemFunction": {}
+ }
}
]
},
@@ -14173,7 +14250,7 @@
]
],
"icrc1_fee": [1000000],
- "icrc1_total_supply": 100006586115079890,
+ "icrc1_total_supply": 100006604101217710,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -15353,7 +15430,7 @@
]
],
"icrc1_fee": [100000],
- "icrc1_total_supply": 100066305464866060,
+ "icrc1_total_supply": 100070574806156050,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
diff --git a/frontend/src/tests/workflows/Launchpad/sns-agg-page-3.json b/frontend/src/tests/workflows/Launchpad/sns-agg-page-3.json
index c2347e4e0c9..43ec1949502 100644
--- a/frontend/src/tests/workflows/Launchpad/sns-agg-page-3.json
+++ b/frontend/src/tests/workflows/Launchpad/sns-agg-page-3.json
@@ -1439,7 +1439,7 @@
]
],
"icrc1_fee": [10000],
- "icrc1_total_supply": 999807030000,
+ "icrc1_total_supply": 999803020000,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -1826,7 +1826,7 @@
]
],
"icrc1_fee": [10000],
- "icrc1_total_supply": 99999838860000,
+ "icrc1_total_supply": 99999834180000,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -2220,7 +2220,7 @@
]
],
"icrc1_fee": [200000],
- "icrc1_total_supply": 1041721529012236700,
+ "icrc1_total_supply": 1041715603958135200,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -2337,7 +2337,8 @@
"buwm7-7yaaa-aaaar-qagva-cai",
"btxkl-saaaa-aaaar-qagvq-cai",
"daijl-2yaaa-aaaar-qag3a-cai",
- "iq4w5-2aaaa-aaaar-qahfq-cai"
+ "iq4w5-2aaaa-aaaar-qahfq-cai",
+ "zftzm-qqaaa-aaaam-adxfa-cai"
],
"archives": ["i2f3v-jyaaa-aaaaq-aadpq-cai"]
},
@@ -2470,6 +2471,14 @@
"NativeNervousSystemFunction": {}
}
},
+ {
+ "id": 15,
+ "name": "Advance SNS target version",
+ "description": "Proposal to advance the target version of this SNS.",
+ "function_type": {
+ "NativeNervousSystemFunction": {}
+ }
+ },
{
"id": 1000,
"name": "Vote on NNS proposal",
@@ -2855,7 +2864,7 @@
]
],
"icrc1_fee": [1000000],
- "icrc1_total_supply": 11648066980982072,
+ "icrc1_total_supply": 11655088901371512,
"swap_params": {
"params": {
"min_participant_icp_e8s": 1000000000,
@@ -3386,11 +3395,11 @@
"neuron_claimer_permissions": {
"permissions": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},
- "neuron_minimum_stake_e8s": 10000000000,
+ "neuron_minimum_stake_e8s": 1e18,
"max_neuron_age_for_age_bonus": 157788000,
"initial_voting_period_seconds": 345600,
"neuron_minimum_dissolve_delay_to_vote_seconds": 7890048,
- "reject_cost_e8s": 50000000000,
+ "reject_cost_e8s": 1e18,
"max_proposals_to_keep_per_action": 100,
"wait_for_quiet_deadline_increase_seconds": 172800,
"max_number_of_neurons": 200000,
@@ -3749,7 +3758,7 @@
]
],
"icrc1_fee": [2808348672, 232830643],
- "icrc1_total_supply": 2005936024740878,
+ "icrc1_total_supply": 2006865829196629,
"swap_params": {
"params": {
"min_participant_icp_e8s": 400000000,
@@ -4357,7 +4366,7 @@
]
],
"icrc1_fee": [705032704, 1],
- "icrc1_total_supply": 2415338342178971000,
+ "icrc1_total_supply": 2414259192154920400,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
@@ -4598,6 +4607,19 @@
"target_method_name": "execute_project_vote_proposal"
}
}
+ },
+ {
+ "id": 24000,
+ "name": "Commit Frontend Canister Update",
+ "description": "Commit updated frontend canister with computed evidence",
+ "function_type": {
+ "GenericNervousSystemFunction": {
+ "validator_canister_id": "mnc6b-aaaaa-aaaap-qhnrq-cai",
+ "target_canister_id": "mnc6b-aaaaa-aaaap-qhnrq-cai",
+ "validator_method_name": "validate_commit_proposed_batch",
+ "target_method_name": "commit_proposed_batch"
+ }
+ }
}
]
},
@@ -4896,7 +4918,7 @@
]
],
"icrc1_fee": [10000],
- "icrc1_total_supply": 1999999981650000,
+ "icrc1_total_supply": 1999999911100000,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000000,
@@ -8227,7 +8249,7 @@
]
],
"icrc1_fee": [10000],
- "icrc1_total_supply": 100015266240226100,
+ "icrc1_total_supply": 100019008171550980,
"swap_params": {
"params": {
"min_participant_icp_e8s": 100000000,
diff --git a/scripts/nns-dapp/test-config-assets/app/arg.did b/scripts/nns-dapp/test-config-assets/app/arg.did
index daa322d61a0..95c6e694e35 100644
--- a/scripts/nns-dapp/test-config-assets/app/arg.did
+++ b/scripts/nns-dapp/test-config-assets/app/arg.did
@@ -10,7 +10,7 @@
record{ 0="CKUSDC_LEDGER_CANISTER_ID"; 1="xevnm-gaaaa-aaaar-qafnq-cai" };
record{ 0="CYCLES_MINTING_CANISTER_ID"; 1="rkp4c-7iaaa-aaaaa-aaaca-cai" };
record{ 0="DFX_NETWORK"; 1="app" };
- record{ 0="FEATURE_FLAGS"; 1="{\"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING\":false,\"ENABLE_CKTESTBTC\":false,\"ENABLE_EXPORT_NEURONS_REPORT\":false,\"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION\":false,\"ENABLE_USD_VALUES\":true,\"ENABLE_USD_VALUES_FOR_NEURONS\":false}" };
+ record{ 0="FEATURE_FLAGS"; 1="{\"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING\":false,\"ENABLE_CKTESTBTC\":false,\"ENABLE_EXPORT_NEURONS_REPORT\":true,\"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION\":false,\"ENABLE_USD_VALUES\":true,\"ENABLE_USD_VALUES_FOR_NEURONS\":false}" };
record{ 0="FETCH_ROOT_KEY"; 1="false" };
record{ 0="GOVERNANCE_CANISTER_ID"; 1="rrkah-fqaaa-aaaaa-aaaaq-cai" };
record{ 0="HOST"; 1="https://icp-api.io" };
diff --git a/scripts/nns-dapp/test-config-assets/app/env b/scripts/nns-dapp/test-config-assets/app/env
index 07d99a8076f..e347c46cf9e 100644
--- a/scripts/nns-dapp/test-config-assets/app/env
+++ b/scripts/nns-dapp/test-config-assets/app/env
@@ -7,7 +7,7 @@ VITE_LEDGER_CANISTER_ID=ryjl3-tyaaa-aaaaa-aaaba-cai
VITE_INDEX_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai
VITE_OWN_CANISTER_ID=xnjld-hqaaa-aaaal-qb56q-cai
VITE_FETCH_ROOT_KEY=false
-VITE_FEATURE_FLAGS="{\"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING\":false,\"ENABLE_CKTESTBTC\":false,\"ENABLE_EXPORT_NEURONS_REPORT\":false,\"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION\":false,\"ENABLE_USD_VALUES\":true,\"ENABLE_USD_VALUES_FOR_NEURONS\":false}"
+VITE_FEATURE_FLAGS="{\"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING\":false,\"ENABLE_CKTESTBTC\":false,\"ENABLE_EXPORT_NEURONS_REPORT\":true,\"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION\":false,\"ENABLE_USD_VALUES\":true,\"ENABLE_USD_VALUES_FOR_NEURONS\":false}"
VITE_HOST=https://icp-api.io
VITE_IDENTITY_SERVICE_URL=https://identity.internetcomputer.org/
VITE_ICP_SWAP_URL=https://uvevg-iyaaa-aaaak-ac27q-cai.raw.ic0.app/
diff --git a/scripts/nns-dapp/test-config-assets/mainnet/arg.did b/scripts/nns-dapp/test-config-assets/mainnet/arg.did
index 09f1cffbf9c..9cba090cd6e 100644
--- a/scripts/nns-dapp/test-config-assets/mainnet/arg.did
+++ b/scripts/nns-dapp/test-config-assets/mainnet/arg.did
@@ -10,7 +10,7 @@
record{ 0="CKUSDC_LEDGER_CANISTER_ID"; 1="xevnm-gaaaa-aaaar-qafnq-cai" };
record{ 0="CYCLES_MINTING_CANISTER_ID"; 1="rkp4c-7iaaa-aaaaa-aaaca-cai" };
record{ 0="DFX_NETWORK"; 1="mainnet" };
- record{ 0="FEATURE_FLAGS"; 1="{\"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING\":false,\"ENABLE_CKTESTBTC\":false,\"ENABLE_EXPORT_NEURONS_REPORT\":false,\"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION\":false,\"ENABLE_USD_VALUES\":true,\"ENABLE_USD_VALUES_FOR_NEURONS\":false}" };
+ record{ 0="FEATURE_FLAGS"; 1="{\"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING\":false,\"ENABLE_CKTESTBTC\":false,\"ENABLE_EXPORT_NEURONS_REPORT\":true,\"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION\":false,\"ENABLE_USD_VALUES\":true,\"ENABLE_USD_VALUES_FOR_NEURONS\":false}" };
record{ 0="FETCH_ROOT_KEY"; 1="false" };
record{ 0="GOVERNANCE_CANISTER_ID"; 1="rrkah-fqaaa-aaaaa-aaaaq-cai" };
record{ 0="HOST"; 1="https://icp-api.io" };
diff --git a/scripts/nns-dapp/test-config-assets/mainnet/env b/scripts/nns-dapp/test-config-assets/mainnet/env
index ba486cd5c55..aa4ba7d4051 100644
--- a/scripts/nns-dapp/test-config-assets/mainnet/env
+++ b/scripts/nns-dapp/test-config-assets/mainnet/env
@@ -7,7 +7,7 @@ VITE_LEDGER_CANISTER_ID=ryjl3-tyaaa-aaaaa-aaaba-cai
VITE_INDEX_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai
VITE_OWN_CANISTER_ID=qoctq-giaaa-aaaaa-aaaea-cai
VITE_FETCH_ROOT_KEY=false
-VITE_FEATURE_FLAGS="{\"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING\":false,\"ENABLE_CKTESTBTC\":false,\"ENABLE_EXPORT_NEURONS_REPORT\":false,\"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION\":false,\"ENABLE_USD_VALUES\":true,\"ENABLE_USD_VALUES_FOR_NEURONS\":false}"
+VITE_FEATURE_FLAGS="{\"DISABLE_IMPORT_TOKEN_VALIDATION_FOR_TESTING\":false,\"ENABLE_CKTESTBTC\":false,\"ENABLE_EXPORT_NEURONS_REPORT\":true,\"ENABLE_PERIODIC_FOLLOWING_CONFIRMATION\":false,\"ENABLE_USD_VALUES\":true,\"ENABLE_USD_VALUES_FOR_NEURONS\":false}"
VITE_HOST=https://icp-api.io
VITE_IDENTITY_SERVICE_URL=https://identity.internetcomputer.org/
VITE_ICP_SWAP_URL=https://uvevg-iyaaa-aaaak-ac27q-cai.raw.ic0.app/