Skip to content

Commit

Permalink
wip calculator
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita-Polyakov committed Jul 20, 2022
1 parent 923ff8f commit f50f6ce
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 61 deletions.
166 changes: 157 additions & 9 deletions src/modules/demeterFarming/components/CalculatorDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,170 @@
<template v-if="baseAsset">{{ baseAsset.symbol }}-</template>{{ poolAsset.symbol }}
</span>
</s-row>

<s-form class="el-form--actions" :show-message="false">
<token-input
v-if="baseAsset"
:balance="baseAssetBalance.toCodecString()"
:is-max-available="isBaseAssetMaxButtonAvailable"
:title="t('demeterFarming.amountAdd')"
:token="baseAsset"
:value="baseAssetValue"
@input="handleBaseAssetValue"
@max="handleBaseAssetMax"
/>

<s-icon v-if="baseAsset && poolAsset" class="icon-divider" name="plus-16" />

<token-input
v-if="poolAsset"
:balance="poolAssetBalance.toCodecString()"
:is-max-available="isPoolAssetMaxButtonAvailable"
:title="t('demeterFarming.amountAdd')"
:token="poolAsset"
:value="poolAssetValue"
@input="handlePoolAssetValue"
@max="handlePoolAssetMax"
/>
</s-form>

<div class="duration">
<info-line label="Duration" class="duration-title" />
<s-tabs type="rounded" :value="selectedPeriod" @click="selectPeriod" class="duration-tabs">
<s-tab v-for="period in intervals" :key="period" :name="String(period)" :label="`${period}D`" />
</s-tabs>
</div>

<div class="results">
<div class="results-title">APR Results</div>

<info-line label="ROI" />
<info-line :label="rewardsText" />
</div>
</div>
</dialog-base>
</template>

