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/