diff --git a/docs/learn/protocols/iota2.0/core-concepts/mana-calculator.md b/docs/learn/protocols/iota2.0/core-concepts/mana-calculator.md new file mode 100644 index 00000000000..06d0cf24ddd --- /dev/null +++ b/docs/learn/protocols/iota2.0/core-concepts/mana-calculator.md @@ -0,0 +1,40 @@ +import ManaCalculator from '@site/src/components/ManaCalculator'; + +# Mana Calculator + +Mana is a reward resource generated by holding IOTA tokens. It is utilized for block issuance, access to network throughput, protection against Sybil attacks, and various other services. Our incentives scheme allows you to stake or delegate IOTA tokens to receive Mana rewards, in addition to the Mana generated simply by holding your IOTA tokens, without having to participate in any consensus-related activity. + +Here, we introduce the Mana calculator: a tool to help you make decisions about the type of participation that will suit you best, to help you predict the Mana generation you’ll be granted, and to decide the number of IOTA tokens you should hold to guarantee your desired level of access in the future. + +## How to use the Calculator: Step-by-step instructions + +- **Choose your network:** This calculator works for both Shimmer and the future IOTA 2.0 network. Both networks will have their own type of Mana (IOTA Mana and Shimmer Mana), with their own parameters and, consequently, their own behavior. Be sure you select the correct scenario you want to simulate! + +- **Choose your role:** Will you delegate to a validator, become a validator yourself, or just hold tokens? To be a validator, you must run a node and lock your tokens while performing validation services, which will give you higher rewards. If you want to delegate to a validator, your tokens will not be locked and you won’t need to run a node, but this will give you fewer rewards than validating. Finally, just holding tokens grants you a certain amount of Mana, which will be less than by validating or delegating. + +- **Input the number of tokens you own:** No matter which network and role you choose, your Mana generation will depend on how many available tokens you have. Pay attention to the units! The input should be done in IOTA or Shimmer (not microIota or Glow). + +With the inputs above, you can already estimate how much Mana you’ll be granted per epoch. But what does it mean in TPS? How many blocks per second does your Mana grant you? The answer depends on the size of your block and the network congestion levels. However, we can use a default block size, which will be enough for most applications, and assume certain levels of congestion. This takes us to the last step in this calculator: + +- **Choose a congestion level:** Given a certain Mana generation per epoch, this can be translated into a number of blocks issued per second that depends on the congestion level. Choose one of the given congestion levels (low, stable, or extreme) to estimate how many blocks per second your tokens and participation grant you! This metric is also shown in another way: time until block issuance, which tells you in which periodicity you’ll be able to issue a block. + + + + +## Advanced settings + +The steps outlined above can give you a rough estimate of your Mana generation and granted TPS. However, playing with the advanced settings will give you more precise results. + +- **Change the state of the system:** The calculator's default settings assume a certain level of participation in the consensus (i.e., locked stake, delegated stake, and performance factor of validators). Those settings can be changed under the “Advanced Settings - State of the System” tab. You can also add or delete validators and change their fixed costs. + +- If you choose to delegate: + - **Change the validator you delegate to:** Under the default settings, you'll delegate to Validator 1 (which is pool that, technically speaking, is under the "equilibrium state", see the WP for more details). However, you can change this setting and know your rewards if you participate in other pools, with different share of delegated to locked stake, and different performance factors. + +- If you choose to validate: + - **Change the amount of stake delegated to you:** The calculator's default settings automatically assign you a certain share of the delegated stake when you start validating. However, you can change this setting to know your rewards should you manage to attract more (or less) delegated stake than the default setting. + - **Change your performance factor:** Your performance factor under the calculator's default settings is 0.95. However, you can change this to simulate larger or smaller shares of online time. + - **Change your fixed costs:** By the default settings of the calculator, your fixed costs are zero. This setting can be changed so you spare part of the pool rowards just for yourself. However, notice that the fixed cost is a public parameter, that you define during registration. This means that the delegator knows how you set this value and might consider it when choosing a validator to delegate. Furthermore, if you set this value too high, you'll be punished and won't get any rewards. + +- **Simulate your Mana accumulation**: the default setting of the calculator assumes a stationary regime and calculates the block creation rate you guarantee depending on your stake and level of participation. However, especially in the early stages of the network, you might want to save your Mana to sell later. This tab simulates how much Mana you'll accumulate in a certain time period. For that (besides the other already defined inputs), you just need to choose an initial and final epoch, and the tool will plot a graph of your accumulation during that period. In the same graph, you can also have an idea of how many blocks can be issued with this accumulated amount of Mana, for the different congestion levels (already defined in the non-advanced settings). + + diff --git a/docs/learn/protocols/iota2.0/core-concepts/mana.md b/docs/learn/protocols/iota2.0/core-concepts/mana.md index 4df3101bcb2..5687a973808 100644 --- a/docs/learn/protocols/iota2.0/core-concepts/mana.md +++ b/docs/learn/protocols/iota2.0/core-concepts/mana.md @@ -1,4 +1,4 @@ -# Tokenomics: Mana, Accounts, Staking and _Delegation_ +# Tokenomics: Mana, Accounts, Staking and Delegation Mana is a scarce resource used to access the IOTA ledger and update its state through block creation. It is a spendable asset tracked in the _ledger state_, powering smart contracts, DeFi_applications, block creation, and various other services, and is linked to [accounts](#accounts), which allow you to [stake or delegate](#staking-and-delegation-1) IOTA to receive [Mana rewards](#mana-rewards). diff --git a/package.json b/package.json index a8556097c30..dba36cf7618 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "flickity-fade": "^2.0.0", "globby": "^13.1.4", "hast-util-is-element": "1.1.0", + "humanize-duration": "^3.30.0", "plugin-image-zoom": "flexanalytics/plugin-image-zoom", "raw-loader": "^4.0.2", "react": "18.2.0", @@ -62,6 +63,7 @@ "react-player": "^2.11.2", "react-popper": "^2.3.0", "react-select": "^5.7.7", + "recharts": "^2.9.2", "rehype-jargon": "3.0.0", "rehype-katex": "4", "rehype-lodash-template": "^0.2.1", diff --git a/src/components/ManaCalculator/actions/calculateBPS.ts b/src/components/ManaCalculator/actions/calculateBPS.ts new file mode 100644 index 00000000000..adf6427c0ca --- /dev/null +++ b/src/components/ManaCalculator/actions/calculateBPS.ts @@ -0,0 +1,5 @@ +import { EPOCH_DURATION } from '../constants'; + +export function calculateBPS(mana: number, congestion: number): number { + return mana / congestion / EPOCH_DURATION; +} diff --git a/src/components/ManaCalculator/actions/calculateManaRewards.ts b/src/components/ManaCalculator/actions/calculateManaRewards.ts new file mode 100644 index 00000000000..b2ea44e3863 --- /dev/null +++ b/src/components/ManaCalculator/actions/calculateManaRewards.ts @@ -0,0 +1,145 @@ +import { getNetworkSupply } from '../utils'; +import type { ValidatorParameters, ValidatorProps } from '../types'; +import { NetworkType, UserType } from '../enums'; +import { decay } from './decay'; +import { targetReward } from './targetReward'; + +export function calculateManaRewards( + stake: number, + yourPool: number, + validatorParameters: ValidatorParameters, + validators: ValidatorProps[], + initialEpoch: number, + finalEpoch: number, + userType: UserType, + networkType: NetworkType, + generationPerSlot: number, +): number { + const supply = getNetworkSupply(networkType); + let totalTargetReward = 0; + let epochDiff = finalEpoch - initialEpoch; + + if (finalEpoch) { + for (let i = 0; i < epochDiff; i++) { + totalTargetReward += decay( + targetReward(initialEpoch + i, supply, generationPerSlot), + epochDiff - i, + ); + } + } else { + epochDiff = 1; + finalEpoch = initialEpoch + 1; + totalTargetReward = targetReward(initialEpoch, supply, generationPerSlot); + } + + const lockedStake: number[] = validators.map( + (validator) => validator.lockedStake, + ); + const fixedCosts: number[] = validators.map( + (validator) => validator.fixedCost, + ); + const performance: number[] = validators.map( + (validator) => validator.performanceFactor, + ); + const delegatedStake: number[] = validators.map( + (validator) => validator.delegatedStake, + ); + + if (userType == UserType.VALIDATOR) { + lockedStake.push(stake * validatorParameters.shareOfYourStakeLocked); + fixedCosts.push(validatorParameters.fixedCost); + performance.push(validatorParameters.performanceFactor); + delegatedStake.push( + (1 - validatorParameters.shareOfYourStakeLocked) * stake + + validatorParameters.attractedNewDelegatedStake + + validatorParameters.attractedDelegatedStakeFromOtherPools * + delegatedStake.reduce((a, b) => a + b, 0), + ); + for (let i = 0; i < validators.length; i++) { + delegatedStake[i] *= + 1 - validatorParameters.attractedDelegatedStakeFromOtherPools; + } + } + + if (userType == UserType.DELEGATOR) { + delegatedStake[yourPool] += stake; + } + + const totalValidatorsStake = lockedStake.reduce((a, b) => a + b, 0); + const totalDelegatedStake = delegatedStake.reduce((a, b) => a + b, 0); + const totalStake = totalDelegatedStake + totalValidatorsStake; + const restOfTokenHoldings = supply - totalStake; + if (restOfTokenHoldings < 0) { + throw new Error('Pools must have (collectively) at most iotaSupply tokens'); + } + + // Calculates profit margin of the epoch (only when there are tokens stake, otherwise, it's set to None) + let profitMargin: number | null; + if (totalStake > 0) { + profitMargin = totalValidatorsStake / (totalValidatorsStake + totalStake); + } else { + profitMargin = null; + } + + // Calculates the total rewards for each pool, already discounting the validator fixed cost + const poolRewards: number[] = new Array(lockedStake.length).fill(0); + if (totalStake > 0) { + if (totalValidatorsStake > 0) { + for (let i = 0; i < lockedStake.length; i++) { + poolRewards[i] = + ((lockedStake[i] + delegatedStake[i]) / totalStake + + lockedStake[i] / totalValidatorsStake) * + totalTargetReward * + (performance[i] / 2.0) - + epochDiff * fixedCosts[i]; + } + } else { + for (let i = 0; i < lockedStake.length; i++) { + poolRewards[i] = + ((lockedStake[i] + delegatedStake[i]) / totalStake) * + totalTargetReward * + (performance[i] / 2.0) - + epochDiff * fixedCosts[i]; + } + } + } + + // Calculates the rewards for each validator + const validatorRewards: number[] = new Array(lockedStake.length).fill(0); + for (let i = 0; i < lockedStake.length; i++) { + if (poolRewards[i] < 0) { + validatorRewards[i] = 0; + poolRewards[i] = 0; + } else if (poolRewards[i] === 0) { + validatorRewards[i] = epochDiff * fixedCosts[i]; + } else { + validatorRewards[i] = + epochDiff * fixedCosts[i] + + poolRewards[i] * profitMargin + + ((1 - profitMargin) * poolRewards[i] * lockedStake[i]) / + (delegatedStake[i] + lockedStake[i]); + } + } + + const delegatorRewards: number[] = new Array(lockedStake.length).fill(0); + for (let i = 0; i < lockedStake.length; i++) { + if (poolRewards[i] > 0) { + delegatorRewards[i] = + (poolRewards[i] * (1 - profitMargin) * delegatedStake[i]) / + (delegatedStake[i] + lockedStake[i]); + } + } + + let rewards = 0; + if (userType == UserType.DELEGATOR) { + if (delegatorRewards[yourPool] > 0) { + rewards = (delegatorRewards[yourPool] * stake) / delegatedStake[yourPool]; + } + } + + if (userType == UserType.VALIDATOR) { + rewards = validatorRewards[lockedStake.length - 1]; + } + + return rewards; +} diff --git a/src/components/ManaCalculator/actions/calculatePassiveRewards.ts b/src/components/ManaCalculator/actions/calculatePassiveRewards.ts new file mode 100644 index 00000000000..8e78629e6a9 --- /dev/null +++ b/src/components/ManaCalculator/actions/calculatePassiveRewards.ts @@ -0,0 +1,16 @@ +import { getFirstSlotOfEpoch } from '../utils'; +import { getPotentialMana } from './getPotentialMana'; + +export function calculatePassiveRewards( + tokens: number, + initialEpoch: number, + finalEpoch: number, + generationPerSlot: number, +): number { + return getPotentialMana( + tokens, + getFirstSlotOfEpoch(initialEpoch) - 1, + getFirstSlotOfEpoch(finalEpoch) - 1, + generationPerSlot, + ); +} diff --git a/src/components/ManaCalculator/actions/decay.ts b/src/components/ManaCalculator/actions/decay.ts new file mode 100644 index 00000000000..35f1e3cf615 --- /dev/null +++ b/src/components/ManaCalculator/actions/decay.ts @@ -0,0 +1,12 @@ +import { BETA_PER_YEAR, EPOCH_DURATION_IN_YEARS } from '../constants'; + +// Returns the decayed value of value by the amount of epochs +export function decay(value: number, epochsAmount: number): number { + if (value != 0 && epochsAmount != 0) { + const decay = Math.exp( + -BETA_PER_YEAR * EPOCH_DURATION_IN_YEARS * epochsAmount, + ); + value = Math.floor(value * decay); + } + return value; +} diff --git a/src/components/ManaCalculator/actions/getPotentialMana.ts b/src/components/ManaCalculator/actions/getPotentialMana.ts new file mode 100644 index 00000000000..7139af49972 --- /dev/null +++ b/src/components/ManaCalculator/actions/getPotentialMana.ts @@ -0,0 +1,47 @@ +import { + BETA_PER_YEAR, + EPOCH_DURATION_IN_YEARS, + SLOTS_IN_EPOCH, +} from '../constants'; +import { getFirstSlotOfEpoch, slotToEpoch } from '../utils'; +import { decay } from './decay'; + +// Returns the potential mana generated by holding value tokens from creationSlot to consumptionSlot +export function getPotentialMana( + value: number, + creationSlot: number, + consumptionSlot: number, + generationRate: number, +): number { + const creationEpoch = slotToEpoch(creationSlot); + const consumptionEpoch = slotToEpoch(consumptionSlot); + + const epochsLeftToConsumption = consumptionEpoch - creationEpoch; + + const slotAfterCreationEpoch = getFirstSlotOfEpoch(creationEpoch + 1); + const firstSlotConsumptionEpoch = getFirstSlotOfEpoch(consumptionEpoch); + + const d1 = slotAfterCreationEpoch - creationSlot; + const d2 = consumptionSlot - firstSlotConsumptionEpoch; + + let potentialMana = 0; + if (epochsLeftToConsumption == 0) { + potentialMana = value * (consumptionSlot - creationSlot) * generationRate; + } else if (epochsLeftToConsumption == 1) { + potentialMana = + value * d2 * generationRate + decay(value * d1 * generationRate, 1); + } else { + const c = + Math.exp(-BETA_PER_YEAR * EPOCH_DURATION_IN_YEARS) / + (1 - Math.exp(-BETA_PER_YEAR * EPOCH_DURATION_IN_YEARS)); + const aux = value * generationRate * c * SLOTS_IN_EPOCH; + const potentialMana_n = decay( + value * d1 * generationRate, + epochsLeftToConsumption, + ); + const potentialMana_n_1 = decay(aux, epochsLeftToConsumption - 1); + const potentialMana_0 = value * d2 * generationRate + aux; + potentialMana = potentialMana_n - potentialMana_n_1 + potentialMana_0; + } + return potentialMana; +} diff --git a/src/components/ManaCalculator/actions/index.ts b/src/components/ManaCalculator/actions/index.ts new file mode 100644 index 00000000000..782afe50677 --- /dev/null +++ b/src/components/ManaCalculator/actions/index.ts @@ -0,0 +1,5 @@ +export * from './calculateManaRewards'; +export * from './calculatePassiveRewards'; +export * from './calculateBPS'; +export * from './getPotentialMana'; +export * from './decay'; diff --git a/src/components/ManaCalculator/actions/targetReward.ts b/src/components/ManaCalculator/actions/targetReward.ts new file mode 100644 index 00000000000..3e7274553d2 --- /dev/null +++ b/src/components/ManaCalculator/actions/targetReward.ts @@ -0,0 +1,34 @@ +import { + BETA_PER_YEAR, + BOOTSTRAPPING_DURATION, + EPOCH_DURATION_IN_YEARS, + REWARDS_MANA_SHARE_COEFFICIENT, + SLOTS_IN_EPOCH, +} from '../constants'; +import { decay } from './decay'; + +// Returns the target reward for the given epoch +export function targetReward( + epoch: number, + supply: number, + generationPerSlot: number, +): number { + const finalReward = + supply * + REWARDS_MANA_SHARE_COEFFICIENT * + generationPerSlot * + SLOTS_IN_EPOCH; + const decayBalancingConstant = + Math.exp(1) / + (BOOTSTRAPPING_DURATION * + (1 - Math.exp(-EPOCH_DURATION_IN_YEARS * BETA_PER_YEAR))); + const initialReward = finalReward * decayBalancingConstant; + + let reward = 0; + if (epoch <= BOOTSTRAPPING_DURATION) { + reward = decay(initialReward, epoch); + } else { + reward = finalReward; + } + return reward; +} diff --git a/src/components/ManaCalculator/components/AdvancedSettingsValidator.tsx b/src/components/ManaCalculator/components/AdvancedSettingsValidator.tsx new file mode 100644 index 00000000000..e29f0ae995f --- /dev/null +++ b/src/components/ManaCalculator/components/AdvancedSettingsValidator.tsx @@ -0,0 +1,54 @@ +import { Details } from '@docusaurus/theme-common/Details'; +import React from 'react'; +import { useManaState } from '../hooks'; +import { ValidatorCard } from './ValidatorCard'; + +export function AdvancedSettingsValidator() { + const { handleAddValidator, state } = useManaState(); + + function onAddValidator() { + handleAddValidator({ + lockedStake: 100, + delegatedStake: 0, + performanceFactor: 1.0, + fixedCost: 0.0, + }); + } + + return ( +
+
+
+
ID
+
+ Stake ({state.network}) +
+
+ Delegated ({state.network}) +
+
+ Performance factor +
+
+ Fixed costs +
+
+
+ {state.validators.map((validator, i) => ( +
+ +
+ ))} +
+ +
+ ); +} diff --git a/src/components/ManaCalculator/components/BlocksAllowance.tsx b/src/components/ManaCalculator/components/BlocksAllowance.tsx new file mode 100644 index 00000000000..16b69c742a2 --- /dev/null +++ b/src/components/ManaCalculator/components/BlocksAllowance.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { + Area, + AreaChart, + CartesianGrid, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { stringifyUnit, Unit } from '../hooks'; +import { EpochReward } from '../types'; +import { chartTooltip } from './ChartTooltip'; + +export function BlocksAllowance({ + results, + unit, +}: { + results: EpochReward[]; + unit: Unit; +}) { + const unitString = stringifyUnit(unit); + return ( + <> +

Blocks Allowance

+ + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/ManaCalculator/components/ChartTooltip.tsx b/src/components/ManaCalculator/components/ChartTooltip.tsx new file mode 100644 index 00000000000..bd3e9563dbb --- /dev/null +++ b/src/components/ManaCalculator/components/ChartTooltip.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { TooltipProps } from 'recharts'; +import { getSizeOfUnit, Unit } from '../hooks'; + +export function chartTooltip(xLabel: string, yLabel: string, unit: Unit) { + const unitSize = getSizeOfUnit(unit); + + const ChartTooltip = ({ + active, + payload, + label, + }: TooltipProps) => { + if (active && payload && payload.length) { + const value = payload[0].value * unitSize; + return ( +
+

{`${xLabel}: ${label}`}

+

{`${yLabel}: ${value}`}

+
+ ); + } + + return null; + }; + + return ChartTooltip; +} diff --git a/src/components/ManaCalculator/components/ManaAcculation.tsx b/src/components/ManaCalculator/components/ManaAcculation.tsx new file mode 100644 index 00000000000..7b1faa850eb --- /dev/null +++ b/src/components/ManaCalculator/components/ManaAcculation.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { + Area, + AreaChart, + CartesianGrid, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { stringifyUnit, Unit } from '../hooks'; +import { EpochReward } from '../types'; +import { chartTooltip } from './ChartTooltip'; + +export function ManaAccumulation({ + results, + unit, +}: { + results: EpochReward[]; + unit: Unit; +}) { + const unitString = stringifyUnit(unit); + return ( + <> +

Mana Accumulation

+ + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/ManaCalculator/components/ManaCalculator.tsx b/src/components/ManaCalculator/components/ManaCalculator.tsx new file mode 100644 index 00000000000..c28102e6dd8 --- /dev/null +++ b/src/components/ManaCalculator/components/ManaCalculator.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react'; +import '../styles.css'; +import { NetworkType } from '../enums'; +import { + getDefaultParameters, + ManaStateContext, + useGivenManaState, + useResultsPerEpoch, + useResultsWithUnit, +} from '../hooks'; +import { + OutputForm, + NetworkSection, + RoleSection, + OtherParametersSection, + ManaAccumulation, + BlocksAllowance, +} from './'; + +export function ManaCalculator() { + const [state, setState] = useState(getDefaultParameters(NetworkType.IOTA)); + const manaState = useGivenManaState(state, setState); + const results = useResultsPerEpoch(manaState.state); + const { data, manaUnit, blocksUnit } = useResultsWithUnit(results); + return ( + +

Configuration

+ + + +
+

Results

+ + + +
+ ); +} diff --git a/src/components/ManaCalculator/components/NetworkSection.tsx b/src/components/ManaCalculator/components/NetworkSection.tsx new file mode 100644 index 00000000000..5bd4e0784ec --- /dev/null +++ b/src/components/ManaCalculator/components/NetworkSection.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { NetworkType } from '../enums'; +import { useManaState } from '../hooks'; +import Select from 'react-select'; + +export function NetworkSection() { + const { handleNetworkChange } = useManaState(); + return ( +
+

Network configuration

+ + { + handleCongestionChange(e.value); + }} + defaultValue={{ + value: CongestionType.LOW, + label: 'Low Congestion', + }} + options={[ + { value: CongestionType.LOW, label: 'Low Congestion' }, + { value: CongestionType.MEDIUM, label: 'Stable Price' }, + { value: CongestionType.HIGH, label: 'Extreme Congestion' }, + ]} + /> +
+ + handleInitialEpochChange(Number(e.target.value))} + > +
+ + handleFinalEpochChange(Number(e.target.value))} + > + +
+ ); +} diff --git a/src/components/ManaCalculator/components/OutputForm.tsx b/src/components/ManaCalculator/components/OutputForm.tsx new file mode 100644 index 00000000000..4a7a88340d0 --- /dev/null +++ b/src/components/ManaCalculator/components/OutputForm.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { useManaState, useResults } from '../hooks'; +import { fromMicro, roundMax } from '../utils'; +import humanizeDuration from 'humanize-duration'; + +export function OutputForm() { + const { state } = useManaState(); + const results = useResults(state); + const passiveRewards = roundMax(fromMicro(results.passiveRewards), 6); + const manaGenerated = roundMax(fromMicro(results.generatedRewards), 6); + const totalBPS = roundMax(results.totalBPS, 2); + const humanizer = humanizeDuration.humanizer({ + units: ['y', 'mo', 'w', 'd', 'h', 'm', 's', 'ms'], + maxDecimalPoints: 3, + }); + const msToTransaction = humanizer(results.msToTransaction); + const passiveMsToTransaction = humanizer(results.passiveMsToTransaction); + + return ( +
+
+ Mana generation (by holding):{' '} + {passiveRewards} +
+
+ Mana rewards: {manaGenerated} +
+
+ Total BPS granted: {totalBPS} +
+
+ Time it takes to accumulate enough mana for a standard transaction... +
+
+ ...as a delegator/validator:{' '} + {msToTransaction} +
+
+ ...as a holder:{' '} + {passiveMsToTransaction} +
+
+ ); +} diff --git a/src/components/ManaCalculator/components/RoleSection.tsx b/src/components/ManaCalculator/components/RoleSection.tsx new file mode 100644 index 00000000000..48c4bc26f21 --- /dev/null +++ b/src/components/ManaCalculator/components/RoleSection.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { useManaState } from '../hooks'; +import Select from 'react-select'; +import { UserType } from '../enums'; +import { fromMicro, toMicro } from '../utils'; +import { ValidatorSettings } from './ValidatorSettings'; + +export function RoleSection() { + const { + state, + handleOwnStakeChange, + handleUserChange, + handleValidatorChange, + handleOwnHoldChange, + } = useManaState(); + const validatorOptions = state.validators.map((_, i) => { + return { value: i, label: `Validator ${i + 1}` }; + }); + + return ( +
+

Role configuration

+ + handleOwnHoldChange(toMicro(Number(e.target.value)))} + > +
+ {state.userType === UserType.VALIDATOR ? ( + <> + + + handleOwnStakeChange(toMicro(Number(e.target.value))) + } + > +
+ + + ) : ( + <> + + + handleOwnStakeChange(toMicro(Number(e.target.value))) + } + > + + )} +
+ ); +} diff --git a/src/components/ManaCalculator/components/ValidatorCard.tsx b/src/components/ManaCalculator/components/ValidatorCard.tsx new file mode 100644 index 00000000000..f2826461bea --- /dev/null +++ b/src/components/ManaCalculator/components/ValidatorCard.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { useManaState } from '../hooks'; +import { ValidatorProps } from '../types'; +import { fromMicro, toMicro } from '../utils'; + +export function ValidatorCard({ + validator, + id, +}: { + validator: ValidatorProps; + id: number; +}) { + const { + handleDelete, + handleStakeChange, + handleDelegatedStakeChange, + handlePFChange, + handleFCChange, + } = useManaState(); + return ( + <> +
Validator {id + 1}
+ handleStakeChange(toMicro(Number(e.target.value)), id)} + > + + handleDelegatedStakeChange(toMicro(Number(e.target.value)), id) + } + > + handlePFChange(Number(e.target.value), id)} + > + handleFCChange(Number(e.target.value), id)} + > + + + ); +} diff --git a/src/components/ManaCalculator/components/ValidatorSettings.tsx b/src/components/ManaCalculator/components/ValidatorSettings.tsx new file mode 100644 index 00000000000..95be4df6508 --- /dev/null +++ b/src/components/ManaCalculator/components/ValidatorSettings.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { Details } from '@docusaurus/theme-common/Details'; +import { useManaState } from '../hooks'; + +export function ValidatorSettings() { + const { + state: { + validator: { + performanceFactor, + fixedCost, + shareOfYourStakeLocked, + attractedNewDelegatedStake, + attractedDelegatedStakeFromOtherPools, + }, + }, + handleOwnPFChange, + handleOwnFCChange, + handleShareOfYourStakeLockedChange, + handleAttractedNewDelegatedStakeChange, + handleAttractedDelegatedStakeFromOtherPoolsChange, + } = useManaState(); + return ( +
+ + handleOwnPFChange(Number(e.target.value))} + > +
+ + handleOwnFCChange(Number(e.target.value))} + > +
+ + + handleShareOfYourStakeLockedChange(Number(e.target.value)) + } + > +
+ + + handleAttractedNewDelegatedStakeChange(Number(e.target.value)) + } + > +
+ + + handleAttractedDelegatedStakeFromOtherPoolsChange( + Number(e.target.value), + ) + } + > +
+ ); +} diff --git a/src/components/ManaCalculator/components/index.ts b/src/components/ManaCalculator/components/index.ts new file mode 100644 index 00000000000..144baf9e54b --- /dev/null +++ b/src/components/ManaCalculator/components/index.ts @@ -0,0 +1,10 @@ +export * from './AdvancedSettingsValidator'; +export * from './ManaCalculator'; +export * from './OtherParametersSection'; +export * from './ValidatorCard'; +export * from './ValidatorSettings'; +export * from './RoleSection'; +export * from './OutputForm'; +export * from './NetworkSection'; +export * from './ManaAcculation'; +export * from './BlocksAllowance'; diff --git a/src/components/ManaCalculator/constants.ts b/src/components/ManaCalculator/constants.ts new file mode 100644 index 00000000000..3eeccf01891 --- /dev/null +++ b/src/components/ManaCalculator/constants.ts @@ -0,0 +1,51 @@ +import { CongestionType } from './enums/parameters.enum'; + +// GENERIC + +export const INITIAL_EPOCH = 1; +export const FINAL_EPOCH = 1000; +export const SLOT_DURATION = 10; +export const SLOTS_IN_EPOCH = 8192; +export const EPOCH_DURATION = SLOTS_IN_EPOCH * SLOT_DURATION; +export const SECONDS_IN_YEAR = 60 * 60 * 24 * 365; +export const EPOCH_DURATION_IN_YEARS = EPOCH_DURATION / SECONDS_IN_YEAR; +export const BETA_PER_YEAR = 1 / 3; +export const BOOTSTRAPPING_DURATION = 1154; +export const REWARDS_MANA_SHARE_COEFFICIENT = 2; +export const EPOCH = 1154 + 1; + +// IOTA + +export const IOTA_SUPPLY = 4600000000000000; + +export const IOTA_CONGESTION = { + [CongestionType.HIGH]: 350000000, + [CongestionType.MEDIUM]: 0, // Dynamic + [CongestionType.LOW]: 5000000, +}; + +export const IOTA_GENERATION_PER_SLOT = Math.pow(2, -17); + +export const IOTA_THROUGHPUT = 100; + +export const IOTA_HOLD = 600; // IOTA +export const IOTA_STAKED = 600; // IOTA +export const IOTA_DELEGATED = 600; // IOTA + +// SHIMMER + +export const SHIMMER_SUPPLY = 1813620509000000; + +export const SHIMMER_CONGESTION = { + [CongestionType.HIGH]: 350000000, + [CongestionType.MEDIUM]: 0, // Dynamic + [CongestionType.LOW]: 5000000, +}; + +export const SHIMMER_GENERATION_PER_SLOT = Math.pow(2, -16); + +export const SHIMMER_THROUGHPUT = 100; + +export const SHIMMER_HOLD = 600; // SMR +export const SHIMMER_STAKED = 600; // SMR +export const SHIMMER_DELEGATED = 600; // SMR diff --git a/src/components/ManaCalculator/enums/index.ts b/src/components/ManaCalculator/enums/index.ts new file mode 100644 index 00000000000..531ace0d0e0 --- /dev/null +++ b/src/components/ManaCalculator/enums/index.ts @@ -0,0 +1 @@ +export * from './parameters.enum'; diff --git a/src/components/ManaCalculator/enums/parameters.enum.ts b/src/components/ManaCalculator/enums/parameters.enum.ts new file mode 100644 index 00000000000..faff9332bdb --- /dev/null +++ b/src/components/ManaCalculator/enums/parameters.enum.ts @@ -0,0 +1,15 @@ +export enum UserType { + DELEGATOR, + VALIDATOR, +} + +export enum NetworkType { + IOTA = 'IOTA', + SHIMMER = 'SMR', +} + +export enum CongestionType { + LOW, + MEDIUM, + HIGH, +} diff --git a/src/components/ManaCalculator/hooks/index.ts b/src/components/ManaCalculator/hooks/index.ts new file mode 100644 index 00000000000..b442d6ec477 --- /dev/null +++ b/src/components/ManaCalculator/hooks/index.ts @@ -0,0 +1,4 @@ +export * from './useManaState'; +export * from './useResults'; +export * from './useResultsPerEpoch'; +export * from './useResultsWithUnit'; diff --git a/src/components/ManaCalculator/hooks/useManaState.ts b/src/components/ManaCalculator/hooks/useManaState.ts new file mode 100644 index 00000000000..92f5c47b90c --- /dev/null +++ b/src/components/ManaCalculator/hooks/useManaState.ts @@ -0,0 +1,276 @@ +import { createContext, useContext } from 'react'; +import { + FINAL_EPOCH, + INITIAL_EPOCH, + IOTA_DELEGATED, + IOTA_HOLD, + IOTA_STAKED, + SHIMMER_DELEGATED, + SHIMMER_HOLD, + SHIMMER_STAKED, +} from '../constants'; +import { CongestionType, NetworkType, UserType } from '../enums'; +import { ManaCalculatorProps, ManaState, ValidatorProps } from '../types'; +import { + getNetworkCongestion, + getNetworkGenerationPerSlot, + getNetworkSupply, + getStakedOrDelegated, + toMicro, +} from '../utils'; + +export const ManaStateContext = createContext(null); + +export function useManaState() { + const { setState, state } = useContext<{ + setState: (state: ManaCalculatorProps) => void; + state: ManaCalculatorProps; + }>(ManaStateContext); + + return useGivenManaState(state, setState); +} + +export function useGivenManaState( + state: ManaCalculatorProps, + setState: (state: ManaCalculatorProps) => void, +) { + function handleDelete(id: number) { + const validators = state.validators.filter((_, i) => i !== id); + setState({ ...state, validators }); + } + + function handleStakeChange(value: number, id: number) { + setState({ + ...state, + validators: state.validators.map((validator, i) => { + return { + ...validator, + lockedStake: i === id ? value : validator.lockedStake, + }; + }), + }); + } + + function handleDelegatedStakeChange(value: number, id: number) { + setState({ + ...state, + validators: state.validators.map((validator, i) => { + return { + ...validator, + delegatedStake: i === id ? value : validator.delegatedStake, + }; + }), + }); + } + + function handlePFChange(value: number, id: number) { + setState({ + ...state, + validators: state.validators.map((validator, i) => { + return { + ...validator, + performanceFactor: i === id ? value : validator.performanceFactor, + }; + }), + }); + } + + function handleFCChange(value: number, id: number) { + setState({ + ...state, + validators: state.validators.map((validator, i) => { + return { + ...validator, + fixedCost: i === id ? value : validator.fixedCost, + }; + }), + }); + } + + function handleOwnStakeChange(value: number) { + setState({ + ...state, + [getStakedOrDelegated(state.userType)]: value, + }); + } + + function handleValidatorChange(value: number) { + setState({ + ...state, + delegator: { ...state.delegator, validator: value }, + }); + } + + function handleCongestionChange(value: CongestionType) { + setState({ + ...state, + congestion: value, + }); + } + + function handleOwnPFChange(value: number) { + setState({ + ...state, + validator: { ...state.validator, performanceFactor: value }, + }); + } + + function handleOwnFCChange(value: number) { + setState({ + ...state, + validator: { ...state.validator, fixedCost: value }, + }); + } + + function handleShareOfYourStakeLockedChange(value: number) { + setState({ + ...state, + validator: { ...state.validator, shareOfYourStakeLocked: value }, + }); + } + + function handleAttractedNewDelegatedStakeChange(value: number) { + setState({ + ...state, + validator: { ...state.validator, attractedNewDelegatedStake: value }, + }); + } + + function handleAttractedDelegatedStakeFromOtherPoolsChange(value: number) { + setState({ + ...state, + validator: { + ...state.validator, + attractedDelegatedStakeFromOtherPools: value, + }, + }); + } + + function handleInitialEpochChange(value: number) { + setState({ + ...state, + initialEpoch: value, + }); + } + + function handleFinalEpochChange(value: number) { + setState({ + ...state, + finalEpoch: value, + }); + } + + function handleNetworkChange(value: NetworkType) { + setState({ + ...getDefaultParameters(value), + }); + } + + function handleUserChange(value: UserType) { + setState({ + ...state, + userType: value, + }); + } + + function handleAddValidator(value: ValidatorProps) { + state.validators.push(value); + setState({ ...state }); + } + + function handleOwnHoldChange(value: number) { + setState({ ...state, heldTokens: value }); + } + + const congestionAmount = getNetworkCongestion( + state.network, + state.congestion, + ); + const generationPerSlot = getNetworkGenerationPerSlot(state.network); + const stakedOrDelegatedTokens = state[getStakedOrDelegated(state.userType)]; + + return { + state: { + ...state, + congestionAmount, + generationPerSlot, + stakedOrDelegatedTokens, + } as ManaState, + handleDelete, + handleStakeChange, + handleAddValidator, + handleAttractedDelegatedStakeFromOtherPoolsChange, + handleAttractedNewDelegatedStakeChange, + handleCongestionChange, + handleDelegatedStakeChange, + handleFCChange, + handleInitialEpochChange, + handleNetworkChange, + handleFinalEpochChange, + handleOwnFCChange, + handleOwnPFChange, + handleUserChange, + handleShareOfYourStakeLockedChange, + handleOwnStakeChange, + handlePFChange, + handleValidatorChange, + handleOwnHoldChange, + }; +} + +export function getDefaultParameters( + network: NetworkType, +): ManaCalculatorProps { + const networkParams = { + [NetworkType.IOTA]: { + stakedTokens: toMicro(IOTA_STAKED), + delegatedTokens: toMicro(IOTA_DELEGATED), + heldTokens: toMicro(IOTA_HOLD), + }, + [NetworkType.SHIMMER]: { + stakedTokens: toMicro(SHIMMER_STAKED), + delegatedTokens: toMicro(SHIMMER_DELEGATED), + heldTokens: toMicro(SHIMMER_HOLD), + }, + }; + + const finalNetworkParams = networkParams[network]; + + return { + ...finalNetworkParams, + initialEpoch: INITIAL_EPOCH, + finalEpoch: FINAL_EPOCH, + validators: getValidators(network), + userType: UserType.DELEGATOR, + congestion: CongestionType.LOW, + delegator: { + validator: 0, + }, + validator: { + performanceFactor: 1.0, + fixedCost: 0.0, + shareOfYourStakeLocked: 100.0, + attractedNewDelegatedStake: finalNetworkParams.stakedTokens * 1.5, + attractedDelegatedStakeFromOtherPools: 0.1, + }, + network, + } as ManaCalculatorProps; +} + +export function getValidators(network: NetworkType): ValidatorProps[] { + const supply = getNetworkSupply(network); + + const delegated = [1.5, 1, 1.5, 2.0]; + + return delegated.flatMap((delegated) => { + const stake = [1, 1, 0.25, 0.5, 0.75, 1.12, 1.5, 1.75]; + return stake.map((stake) => { + return { + lockedStake: (supply * stake) / 100, + delegatedStake: (supply * delegated) / 100, + performanceFactor: 1.0, + fixedCost: 0.0, + }; + }); + }); +} diff --git a/src/components/ManaCalculator/hooks/useResults.ts b/src/components/ManaCalculator/hooks/useResults.ts new file mode 100644 index 00000000000..3d883be690e --- /dev/null +++ b/src/components/ManaCalculator/hooks/useResults.ts @@ -0,0 +1,57 @@ +import { + calculateManaRewards, + calculatePassiveRewards, + calculateBPS, +} from '../actions'; +import { UserType } from '../enums'; +import { ManaState, ValidatorParameters } from '../types'; + +export function useResults(state: ManaState) { + const passiveRewards = calculatePassiveRewards( + state.heldTokens, + state.initialEpoch, + state.finalEpoch, + state.generationPerSlot, + ); + + const passiveBPS = calculateBPS(passiveRewards, state.congestionAmount); + + const validatorParameters = + state.userType === UserType.VALIDATOR + ? ({ + performanceFactor: state.validator.performanceFactor, + fixedCost: state.validator.fixedCost, + shareOfYourStakeLocked: state.validator.shareOfYourStakeLocked, + attractedNewDelegatedStake: + state.validator.attractedNewDelegatedStake, + attractedDelegatedStakeFromOtherPools: + state.validator.attractedDelegatedStakeFromOtherPools, + } as ValidatorParameters) + : null; + + const generatedRewards = calculateManaRewards( + state.stakedOrDelegatedTokens, + state.delegator.validator, + validatorParameters, + state.validators, + state.initialEpoch, + state.finalEpoch, + state.userType, + state.network, + state.generationPerSlot, + ); + + const generatedBPS = calculateBPS(generatedRewards, state.congestionAmount); + const totalBPS = generatedBPS + passiveBPS; + + const msToTransaction = (1 / totalBPS) * 1_000; + const passiveMsToTransaction = (1 / passiveBPS) * 1_000; + + return { + generatedRewards, + passiveRewards, + totalBPS, + msToTransaction, + passiveMsToTransaction, + }; +} diff --git a/src/components/ManaCalculator/hooks/useResultsPerEpoch.ts b/src/components/ManaCalculator/hooks/useResultsPerEpoch.ts new file mode 100644 index 00000000000..063916fb200 --- /dev/null +++ b/src/components/ManaCalculator/hooks/useResultsPerEpoch.ts @@ -0,0 +1,65 @@ +import { + calculateManaRewards, + calculatePassiveRewards, + calculateBPS, +} from '../actions'; +import { UserType } from '../enums'; +import { EpochReward, ManaState, ValidatorParameters } from '../types'; +import { fromMicro } from '../utils'; + +export function useResultsPerEpoch(state: ManaState): EpochReward[] { + const validatorParameters = + state.userType === UserType.VALIDATOR + ? ({ + performanceFactor: state.validator.performanceFactor, + fixedCost: state.validator.fixedCost, + shareOfYourStakeLocked: state.validator.shareOfYourStakeLocked, + attractedNewDelegatedStake: + state.validator.attractedNewDelegatedStake, + attractedDelegatedStakeFromOtherPools: + state.validator.attractedDelegatedStakeFromOtherPools, + } as ValidatorParameters) + : null; + const results = []; + + for (let i = state.initialEpoch; i <= state.finalEpoch; i++) { + const generatedRewards = calculateManaRewards( + state.stakedOrDelegatedTokens, + state.delegator.validator, + validatorParameters, + state.validators, + state.initialEpoch, + i, + state.userType, + state.network, + state.generationPerSlot, + ); + + const passiveRewards = calculatePassiveRewards( + state.heldTokens, + state.initialEpoch, + i, + state.generationPerSlot, + ); + + const mana = generatedRewards + passiveRewards; + + const bpsFromPassiveRewards = calculateBPS( + passiveRewards, + state.congestionAmount, + ); + const bpsFromGeneratedMana = calculateBPS( + generatedRewards, + state.congestionAmount, + ); + const totalBps = bpsFromPassiveRewards + bpsFromGeneratedMana; + + results.push({ + epoch: i, + mana: fromMicro(mana), + totalBps, + }); + } + + return results; +} diff --git a/src/components/ManaCalculator/hooks/useResultsWithUnit.ts b/src/components/ManaCalculator/hooks/useResultsWithUnit.ts new file mode 100644 index 00000000000..8e170bf2a0a --- /dev/null +++ b/src/components/ManaCalculator/hooks/useResultsWithUnit.ts @@ -0,0 +1,84 @@ +import { EpochReward } from '../types/epoch-reward.type'; + +export enum Unit { + default = 'default', + K = 'K', + M = 'M', + G = 'G', + T = 'T', + P = 'P', +} + +const UNIT_MAP: { [unit in Unit]: { val: number; dp: number } } = { + default: { val: 1, dp: 0 }, + K: { val: 1000, dp: 3 }, + M: { val: 1000000, dp: 6 }, + G: { val: 1000000000, dp: 9 }, + T: { val: 1000000000000, dp: 12 }, + P: { val: 1000000000000000, dp: 15 }, +}; + +export const stringifyUnit = (unit: Unit): string => { + if (unit == Unit.default) { + return ''; + } else { + return unit; + } +}; + +const getUnit = (value: number): Unit => { + let bestUnits: Unit = Unit.default; + + const checkLength = Math.abs(value).toFixed(0).replace('.', '').length; + console.log(checkLength, value); + if (checkLength > UNIT_MAP.M.dp) { + bestUnits = Unit.M; + } else if (checkLength > UNIT_MAP.K.dp) { + bestUnits = Unit.K; + } else if (checkLength > UNIT_MAP.G.dp) { + bestUnits = Unit.G; + } else if (checkLength > UNIT_MAP.M.dp) { + bestUnits = Unit.M; + } else if (checkLength > UNIT_MAP.K.dp) { + bestUnits = Unit.K; + } + + return bestUnits; +}; + +interface UseResultsWithUnit { + data: EpochReward[]; + manaUnit: Unit; + blocksUnit: Unit; +} + +export function getSizeOfUnit(unit: Unit): number { + return UNIT_MAP[unit].val; +} + +export function useResultsWithUnit(results: EpochReward[]): UseResultsWithUnit { + const averageMana = + results.reduce((acc, reward) => acc + reward.mana, 0) / results.length; + const averageBlocks = + results.reduce((acc, reward) => acc + reward.totalBps, 0) / results.length; + + const manaUnit = getUnit(averageMana); + const blocksUnit = getUnit(averageBlocks); + + const manaSize = getSizeOfUnit(manaUnit); + const blocksSize = getSizeOfUnit(blocksUnit); + + const data = results.map((reward) => { + return { + ...reward, + mana: reward.mana / manaSize, + totalTps: reward.totalBps / blocksSize, + }; + }); + + return { + data, + manaUnit, + blocksUnit, + }; +} diff --git a/src/components/ManaCalculator/index.ts b/src/components/ManaCalculator/index.ts new file mode 100644 index 00000000000..067e1f026f5 --- /dev/null +++ b/src/components/ManaCalculator/index.ts @@ -0,0 +1 @@ +export { ManaCalculator as default } from './components/ManaCalculator'; diff --git a/src/components/ManaCalculator/styles.css b/src/components/ManaCalculator/styles.css new file mode 100644 index 00000000000..e95fa997a03 --- /dev/null +++ b/src/components/ManaCalculator/styles.css @@ -0,0 +1,113 @@ +.mana--card { + box-sizing: border-box; +} +.row > .button { + margin: 0; +} + +.table .row { + background: var(--ifm-table-stripe-background); + border: var(--ifm-table-border-width) solid var(--ifm-table-border-color); +} + +.table .row .col { + box-sizing: border-box; +} + +.table .row .add-button { + margin: 0 calc(50% - var(--ifm-navbar-padding-horizontal)); +} + +.table .row .col--4:nth-child(1), +.table .row .col--8:nth-child(1), +.table .row .col--4:nth-child(2), +.table .row .col--6:nth-child(1) { + border-right: var(--ifm-table-border-width) solid + var(--ifm-table-border-color); +} + +.table .row:nth-child(even) { + background: var(--ifm-table-cell-color); +} + +.mana_calculator__card { + background-color: var(--ifm-color-emphasis-100); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: var(--ifm-global-radius); + color: inherit; + transition: border-color 200ms ease-in-out; + padding: 15px; + margin: 10px 0px; +} + +.mana_calculator_inner__card { + background-color: var(--ifm-color-emphasis-0); + box-shadow: none; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: var(--ifm-global-radius); + color: inherit; + transition: border-color 200ms ease-in-out; + padding: 15px; + margin: 10px 0px; +} + +.mana_calculator__card input:not([role$='combobox']) { + background-color: var(--ifm-color-emphasis-200) !important; + padding: 10px; + border-radius: 4px; + border: none; +} + +.input--vertical-spaced { + margin-bottom: 1rem; +} + +.horizontal-spaced { + margin-right: 5px; +} + +.button--remove { + padding: 10px; +} + +.row--centered { + display: flex; + align-items: center; +} + +.mana-calculator__tooltip { + background-color: var(--ifm-color-emphasis-100); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: var(--ifm-global-radius); + color: inherit; + padding: 10px; +} + +.mana-calculator__button { + background-color: var(--ifm-color-emphasis-100); + margin: 0px; + margin-top: 1rem; +} + +.mana-calculator__tooltip > p { + margin: 0; +} + +.inlined-label { + display: inline-block; + width: 200px; +} + +.inlined-long-label { + display: inline-block; + width: 350px; +} + +.small-row-head { + font-size: 15px; +} + +.mana_calculator__compact { + width: 200px; + margin-bottom: 1rem !important; +} diff --git a/src/components/ManaCalculator/types/epoch-reward.type.ts b/src/components/ManaCalculator/types/epoch-reward.type.ts new file mode 100644 index 00000000000..7cdaba7dba9 --- /dev/null +++ b/src/components/ManaCalculator/types/epoch-reward.type.ts @@ -0,0 +1,5 @@ +export interface EpochReward { + epoch: number; + mana: number; + totalBps: number; +} diff --git a/src/components/ManaCalculator/types/index.ts b/src/components/ManaCalculator/types/index.ts new file mode 100644 index 00000000000..1dca01c9553 --- /dev/null +++ b/src/components/ManaCalculator/types/index.ts @@ -0,0 +1,4 @@ +export * from './mana-calculator.type'; +export * from './validator.type'; +export * from './epoch-reward.type'; +export * from './mana-state.type'; diff --git a/src/components/ManaCalculator/types/mana-calculator.type.ts b/src/components/ManaCalculator/types/mana-calculator.type.ts new file mode 100644 index 00000000000..c4467fc8e9f --- /dev/null +++ b/src/components/ManaCalculator/types/mana-calculator.type.ts @@ -0,0 +1,25 @@ +import { CongestionType, NetworkType, UserType } from '../enums'; +import { ValidatorProps } from './validator.type'; + +export interface ManaCalculatorProps { + epoch: number; + initialEpoch: number; + finalEpoch: number; + validators: ValidatorProps[]; + userType: UserType; + congestion: CongestionType; + delegator: { + validator: number; + }; + validator: { + shareOfYourStakeLocked: number; + attractedNewDelegatedStake: number; + attractedDelegatedStakeFromOtherPools: number; + fixedCost: number; + performanceFactor: number; + }; + stakedTokens: number; + delegatedTokens: number; + heldTokens: number; + network: NetworkType; +} diff --git a/src/components/ManaCalculator/types/mana-state.type.ts b/src/components/ManaCalculator/types/mana-state.type.ts new file mode 100644 index 00000000000..3361060224c --- /dev/null +++ b/src/components/ManaCalculator/types/mana-state.type.ts @@ -0,0 +1,7 @@ +import { ManaCalculatorProps } from './mana-calculator.type'; + +export interface ManaState extends ManaCalculatorProps { + congestionAmount: number; + generationPerSlot: number; + stakedOrDelegatedTokens: number; +} diff --git a/src/components/ManaCalculator/types/validator.type.ts b/src/components/ManaCalculator/types/validator.type.ts new file mode 100644 index 00000000000..0de049520b5 --- /dev/null +++ b/src/components/ManaCalculator/types/validator.type.ts @@ -0,0 +1,14 @@ +export interface ValidatorProps { + lockedStake: number; + delegatedStake: number; + performanceFactor: number; + fixedCost: number; +} + +export interface ValidatorParameters { + shareOfYourStakeLocked: number; + attractedNewDelegatedStake: number; + attractedDelegatedStakeFromOtherPools: number; + fixedCost: number; + performanceFactor: number; +} diff --git a/src/components/ManaCalculator/utils.ts b/src/components/ManaCalculator/utils.ts new file mode 100644 index 00000000000..9a6ca572255 --- /dev/null +++ b/src/components/ManaCalculator/utils.ts @@ -0,0 +1,90 @@ +import { + IOTA_CONGESTION, + IOTA_GENERATION_PER_SLOT, + IOTA_SUPPLY, + IOTA_THROUGHPUT, + SHIMMER_CONGESTION, + SHIMMER_GENERATION_PER_SLOT, + SHIMMER_SUPPLY, + SHIMMER_THROUGHPUT, + SLOTS_IN_EPOCH, +} from './constants'; +import { CongestionType, NetworkType, UserType } from './enums'; + +// Given a slot, returns the epoch that slot belongs to +export function slotToEpoch(slot: number): number { + const epoch = Math.floor(slot / SLOTS_IN_EPOCH); + return epoch; +} + +// Returns the first slot of the epoch +export function getFirstSlotOfEpoch(epoch: number): number { + const slot = epoch * SLOTS_IN_EPOCH; + return slot; +} + +// Returns The number of Mana/IOTA from micros +export function fromMicro(n: number): number { + return n / 1_000_000; +} + +// Returns The number of micro(Mana/IOTA). +export function toMicro(n: number): number { + return n * 1_000_000; +} + +// Returns the total supply given the network type +export function getNetworkSupply(network: NetworkType): number { + if (network == NetworkType.IOTA) { + return IOTA_SUPPLY; + } else { + return SHIMMER_SUPPLY; + } +} + +export function getNetworkCongestion( + network: NetworkType, + congestionType: CongestionType, +): number { + if (congestionType === CongestionType.MEDIUM) { + const supply = getNetworkSupply(network); + const generation = getNetworkGenerationPerSlot(network); + const throughput = getNetworkThroughput(network); + + return (supply * generation) / (10 * throughput); + } else { + if (network == NetworkType.IOTA) { + return IOTA_CONGESTION[congestionType]; + } else { + return SHIMMER_CONGESTION[congestionType]; + } + } +} + +export function getNetworkGenerationPerSlot(network: NetworkType): number { + if (network == NetworkType.IOTA) { + return IOTA_GENERATION_PER_SLOT; + } else { + return SHIMMER_GENERATION_PER_SLOT; + } +} + +export function getNetworkThroughput(network: NetworkType): number { + if (network == NetworkType.IOTA) { + return IOTA_THROUGHPUT; + } else { + return SHIMMER_THROUGHPUT; + } +} + +export function getStakedOrDelegated(userType: UserType) { + if (userType == UserType.DELEGATOR) { + return 'delegatedTokens'; + } else { + return 'stakedTokens'; + } +} + +export const roundMax = function (num: number, places: number) { + return +(Math.round(Number(num + 'e+' + places)) + 'e-' + places); +}; diff --git a/src/components/TutorialFilters/index.tsx b/src/components/TutorialFilters/index.tsx index 11bf5ed4e2b..e4055bc0865 100644 --- a/src/components/TutorialFilters/index.tsx +++ b/src/components/TutorialFilters/index.tsx @@ -218,6 +218,7 @@ function TutorialFilters() { options={tags} {...selectProps} value={value} + className='react-select-spaced__control' /> ); diff --git a/src/components/TutorialFilters/styles.css b/src/components/TutorialFilters/styles.css index 79141940fad..6bf6a194b53 100644 --- a/src/components/TutorialFilters/styles.css +++ b/src/components/TutorialFilters/styles.css @@ -57,10 +57,13 @@ cursor: pointer !important; background-color: var(--ifm-color-emphasis-200) !important; border: 0 !important; - margin-bottom: 1rem !important; border-radius: 6px !important; } +.react-select-spaced__control { + margin-bottom: 1rem !important; +} + .react-select__indicator { cursor: pointer; color: var(--ifm-font-color-base) !important; diff --git a/src/css/custom.css b/src/css/custom.css index c16c36d0842..a41d4ad16b2 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -468,3 +468,55 @@ ul ul .table-of-contents__link { .hide-code-block-extras a { display: none; } + +.align-right { + text-align: right; +} + +.align-center { + text-align: center; +} + +.inlined { + display: inline-block; +} + +.react-select__control { + background-color: var(--ifm-color-emphasis-200) !important; + border: 0 !important; + margin: 0 !important; +} + +.react-select__placeholder { + color: var(--ifm-font-color-base) !important; +} + +.react-select__indicator { + color: var(--ifm-font-color-base) !important; +} + +.react-select__indicator-separator { + background-color: transparent !important; +} + +.react-select__menu { + background-color: var(--ifm-color-emphasis-200) !important; + z-index: 9999; +} + +.react-select__option--is-focused { + background-color: var(--ifm-color-emphasis-300) !important; +} + +.react-select__multi-value { + background-color: var(--ifm-background-color) !important; +} + +.react-select__single-value, +.react-select__multi-value__label { + color: var(--ifm-font-color-base) !important; +} + +.recharts-text { + fill: var(--ifm-menu-color) !important; +} diff --git a/yarn.lock b/yarn.lock index 708464eb3c3..8da2d956e39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2835,6 +2835,7 @@ __metadata: flickity-fade: ^2.0.0 globby: ^13.1.4 hast-util-is-element: 1.1.0 + humanize-duration: ^3.30.0 plugin-image-zoom: flexanalytics/plugin-image-zoom prettier: ^2.8.8 raw-loader: ^4.0.2 @@ -2845,6 +2846,7 @@ __metadata: react-player: ^2.11.2 react-popper: ^2.3.0 react-select: ^5.7.7 + recharts: ^2.9.2 rehype-jargon: 3.0.0 rehype-katex: 4 rehype-lodash-template: ^0.2.1 @@ -4678,6 +4680,75 @@ __metadata: languageName: node linkType: hard +"@types/d3-array@npm:^3.0.3": + version: 3.0.9 + resolution: "@types/d3-array@npm:3.0.9" + checksum: 5845a39e1c1063664bce6f1f2640ea54ed9f6ce81f3a72b1770fc58a8bd62131abfeb53b4af84b8bde36696adb87023a04d0876d7059b98fb07fb139248c7769 + languageName: node + linkType: hard + +"@types/d3-color@npm:*": + version: 3.1.2 + resolution: "@types/d3-color@npm:3.1.2" + checksum: e8f445a2b8c49f4f6ce53ef6bf49858902947936097140e6773411deb80db694cb2103ca62363aebf5a690986470a498756199473cd8ab056c3b79bc881eca0e + languageName: node + linkType: hard + +"@types/d3-ease@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/d3-ease@npm:3.0.1" + checksum: 2b6d98a9d9f3e25b2268633fcc3d936ca6071c7f34e739e3985215fcd7bbda17bcaae98ac9ed0c0637ca2d5b906dc46326ef5a4d9926be46b46b9f321449a0b9 + languageName: node + linkType: hard + +"@types/d3-interpolate@npm:^3.0.1": + version: 3.0.3 + resolution: "@types/d3-interpolate@npm:3.0.3" + dependencies: + "@types/d3-color": "*" + checksum: 57b6c83f109a33fff8bfc7265e4cfe6608f7509d6479808176518b115fa5f245b94f4d77bf4677218e41d48585a331cef54c1a5cb9ce5d1b5d7226b4d453f0a0 + languageName: node + linkType: hard + +"@types/d3-path@npm:*": + version: 3.0.1 + resolution: "@types/d3-path@npm:3.0.1" + checksum: 27bdf617166abb08a12d147d5d1e508598a953636cc9e963c3553daf4acbdee0dbce6df0930e56fddabdddc4d421f97e9639eabeb8a27795fcf9845c6a46c3b4 + languageName: node + linkType: hard + +"@types/d3-scale@npm:^4.0.2": + version: 4.0.6 + resolution: "@types/d3-scale@npm:4.0.6" + dependencies: + "@types/d3-time": "*" + checksum: 1c76c74b0333e8a1e0a2e45d26a22a448808b5381f72359b4442d02d7c77dae976c03559569e8347635adfa77fe781a70717e331b819064aeea7b9e24028c1aa + languageName: node + linkType: hard + +"@types/d3-shape@npm:^3.1.0": + version: 3.1.4 + resolution: "@types/d3-shape@npm:3.1.4" + dependencies: + "@types/d3-path": "*" + checksum: 3d529cc96127d8d58da05bc602e51bb7946171bdae96dcce9dae81c1549d3fd3f226a4e651c3302951a1ebf7803e86388c4afcdd654b0d40d203219ce68eb383 + languageName: node + linkType: hard + +"@types/d3-time@npm:*, @types/d3-time@npm:^3.0.0": + version: 3.0.2 + resolution: "@types/d3-time@npm:3.0.2" + checksum: c70538a22902f72aef9d0237c120859cec38bd20fd839870f81d8a81f155aa5876a180fae691a75e9c8e427172d3d3f8a8ef9e8c8c2a8202b24c1d801193c5e7 + languageName: node + linkType: hard + +"@types/d3-timer@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/d3-timer@npm:3.0.1" + checksum: e6b986d2e397ea38babd6799b52fb46b5d8554e2a383a8d551b803996a6470032e5c57a92ff961c5fdef71ce2b34ff206b42e9e337edc3b331b898c06d73062b + languageName: node + linkType: hard + "@types/debug@npm:^4.0.0": version: 4.1.8 resolution: "@types/debug@npm:4.1.8" @@ -6869,6 +6940,13 @@ __metadata: languageName: node linkType: hard +"classnames@npm:^2.2.5": + version: 2.3.2 + resolution: "classnames@npm:2.3.2" + checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e + languageName: node + linkType: hard + "clean-css@npm:^5.2.2, clean-css@npm:^5.3.0": version: 5.3.2 resolution: "clean-css@npm:5.3.2" @@ -7822,6 +7900,99 @@ __metadata: languageName: node linkType: hard +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6": + version: 3.2.4 + resolution: "d3-array@npm:3.2.4" + dependencies: + internmap: 1 - 2 + checksum: a5976a6d6205f69208478bb44920dd7ce3e788c9dceb86b304dbe401a4bfb42ecc8b04c20facde486e9adcb488b5d1800d49393a3f81a23902b68158e12cddd0 + languageName: node + linkType: hard + +"d3-color@npm:1 - 3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 4931fbfda5d7c4b5cfa283a13c91a954f86e3b69d75ce588d06cde6c3628cebfc3af2069ccf225e982e8987c612aa7948b3932163ce15eb3c11cd7c003f3ee3b + languageName: node + linkType: hard + +"d3-ease@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 06e2ee5326d1e3545eab4e2c0f84046a123dcd3b612e68858219aa034da1160333d9ce3da20a1d3486d98cb5c2a06f7d233eee1bc19ce42d1533458bd85dedcd + languageName: node + linkType: hard + +"d3-format@npm:1 - 3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: f345ec3b8ad3cab19bff5dead395bd9f5590628eb97a389b1dd89f0b204c7c4fc1d9520f13231c2c7cf14b7c9a8cf10f8ef15bde2befbab41454a569bd706ca2 + languageName: node + linkType: hard + +"d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: 1 - 3 + checksum: a42ba314e295e95e5365eff0f604834e67e4a3b3c7102458781c477bd67e9b24b6bb9d8e41ff5521050a3f2c7c0c4bbbb6e187fd586daa3980943095b267e78b + languageName: node + linkType: hard + +"d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: 2306f1bd9191e1eac895ec13e3064f732a85f243d6e627d242a313f9777756838a2215ea11562f0c7630c7c3b16a19ec1fe0948b1c82f3317fac55882f6ee5d8 + languageName: node + linkType: hard + +"d3-scale@npm:^4.0.2": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" + dependencies: + d3-array: 2.10.0 - 3 + d3-format: 1 - 3 + d3-interpolate: 1.2.0 - 3 + d3-time: 2.1.1 - 3 + d3-time-format: 2 - 4 + checksum: a9c770d283162c3bd11477c3d9d485d07f8db2071665f1a4ad23eec3e515e2cefbd369059ec677c9ac849877d1a765494e90e92051d4f21111aa56791c98729e + languageName: node + linkType: hard + +"d3-shape@npm:^3.1.0": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" + dependencies: + d3-path: ^3.1.0 + checksum: de2af5fc9a93036a7b68581ca0bfc4aca2d5a328aa7ba7064c11aedd44d24f310c20c40157cb654359d4c15c3ef369f95ee53d71221017276e34172c7b719cfa + languageName: node + linkType: hard + +"d3-time-format@npm:2 - 4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" + dependencies: + d3-time: 1 - 3 + checksum: 7342bce28355378152bbd4db4e275405439cabba082d9cd01946d40581140481c8328456d91740b0fe513c51ec4a467f4471ffa390c7e0e30ea30e9ec98fcdf4 + languageName: node + linkType: hard + +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:^3.0.0": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" + dependencies: + d3-array: 2 - 3 + checksum: 613b435352a78d9f31b7f68540788186d8c331b63feca60ad21c88e9db1989fe888f97f242322ebd6365e45ec3fb206a4324cd4ca0dfffa1d9b5feb856ba00a7 + languageName: node + linkType: hard + +"d3-timer@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 1cfddf86d7bca22f73f2c427f52dfa35c49f50d64e187eb788dcad6e927625c636aa18ae4edd44d084eb9d1f81d8ca4ec305dae7f733c15846a824575b789d73 + languageName: node + linkType: hard + "debug@npm:2.6.9, debug@npm:^2.6.0": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -7859,6 +8030,13 @@ __metadata: languageName: node linkType: hard +"decimal.js-light@npm:^2.4.1": + version: 2.5.1 + resolution: "decimal.js-light@npm:2.5.1" + checksum: f5a2c7eac1c4541c8ab8a5c8abea64fc1761cefc7794bd5f8afd57a8a78d1b51785e0c4e4f85f4895a043eaa90ddca1edc3981d1263eb6ddce60f32bf5fe66c9 + languageName: node + linkType: hard + "decode-named-character-reference@npm:^1.0.0": version: 1.0.2 resolution: "decode-named-character-reference@npm:1.0.2" @@ -8234,6 +8412,15 @@ __metadata: languageName: node linkType: hard +"dom-helpers@npm:^3.4.0": + version: 3.4.0 + resolution: "dom-helpers@npm:3.4.0" + dependencies: + "@babel/runtime": ^7.1.2 + checksum: 58d9f1c4a96daf77eddc63ae1236b826e1cddd6db66bbf39b18d7e21896d99365b376593352d52a60969d67fa4a8dbef26adc1439fa2c1b355efa37cacbaf637 + languageName: node + linkType: hard + "dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.2.1": version: 5.2.1 resolution: "dom-helpers@npm:5.2.1" @@ -8928,7 +9115,7 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.0": +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.1": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" checksum: 1875311c42fcfe9c707b2712c32664a245629b42bb0a5a84439762dd0fd637fc54d078155ea83c2af9e0323c9ac13687e03cfba79b03af9f40c89b4960099374 @@ -9062,6 +9249,13 @@ __metadata: languageName: node linkType: hard +"fast-equals@npm:^5.0.0": + version: 5.0.1 + resolution: "fast-equals@npm:5.0.1" + checksum: fbb3b6a74f3a0fa930afac151ff7d01639159b4fddd2678b5d50708e0ba38e9ec14602222d10dadb8398187342692c04fbef5a62b1cfcc7942fe03e754e064bc + languageName: node + linkType: hard + "fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": version: 3.3.0 resolution: "fast-glob@npm:3.3.0" @@ -10559,6 +10753,13 @@ __metadata: languageName: node linkType: hard +"humanize-duration@npm:^3.30.0": + version: 3.30.0 + resolution: "humanize-duration@npm:3.30.0" + checksum: 6eaf888219801d47d42cfb03523e736367c260e3f32cddb4e30c30f49500e08e7cdfd413f02a5ed24943ef382b3e89c8a11e6eda3a432846385903a9f49e576a + languageName: node + linkType: hard + "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -10855,6 +11056,13 @@ __metadata: languageName: node linkType: hard +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 7ca41ec6aba8f0072fc32fa8a023450a9f44503e2d8e403583c55714b25efd6390c38a87161ec456bf42d7bc83aab62eb28f5aef34876b1ac4e60693d5e1d241 + languageName: node + linkType: hard + "interpret@npm:^1.0.0": version: 1.4.0 resolution: "interpret@npm:1.4.0" @@ -15154,7 +15362,7 @@ plugin-image-zoom@flexanalytics/plugin-image-zoom: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0": +"react-is@npm:^16.10.2, react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -15365,6 +15573,18 @@ plugin-image-zoom@flexanalytics/plugin-image-zoom: languageName: node linkType: hard +"react-resize-detector@npm:^8.0.4": + version: 8.1.0 + resolution: "react-resize-detector@npm:8.1.0" + dependencies: + lodash: ^4.17.21 + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 45e6b87ea7331406bed2a806d0cea98c1467d53a7cfcdf19c2dd55a3460047917d3b175d9cceea6f314b65eb54858cbb981acffd007d67aa16388e517dafb83e + languageName: node + linkType: hard + "react-router-config@npm:^5.1.1": version: 5.1.1 resolution: "react-router-config@npm:5.1.1" @@ -15433,6 +15653,20 @@ plugin-image-zoom@flexanalytics/plugin-image-zoom: languageName: node linkType: hard +"react-smooth@npm:^2.0.4": + version: 2.0.5 + resolution: "react-smooth@npm:2.0.5" + dependencies: + fast-equals: ^5.0.0 + react-transition-group: 2.9.0 + peerDependencies: + prop-types: ^15.6.0 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 914c17f741e8b533ff6e3d5e3285aea0625cdd0f98e04202d01351f9516dbdc0a0e297dc22cc2377d6916fb819da8d4ed999c0314a4c186592ca51870012e6f7 + languageName: node + linkType: hard + "react-textarea-autosize@npm:^8.3.2": version: 8.5.2 resolution: "react-textarea-autosize@npm:8.5.2" @@ -15446,6 +15680,21 @@ plugin-image-zoom@flexanalytics/plugin-image-zoom: languageName: node linkType: hard +"react-transition-group@npm:2.9.0": + version: 2.9.0 + resolution: "react-transition-group@npm:2.9.0" + dependencies: + dom-helpers: ^3.4.0 + loose-envify: ^1.4.0 + prop-types: ^15.6.2 + react-lifecycles-compat: ^3.0.4 + peerDependencies: + react: ">=15.0.0" + react-dom: ">=15.0.0" + checksum: d8c9e50aabdc2cfc324e5cdb0ad1c6eecb02e1c0cd007b26d5b30ccf49015e900683dd489348c71fba4055858308d9ba7019e0d37d0e8d37bd46ed098788f670 + languageName: node + linkType: hard + "react-transition-group@npm:^4.3.0, react-transition-group@npm:^4.4.2": version: 4.4.5 resolution: "react-transition-group@npm:4.4.5" @@ -15547,6 +15796,36 @@ plugin-image-zoom@flexanalytics/plugin-image-zoom: languageName: node linkType: hard +"recharts-scale@npm:^0.4.4": + version: 0.4.5 + resolution: "recharts-scale@npm:0.4.5" + dependencies: + decimal.js-light: ^2.4.1 + checksum: e970377190a610e684a32c7461c7684ac3603c2e0ac0020bbba1eea9d099b38138143a8e80bf769bb49c0b7cecf22a2f5c6854885efed2d56f4540d4aa7052bd + languageName: node + linkType: hard + +"recharts@npm:^2.9.2": + version: 2.9.2 + resolution: "recharts@npm:2.9.2" + dependencies: + classnames: ^2.2.5 + eventemitter3: ^4.0.1 + lodash: ^4.17.19 + react-is: ^16.10.2 + react-resize-detector: ^8.0.4 + react-smooth: ^2.0.4 + recharts-scale: ^0.4.4 + tiny-invariant: ^1.3.1 + victory-vendor: ^36.6.8 + peerDependencies: + prop-types: ^15.6.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 77a87e3d91229ac5400240409568e3345ded50fc117e70e43e61a135b44bbc3164048704393aa988201ea2989278e1c4e96ce350f6a2f87044d1f0a48f290e84 + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -17438,7 +17717,7 @@ plugin-image-zoom@flexanalytics/plugin-image-zoom: languageName: node linkType: hard -"tiny-invariant@npm:^1.0.2": +"tiny-invariant@npm:^1.0.2, tiny-invariant@npm:^1.3.1": version: 1.3.1 resolution: "tiny-invariant@npm:1.3.1" checksum: 872dbd1ff20a21303a2fd20ce3a15602cfa7fcf9b228bd694a52e2938224313b5385a1078cb667ed7375d1612194feaca81c4ecbe93121ca1baebe344de4f84c @@ -18614,6 +18893,28 @@ plugin-image-zoom@flexanalytics/plugin-image-zoom: languageName: node linkType: hard +"victory-vendor@npm:^36.6.8": + version: 36.6.11 + resolution: "victory-vendor@npm:36.6.11" + dependencies: + "@types/d3-array": ^3.0.3 + "@types/d3-ease": ^3.0.0 + "@types/d3-interpolate": ^3.0.1 + "@types/d3-scale": ^4.0.2 + "@types/d3-shape": ^3.1.0 + "@types/d3-time": ^3.0.0 + "@types/d3-timer": ^3.0.0 + d3-array: ^3.1.6 + d3-ease: ^3.0.1 + d3-interpolate: ^3.0.1 + d3-scale: ^4.0.2 + d3-shape: ^3.1.0 + d3-time: ^3.0.0 + d3-timer: ^3.0.1 + checksum: 55800076dfa6abedf7758840986a302778a904678d4b66fe47d977c48b6f9484276b780871e6e5105b31c1eb936e9f1331ee39afcc2869bf65ceb7d456143172 + languageName: node + linkType: hard + "vm-browserify@npm:^1.1.2": version: 1.1.2 resolution: "vm-browserify@npm:1.1.2"