mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge pull request #3548 from gradido/frontend_fix_link_send_message
feat(frontend): make link send confirmation dialog more accurate
This commit is contained in:
commit
febc1277e5
@ -14,7 +14,7 @@ import { User } from '@model/User'
|
||||
import { QueryLinkResult } from '@union/QueryLinkResult'
|
||||
import { Decay, interpretEncryptedTransferArgs, TransactionTypeId } from 'core'
|
||||
import {
|
||||
AppDatabase, Community as DbCommunity, Contribution as DbContribution,
|
||||
AppDatabase, Contribution as DbContribution,
|
||||
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity, Transaction as DbTransaction,
|
||||
TransactionLink as DbTransactionLink,
|
||||
User as DbUser,
|
||||
@ -36,7 +36,7 @@ import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
|
||||
import { calculateBalance } from '@/util/validate'
|
||||
import { fullName } from 'core'
|
||||
import { TRANSACTION_LINK_LOCK, TRANSACTIONS_LOCK } from 'database'
|
||||
import { calculateDecay, decode, DisburseJwtPayloadType, encode, encryptAndSign, EncryptedJWEJwtPayloadType, RedeemJwtPayloadType, verify } from 'shared'
|
||||
import { calculateDecay, compoundInterest, decayFormula, decode, DisburseJwtPayloadType, encode, encryptAndSign, EncryptedJWEJwtPayloadType, RedeemJwtPayloadType, verify } from 'shared'
|
||||
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
|
||||
@ -48,7 +48,6 @@ import { randombytes_random } from 'sodium-native'
|
||||
import { executeTransaction } from './TransactionResolver'
|
||||
import {
|
||||
getAuthenticatedCommunities,
|
||||
getCommunityByIdentifier,
|
||||
getCommunityByPublicKey,
|
||||
getCommunityByUuid,
|
||||
} from './util/communities'
|
||||
@ -90,7 +89,7 @@ export class TransactionLinkResolver {
|
||||
const createdDate = new Date()
|
||||
const validUntil = transactionLinkExpireDate(createdDate)
|
||||
|
||||
const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay)
|
||||
const holdAvailableAmount = compoundInterest(amount, CODE_VALID_DAYS_DURATION * 24 * 60 * 60)
|
||||
|
||||
// validate amount
|
||||
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
||||
|
||||
@ -20,20 +20,29 @@
|
||||
<BCol>{{ $t('advanced-calculation') }}</BCol>
|
||||
</BRow>
|
||||
<BRow class="pe-3" offset="2">
|
||||
<BCol offset="2">{{ $t('form.current_balance') }}</BCol>
|
||||
<BCol offset="2">{{ $t('form.current_available') }}</BCol>
|
||||
<BCol>{{ $filters.GDD(balance) }}</BCol>
|
||||
</BRow>
|
||||
<BRow class="pe-3">
|
||||
<BCol offset="2">
|
||||
<strong>{{ $t('form.your_amount') }}</strong>
|
||||
<strong>{{ $t('form.link_amount') }}</strong>
|
||||
</BCol>
|
||||
<BCol class="borderbottom">
|
||||
<BCol>
|
||||
<strong>{{ $filters.GDD(amount * -1) }}</strong>
|
||||
</BCol>
|
||||
</BRow>
|
||||
<BRow class="pe-3">
|
||||
<BCol offset="2">{{ $t('form.new_balance') }}</BCol>
|
||||
<BCol>{{ $filters.GDD(balance - amount) }}</BCol>
|
||||
<BCol offset="2">{{ $t('decay.decay') }}</BCol>
|
||||
<BCol class="borderbottom">{{ $filters.GDD(amount - blockedAmount) }}</BCol>
|
||||
</BRow>
|
||||
<BRow class="pe-3">
|
||||
<BCol offset="2">{{ $t('form.available_after') }}</BCol>
|
||||
<BCol>{{ $filters.GDD(balance - blockedAmount) }}</BCol>
|
||||
</BRow>
|
||||
<BRow class="pe-6 mt-2">
|
||||
<BCol offset="1">
|
||||
<small>{{ $t('form.link_decay_description') }}</small>
|
||||
</BCol>
|
||||
</BRow>
|
||||
<BRow class="mt-5">
|
||||
<BCol cols="12" md="6" lg="6">
|
||||
@ -57,6 +66,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { LINK_COMPOUND_INTEREST_FACTOR } from '@/constants'
|
||||
export default {
|
||||
name: 'TransactionConfirmationLink',
|
||||
props: {
|
||||
@ -68,7 +78,13 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
totalBalance() {
|
||||
return this.balance - this.amount * 1.028
|
||||
return this.balance - this.blockedAmount
|
||||
},
|
||||
blockedAmount() {
|
||||
// correct formula
|
||||
return this.amount * LINK_COMPOUND_INTEREST_FACTOR
|
||||
// same formula as in backend
|
||||
// return 2 * this.amount - this.amount * Math.pow(0.99999997803504048, 1209600)
|
||||
},
|
||||
disabled() {
|
||||
if (this.totalBalance < 0) {
|
||||
|
||||
@ -1,2 +1,5 @@
|
||||
export const GDD_PER_HOUR = 20
|
||||
export const PAGE_SIZE = 25
|
||||
// compound interest factor (decay reversed) for 14 days (hard coded backend link timeout)
|
||||
// 365.2425 days per year (gregorian calendar year)
|
||||
export const LINK_COMPOUND_INTEREST_FACTOR = Math.pow(2, 14 / 365.2425)
|
||||
|
||||
@ -164,10 +164,12 @@
|
||||
"form": {
|
||||
"amount": "Betrag",
|
||||
"at": "am",
|
||||
"available_after": "Verfügbar nach Bestätigung",
|
||||
"cancel": "Abbrechen",
|
||||
"change": "Ändern",
|
||||
"check_now": "Jetzt prüfen",
|
||||
"close": "Schließen",
|
||||
"current_available": "Aktuell verfügbar",
|
||||
"current_balance": "Aktueller Kontostand",
|
||||
"date": "Datum",
|
||||
"description": "Beschreibung",
|
||||
@ -178,6 +180,8 @@
|
||||
"hours": "Stunden",
|
||||
"identifier": "Email, Nutzername oder Gradido ID",
|
||||
"lastname": "Nachname",
|
||||
"link_amount": "Link-Betrag",
|
||||
"link_decay_description": "Der Link-Betrag wird zusammen mit der maximalen Vergänglichkeit blockiert. Nach Einlösen, Verfallen oder Löschen des Links wird der Rest wieder freigegeben.",
|
||||
"memo": "Nachricht",
|
||||
"message": "Nachricht",
|
||||
"new_balance": "Neuer Kontostand nach Bestätigung",
|
||||
|
||||
@ -164,10 +164,12 @@
|
||||
"form": {
|
||||
"amount": "Amount",
|
||||
"at": "at",
|
||||
"available_after": "Available after confirmation",
|
||||
"cancel": "Cancel",
|
||||
"change": "Change",
|
||||
"check_now": "Check now",
|
||||
"close": "Close",
|
||||
"current_available": "Currently available",
|
||||
"current_balance": "Current Balance",
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
@ -178,6 +180,8 @@
|
||||
"hours": "Hours",
|
||||
"identifier": "Email, username or gradido ID",
|
||||
"lastname": "Lastname",
|
||||
"link_amount": "Link amount",
|
||||
"link_decay_description": "The link amount is blocked along with the maximum decay. After the link has been redeemed, expired, or deleted, the rest is released again.",
|
||||
"memo": "Message",
|
||||
"message": "Message",
|
||||
"new_balance": "Account balance after confirmation",
|
||||
|
||||
@ -146,10 +146,12 @@
|
||||
"form": {
|
||||
"amount": "Importe",
|
||||
"at": "am",
|
||||
"available_after": "Disponible tras la confirmación",
|
||||
"cancel": "Cancelar",
|
||||
"change": "Cambiar",
|
||||
"check_now": "Revisar",
|
||||
"close": "Cerrar",
|
||||
"current_available": "Actualmente disponible",
|
||||
"current_balance": "Saldo de cuenta actual",
|
||||
"date": "Fecha",
|
||||
"description": "Descripción",
|
||||
@ -158,6 +160,8 @@
|
||||
"from": "De",
|
||||
"generate_now": "crear ahora",
|
||||
"lastname": "Apellido",
|
||||
"link_amount": "Importe del enlace",
|
||||
"link_decay_description": "El importe del enlace se bloquea junto con la ransience máxima. Una vez que el enlace se ha canjeado, caducado o eliminado, el resto se libera de nuevo.",
|
||||
"memo": "Mensaje",
|
||||
"message": "Noticia",
|
||||
"new_balance": "Saldo de cuenta nuevo depués de confirmación",
|
||||
|
||||
@ -151,10 +151,12 @@
|
||||
"form": {
|
||||
"amount": "Montant",
|
||||
"at": "à",
|
||||
"available_after": "Disponible après confirmation",
|
||||
"cancel": "Annuler",
|
||||
"change": "Changer",
|
||||
"check_now": "Vérifier maintenant",
|
||||
"close": "Fermer",
|
||||
"current_available": "Actuellement disponible",
|
||||
"current_balance": "Solde actuel",
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
@ -164,6 +166,8 @@
|
||||
"generate_now": "Produire maintenant",
|
||||
"hours": "Heures",
|
||||
"lastname": "Nom",
|
||||
"link_amount": "Montant du lien",
|
||||
"link_decay_description": "Le montant du lien est bloqué avec le dépérissement maximale. Une fois le lien utilisé, expiré ou supprimé, le reste est à nouveau débloqué.",
|
||||
"memo": "Note",
|
||||
"message": "Message",
|
||||
"new_balance": "Montant du solde après confirmation",
|
||||
|
||||
@ -146,10 +146,12 @@
|
||||
"form": {
|
||||
"amount": "Bedrag",
|
||||
"at": "op",
|
||||
"available_after": "Beschikbaar na bevestiging",
|
||||
"cancel": "Annuleren",
|
||||
"change": "Wijzigen",
|
||||
"check_now": "Nu controleren",
|
||||
"close": "Sluiten",
|
||||
"current_available": "Momenteel beschikbaar",
|
||||
"current_balance": "Actueel banksaldo",
|
||||
"date": "Datum",
|
||||
"description": "Beschrijving",
|
||||
@ -158,6 +160,8 @@
|
||||
"from": "Van",
|
||||
"generate_now": "Nu genereren",
|
||||
"lastname": "Achternaam",
|
||||
"link_amount": "Linkbedrag",
|
||||
"link_decay_description": "Het linkbedrag wordt geblokkeerd samen met de maximale vergankelijkheid. Nadat de link is ingewisseld, verlopen of verwijderd, wordt het resterende bedrag weer vrijgegeven.",
|
||||
"memo": "Memo",
|
||||
"message": "Bericht",
|
||||
"new_balance": "Nieuw banksaldo na bevestiging",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export const DECAY_START_TIME = new Date('2021-05-13T17:46:31Z')
|
||||
export const SECONDS_PER_YEAR_GREGORIAN_CALENDER = 31556952.0
|
||||
export const LOG4JS_BASE_CATEGORY_NAME = 'shared'
|
||||
export const REDEEM_JWT_TOKEN_EXPIRATION = '10m'
|
||||
@ -1,6 +1,6 @@
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { calculateDecay, decayFormula } from './decay'
|
||||
import { calculateDecay, compoundInterest, decayFormula, decayFormulaFast } from './decay'
|
||||
|
||||
describe('utils/decay', () => {
|
||||
describe('decayFormula', () => {
|
||||
@ -10,6 +10,18 @@ describe('utils/decay', () => {
|
||||
// TODO: toString() was required, we could not compare two decimals
|
||||
expect(decayFormula(amount, seconds).toString()).toBe('0.999999978035040489732012')
|
||||
})
|
||||
|
||||
it('with large values', () => {
|
||||
const amount = new Decimal(100.0)
|
||||
const seconds = 1209600
|
||||
expect(decayFormula(amount, seconds).toString()).toBe('97.3781030481034505778419')
|
||||
})
|
||||
|
||||
it('with one year', () => {
|
||||
const amount = new Decimal(100.0)
|
||||
const seconds = 31556952
|
||||
expect(decayFormula(amount, seconds).toString()).toBe('49.99999999999999999999999')
|
||||
})
|
||||
|
||||
it('has correct backward calculation', () => {
|
||||
const amount = new Decimal(1.0)
|
||||
@ -26,6 +38,40 @@ describe('utils/decay', () => {
|
||||
expect(decayFormula(amount, seconds).toString()).toBe('1.0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('decayFormulaFast', () => {
|
||||
it('work like expected with small values', () => {
|
||||
const amount = new Decimal(1.0)
|
||||
const seconds = 1
|
||||
expect(decayFormulaFast(amount, seconds).toString()).toBe('0.999999978035040489732012')
|
||||
})
|
||||
it('work like expected with large values', () => {
|
||||
const amount = new Decimal(100.0)
|
||||
const seconds = 1209600
|
||||
expect(decayFormulaFast(amount, seconds).toString()).toBe('97.3781030481034505778419')
|
||||
})
|
||||
it('work like expected with one year', () => {
|
||||
const amount = new Decimal(100.0)
|
||||
const seconds = 31556952
|
||||
expect(decayFormulaFast(amount, seconds).toString()).toBe('50')
|
||||
})
|
||||
it('work correct when calculating future decay', () => {
|
||||
// for example on linked transaction
|
||||
// if I have amount = 100 GDD and want to know how much GDD I must lock to able to pay
|
||||
// decay in 14 days (1209600 seconds)
|
||||
const amount = new Decimal(100.0)
|
||||
const seconds = 1209600
|
||||
expect(compoundInterest(amount, seconds).toString()).toBe('102.6924912992003635568199')
|
||||
// if I lock 102.6924912992003635568199 GDD, I will have 99.99999999999999999999998 GDD after 14 days, not 100% but near enough
|
||||
expect(decayFormulaFast(compoundInterest(amount, seconds), seconds).toString()).toBe('99.99999999999999999999998')
|
||||
expect(compoundInterest(amount, seconds).toDecimalPlaces(4).toString()).toBe('102.6925')
|
||||
// rounded to 4 decimal places it is working like a charm
|
||||
expect(decayFormulaFast(new Decimal(
|
||||
compoundInterest(amount, seconds).toDecimalPlaces(4)),
|
||||
seconds
|
||||
).toDecimalPlaces(4).toString()).toBe('100')
|
||||
})
|
||||
})
|
||||
|
||||
it('has base 0.99999997802044727', () => {
|
||||
const now = new Date()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { DECAY_START_TIME } from '../const'
|
||||
import { DECAY_START_TIME, SECONDS_PER_YEAR_GREGORIAN_CALENDER } from '../const'
|
||||
|
||||
Decimal.set({
|
||||
precision: 25,
|
||||
@ -26,6 +26,13 @@ export function decayFormula(value: Decimal, seconds: number): Decimal {
|
||||
)
|
||||
}
|
||||
|
||||
export function decayFormulaFast(value: Decimal, seconds: number): Decimal {
|
||||
return value.mul(new Decimal(2).pow(new Decimal(-seconds).div(new Decimal(SECONDS_PER_YEAR_GREGORIAN_CALENDER))))
|
||||
}
|
||||
export function compoundInterest(value: Decimal, seconds: number): Decimal {
|
||||
return value.mul(new Decimal(2).pow(new Decimal(seconds).div(new Decimal(SECONDS_PER_YEAR_GREGORIAN_CALENDER))))
|
||||
}
|
||||
|
||||
export function calculateDecay(
|
||||
amount: Decimal,
|
||||
from: Date,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user