<script lang="ts">
import { Component, Mixins } from 'vue-property-decorator';
import { components, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web';
import { Component, Mixins, Watch } from 'vue-property-decorator';
import { components } from '@soramitsu/soraneo-wallet-web';
import { FPNumber } from '@sora-substrate/util';
import type { AccountAsset } from '@sora-substrate/util/build/assets/types';
import PoolMixin from '../mixins/PoolMixin';
import StakeDialogMixin from '../mixins/StakeDialogMixin';
import TranslationMixin from '@/components/mixins/TranslationMixin';
import DialogMixin from '@/components/mixins/DialogMixin';
import DialogBase from '@/components/DialogBase.vue';
import { lazyComponent } from '@/router';
import { Components } from '@/consts';
import { isMaxButtonAvailable, getMaxValue } from '@/utils';
import { getter } from '@/store/decorators';
@Component({
components: {
DialogBase,
PairTokenLogo: lazyComponent(Components.PairTokenLogo),
TokenInput: lazyComponent(Components.TokenInput),
InfoLine: components.InfoLine,
TokenLogo: components.TokenLogo,
FormattedAmount: components.FormattedAmount,
},
})
export default class CalculatorDialog extends Mixins(PoolMixin, TranslationMixin, DialogMixin) {
readonly FontSizeRate = WALLET_CONSTS.FontSizeRate;
export default class CalculatorDialog extends Mixins(StakeDialogMixin) {
@getter.assets.xor private xor!: AccountAsset;
@Watch('visible')
private resetValue() {
this.baseAssetValue = '';
this.poolAssetValue = '';
}
baseAssetValue = '';
poolAssetValue = '';
readonly intervals = [1, 7, 30, 90];
interval = 1;
get selectedPeriod(): string {
return String(this.interval);
}
get rewardsText(): string {
return `${this.rewardAssetSymbol} rewards`;
}
get isBaseAssetMaxButtonAvailable(): boolean {
if (!this.baseAsset) return false;
return isMaxButtonAvailable(true, this.baseAsset, this.baseAssetValue, this.networkFee, this.xor);
}
get isPoolAssetMaxButtonAvailable(): boolean {
if (!this.poolAsset) return false;
return isMaxButtonAvailable(true, this.poolAsset, this.poolAssetValue, this.networkFee, this.xor);
}
selectPeriod({ name }): void {
this.interval = Number(name);
}
handleBaseAssetValue(value: string): void {
this.baseAssetValue = value;
if (!value) {
this.poolAssetValue = '';
} else if (this.liquidity) {
this.poolAssetValue = new FPNumber(this.baseAssetValue)
.mul(FPNumber.fromCodecValue(this.liquidity?.secondBalance ?? 0))
.div(FPNumber.fromCodecValue(this.liquidity?.firstBalance ?? 0))
.toString();
}
}
handlePoolAssetValue(value: string): void {
this.poolAssetValue = value;
if (!value) {
this.baseAssetValue = '';
} else if (this.liquidity) {
this.baseAssetValue = new FPNumber(this.poolAssetValue)
.mul(FPNumber.fromCodecValue(this.liquidity?.firstBalance ?? 0))
.div(FPNumber.fromCodecValue(this.liquidity?.secondBalance ?? 0))
.toString();
}
}
handleBaseAssetMax(): void {
this.handleBaseAssetValue(getMaxValue(this.baseAsset, this.networkFee));
}
handlePoolAssetMax(): void {
this.handlePoolAssetValue(getMaxValue(this.poolAsset, this.networkFee));
}
}
</script>

<style lang="scss">
.duration-title.info-line {
border-bottom: none;
}
.duration-tabs.s-tabs {
.el-tabs__header,
.el-tabs__nav {
width: 100%;
}
.el-tabs__item {
flex: 1;
text-align: center;
}
}
</style>

<style lang="scss" scoped>
.calculator-dialog {
& > *:not(:last-child) {
& > *:not(:first-child) {
margin-top: $inner-spacing-medium;
}
Expand All @@ -53,5 +183,23 @@ export default class CalculatorDialog extends Mixins(PoolMixin, TranslationMixin
.title-logo {
margin-right: $inner-spacing-mini;
}
@include vertical-divider('icon-divider', $inner-spacing-medium);
}
.duration {
&-title + &-tabs {
margin-top: $inner-spacing-small;
}
}
.results {
&-title {
font-size: var(--s-heading3-font-size);
font-weight: 500;
line-height: var(--s-line-height-small);
letter-spacing: var(--s-letter-spacing-mini);
margin-bottom: $inner-spacing-big;
}
}
</style>
39 changes: 7 additions & 32 deletions src/modules/demeterFarming/components/StakeDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
:max="100"
@input="handleValue"
>
<div slot="top" class="amount">{{ inputFieldTitle }}</div>
<div slot="top" class="amount">{{ inputTitle }}</div>
<div slot="right"><span class="percent">%</span></div>
<s-slider
slot="bottom"
Expand All @@ -40,7 +40,7 @@
v-else
:balance="stakingBalanceCodec"
:is-max-available="isMaxButtonAvailable"
:title="inputFieldTitle"
:title="inputTitle"
:token="poolAsset"
:value="value"
@input="handleValue"
Expand Down Expand Up @@ -102,19 +102,16 @@
<script lang="ts">
import { Component, Mixins, Prop, Watch } from 'vue-property-decorator';
import { components } from '@soramitsu/soraneo-wallet-web';
import { FPNumber, Operation } from '@sora-substrate/util';
import { FPNumber } from '@sora-substrate/util';
import PoolMixin from '../mixins/PoolMixin';
import StakeDialogMixin from '../mixins/StakeDialogMixin';
import TranslationMixin from '@/components/mixins/TranslationMixin';
import DialogMixin from '@/components/mixins/DialogMixin';
import DialogBase from '@/components/DialogBase.vue';
import { lazyComponent } from '@/router';
import { Components, ZeroStringValue } from '@/consts';
import { isXorAccountAsset, getMaxValue } from '@/utils';
import { getMaxValue, isXorAccountAsset } from '@/utils';
import type { CodecString } from '@sora-substrate/util';
import type { AccountAsset } from '@sora-substrate/util/build/assets/types';
import type { DemeterLiquidityParams } from '@/store/demeterFarming/types';
Expand All @@ -127,38 +124,20 @@ import type { DemeterLiquidityParams } from '@/store/demeterFarming/types';
TokenLogo: components.TokenLogo,
},
})
export default class StakeDialog extends Mixins(PoolMixin, TranslationMixin, DialogMixin) {
readonly delimiters = FPNumber.DELIMITERS_CONFIG;
@Prop({ default: () => true, type: Boolean }) readonly isAdding!: boolean;
export default class StakeDialog extends Mixins(StakeDialogMixin) {
@Watch('visible')
private resetValue() {
this.value = '';
}
value = '';
get networkFee(): CodecString {
const operation = this.isAdding
? Operation.DemeterFarmingDepositLiquidity
: Operation.DemeterFarmingWithdrawLiquidity;
return this.networkFees[operation];
}
get title(): string {
const actionKey = this.isAdding ? (this.hasStake ? 'add' : 'start') : 'remove';
return this.t(`demeterFarming.actions.${actionKey}`);
}
get inputFieldTitle(): string {
const key = this.isAdding ? 'amountAdd' : 'amountRemove';
return this.t(`demeterFarming.${key}`);
}
get valuePartCharClass(): string {
const charClassName =
{
Expand Down Expand Up @@ -219,10 +198,6 @@ export default class StakeDialog extends Mixins(PoolMixin, TranslationMixin, Dia
}
}
get valueFiatAmount(): Nullable<string> {
return this.getFiatAmountByFPNumber(this.valueFunds, this.poolAsset as AccountAsset);
}
get valueFundsEmpty(): boolean {
return this.valueFunds.isZero();
}
Expand Down Expand Up @@ -285,7 +260,7 @@ export default class StakeDialog extends Mixins(PoolMixin, TranslationMixin, Dia
.stake-dialog {
@include full-width-button('action-button');
& > *:not(:last-child) {
& > *:not(:first-child) {
margin-top: $inner-spacing-medium;
}
Expand Down
47 changes: 27 additions & 20 deletions src/modules/demeterFarming/mixins/PoolMixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export default class PoolMixin extends Mixins(AccountPoolMixin, TranslationMixin
return this.getAsset(this.liquidity?.firstAddress);
}

get baseAssetDecimals(): number {
return this.baseAsset?.decimals ?? FPNumber.DEFAULT_PRECISION;
}

get poolAsset(): Nullable<AccountAsset> {
return this.getAsset(this.pool?.poolAsset);
}
Expand Down Expand Up @@ -63,6 +67,12 @@ export default class PoolMixin extends Mixins(AccountPoolMixin, TranslationMixin
return FPNumber.fromCodecValue(getAssetBalance(this.liquidity, { parseAsLiquidity: true }) ?? 0);
}

get baseAssetBalance(): FPNumber {
if (!this.baseAsset) return FPNumber.ZERO;

return FPNumber.fromCodecValue(getAssetBalance(this.baseAsset) ?? 0, this.baseAssetDecimals);
}

get poolAssetBalance(): FPNumber {
if (!this.poolAsset) return FPNumber.ZERO;

Expand Down Expand Up @@ -154,12 +164,7 @@ export default class PoolMixin extends Mixins(AccountPoolMixin, TranslationMixin
return `$${this.tvl}`;
}

// allocation * token_per_block * 5256000 * multiplierPercent * reward_token_price / liquidityInPool * 100
get apr(): FPNumber {
if (!this.pool || (this.isFarm && !this.liquidity)) return FPNumber.ZERO;

let liquidityInPool: FPNumber;

get liquidityInPool(): FPNumber {
if (this.isFarm) {
const accountPoolShare = new FPNumber(this.liquidity.poolShare).div(FPNumber.HUNDRED);
const lpTokens = this.liqudityLP.div(accountPoolShare);
Expand All @@ -170,35 +175,37 @@ export default class PoolMixin extends Mixins(AccountPoolMixin, TranslationMixin
.mul(new FPNumber(2))
.div(accountPoolShare);

liquidityInPool = liquidityLockedPercent.mul(wholeLiquidityPrice);
return liquidityLockedPercent.mul(wholeLiquidityPrice);
} else {
// if stake is empty, show arp if user will stake all his tokens
const liquidityTokens = this.pool.totalTokensInPool.isZero()
? this.poolAssetBalance
: this.pool.totalTokensInPool;
liquidityInPool = liquidityTokens.mul(this.poolAssetPrice);
return liquidityTokens.mul(this.poolAssetPrice);
}
}

if (liquidityInPool.isZero()) return FPNumber.ZERO;

// allocation * token_per_block * multiplier
get emission(): FPNumber {
const allocation =
(this.isFarm ? this.tokenInfo?.farmsAllocation : this.tokenInfo?.stakingAllocation) ?? FPNumber.ZERO;
const tokenPerBlock = this.tokenInfo?.tokenPerBlock ?? FPNumber.ZERO;
const blocksPerYear = new FPNumber(5_256_000);
const poolMultiplier = new FPNumber(this.pool.multiplier);
const tokenMultiplier = new FPNumber(
(this.isFarm ? this.tokenInfo?.farmsTotalMultiplier : this.tokenInfo?.stakingTotalMultiplier) ?? 0
);
const multiplier = poolMultiplier.div(tokenMultiplier);
const rewardTokenPrice = this.rewardAssetPrice;

return allocation
.mul(tokenPerBlock)
.mul(blocksPerYear)
.mul(multiplier)
.mul(rewardTokenPrice)
.div(liquidityInPool)
.mul(FPNumber.HUNDRED);

return allocation.mul(tokenPerBlock).mul(multiplier);
}

// allocation * token_per_block * multiplier * 5256000 * reward_token_price / liquidityInPool * 100
get apr(): FPNumber {
if (!this.pool || (this.isFarm && !this.liquidity) || this.liquidityInPool.isZero()) return FPNumber.ZERO;

const blocksPerYear = new FPNumber(5_256_000);

return this.emission.mul(blocksPerYear).mul(this.rewardAssetPrice).div(this.liquidityInPool).mul(FPNumber.HUNDRED);
}

get aprFormatted(): string {
Expand Down
Loading

0 comments on commit f50f6ce

Please sign in to comment.