diff --git a/src/Create.tsx b/src/Create.tsx index c7b61168..b18d787c 100644 --- a/src/Create.tsx +++ b/src/Create.tsx @@ -62,10 +62,15 @@ const Create = () => { const target = evt.currentTarget as HTMLInputElement; const amount = target.value.trim(); const satAmount = convertAmount(BigNumber(amount), denomination()); - const sendAmount = calculateSendAmount(satAmount.toNumber()); + const sendAmount = calculateSendAmount( + satAmount, + boltzFee(), + minerFee(), + reverse(), + ); setAmountChanged(sideReceive); - setReceiveAmount(BigNumber(satAmount)); - setSendAmount(BigNumber(sendAmount)); + setReceiveAmount(satAmount); + setSendAmount(sendAmount); validateAmount(); target.setCustomValidity(""); target.classList.remove("invalid"); @@ -75,10 +80,15 @@ const Create = () => { const target = evt.currentTarget as HTMLInputElement; const amount = target.value.trim(); const satAmount = convertAmount(BigNumber(amount), denomination()); - const receiveAmount = calculateReceiveAmount(satAmount.toNumber()); + const receiveAmount = calculateReceiveAmount( + satAmount, + boltzFee(), + minerFee(), + reverse(), + ); setAmountChanged(sideSend); - setSendAmount(BigNumber(satAmount)); - setReceiveAmount(BigNumber(receiveAmount)); + setSendAmount(satAmount); + setReceiveAmount(receiveAmount); validateAmount(); target.setCustomValidity(""); target.classList.remove("invalid"); @@ -151,7 +161,14 @@ const Create = () => { const setAmount = (amount: number) => { setSendAmount(BigNumber(amount)); - setReceiveAmount(BigNumber(calculateReceiveAmount(amount))); + setReceiveAmount( + calculateReceiveAmount( + BigNumber(amount), + boltzFee(), + minerFee(), + reverse(), + ), + ); validateAmount(); sendAmountRef.focus(); }; @@ -164,11 +181,21 @@ const Create = () => { on([boltzFee, minerFee, reverse, asset], () => { if (amountChanged() === sideReceive) { setSendAmount( - BigNumber(calculateSendAmount(receiveAmount().toNumber())), + calculateSendAmount( + receiveAmount(), + boltzFee(), + minerFee(), + reverse(), + ), ); } else { setReceiveAmount( - BigNumber(calculateReceiveAmount(sendAmount().toNumber())), + calculateReceiveAmount( + sendAmount(), + boltzFee(), + minerFee(), + reverse(), + ), ); } validateAmount(); @@ -282,7 +309,12 @@ const Create = () => { required type="text" placeholder={formatAmount( - BigNumber(calculateReceiveAmount(minimum())), + calculateReceiveAmount( + BigNumber(minimum()), + boltzFee(), + minerFee(), + reverse(), + ), denomination(), )} maxlength={calculateDigits(maximum(), denomination())} diff --git a/src/components/Fees.tsx b/src/components/Fees.tsx index 5ef89c3b..16242354 100644 --- a/src/components/Fees.tsx +++ b/src/components/Fees.tsx @@ -41,8 +41,15 @@ const Fees = () => { setMinerFee(fee); } - const calculateLimit = (limit: number) => { - return reverse() ? limit : calculateSendAmount(limit); + const calculateLimit = (limit: number): number => { + return reverse() + ? limit + : calculateSendAmount( + BigNumber(limit), + boltzFee(), + minerFee(), + reverse(), + ).toNumber(); }; setMinimum(calculateLimit(cfg.limits.minimal)); @@ -86,7 +93,12 @@ const Fees = () => { {t("fee")} ({boltzFee()}%):{" "} {formatAmount( - BigNumber(calculateBoltzFeeOnSend(sendAmount())), + calculateBoltzFeeOnSend( + sendAmount(), + boltzFee(), + minerFee(), + reverse(), + ), denomination(), true, )} diff --git a/src/components/InvoiceInput.tsx b/src/components/InvoiceInput.tsx index ba4d1cd3..8c5365e4 100644 --- a/src/components/InvoiceInput.tsx +++ b/src/components/InvoiceInput.tsx @@ -5,8 +5,10 @@ import { RBTC } from "../consts"; import t from "../i18n"; import { asset, + boltzFee, denomination, invoice, + minerFee, receiveAmount, receiveAmountFormatted, reverse, @@ -35,7 +37,14 @@ const InvoiceInput = () => { } else { const sats = validateInvoice(inputValue); setReceiveAmount(BigNumber(sats)); - setSendAmount(BigNumber(calculateSendAmount(sats))); + setSendAmount( + calculateSendAmount( + BigNumber(sats), + boltzFee(), + minerFee(), + reverse(), + ), + ); setInvoice(inputValue); setLnurl(""); setInvoiceValid(true); diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts index f7ead273..a104d4d2 100644 --- a/src/utils/calculate.ts +++ b/src/utils/calculate.ts @@ -1,58 +1,73 @@ import { BigNumber } from "bignumber.js"; -import { boltzFee, minerFee, reverse } from "../signals"; - -const bigRound = (big: BigNumber): BigNumber => { +const bigCeil = (big: BigNumber): BigNumber => { return big.integerValue(BigNumber.ROUND_CEIL); }; -export const calculateReceiveAmount = (sendAmount: number): number => { - const receiveAmount = reverse() - ? BigNumber(sendAmount) - .minus(bigRound(BigNumber(sendAmount).times(boltzFee()).div(100))) - .minus(minerFee()) - : BigNumber(sendAmount) - .minus(minerFee()) - .div(BigNumber(1).plus(BigNumber(boltzFee()).div(100))); - return Math.max(Math.floor(receiveAmount.toNumber()), 0); +const bigFloor = (big: BigNumber): BigNumber => { + return big.integerValue(BigNumber.ROUND_FLOOR); +}; + +export const calculateReceiveAmount = ( + sendAmount: BigNumber, + boltzFee: number, + minerFee: number, + reverse: boolean, +): BigNumber => { + const receiveAmount = reverse + ? sendAmount + .minus(bigCeil(sendAmount.times(boltzFee).div(100))) + .minus(minerFee) + : sendAmount + .minus(minerFee) + .div(BigNumber(1).plus(BigNumber(boltzFee).div(100))); + return BigNumber.maximum(bigFloor(receiveAmount), 0); }; -export const calculateBoltzFeeOnSend = (sendAmount: BigNumber): number => { +export const calculateBoltzFeeOnSend = ( + sendAmount: BigNumber, + boltzFee: number, + minerFee: number, + reverse: boolean, +): BigNumber => { let fee: BigNumber; - if (reverse()) { - fee = bigRound(sendAmount.times(boltzFee()).div(100)); + if (reverse) { + fee = bigCeil(sendAmount.times(boltzFee).div(100)); } else { fee = sendAmount - .minus(calculateReceiveAmount(sendAmount.toNumber())) - .minus(minerFee()); + .minus( + calculateReceiveAmount(sendAmount, boltzFee, minerFee, reverse), + ) + .minus(minerFee); - if (sendAmount.toNumber() < minerFee()) { + if (sendAmount.toNumber() < minerFee) { fee = BigNumber(0); } } - return Math.ceil(fee.toNumber()); + return bigCeil(fee); }; -export const calculateSendAmount = (receiveAmount: number): number => { - return reverse() - ? Math.ceil( - BigNumber(receiveAmount) - .plus(minerFee()) - .div(BigNumber(1).minus(BigNumber(boltzFee()).div(100))) - .toNumber(), +export const calculateSendAmount = ( + receiveAmount: BigNumber, + boltzFee: number, + minerFee: number, + reverse: boolean, +): BigNumber => { + return reverse + ? bigCeil( + receiveAmount + .plus(minerFee) + .div(BigNumber(1).minus(BigNumber(boltzFee).div(100))), ) - : Math.floor( - BigNumber(receiveAmount) + : bigFloor( + receiveAmount .plus( - bigRound( - BigNumber(receiveAmount).times( - BigNumber(boltzFee()).div(100), - ), + bigCeil( + receiveAmount.times(BigNumber(boltzFee).div(100)), ), ) - .plus(minerFee()) - .toNumber(), + .plus(minerFee), ); }; diff --git a/tests/components/Create.spec.tsx b/tests/components/Create.spec.tsx index 949fffad..0bff4119 100644 --- a/tests/components/Create.spec.tsx +++ b/tests/components/Create.spec.tsx @@ -156,7 +156,12 @@ describe("Create", () => { expect(setReceiveAmount).toHaveBeenCalledTimes(1); expect(setReceiveAmount).toHaveBeenCalledWith( - BigNumber(calculateReceiveAmount(amount)), + calculateReceiveAmount( + BigNumber(amount), + signals.boltzFee(), + signals.minerFee(), + signals.reverse(), + ), ); }); }); diff --git a/tests/components/Fees.spec.tsx b/tests/components/Fees.spec.tsx index 0e84e2b0..f1c81f8c 100644 --- a/tests/components/Fees.spec.tsx +++ b/tests/components/Fees.spec.tsx @@ -1,4 +1,5 @@ import { render } from "@solidjs/testing-library"; +import { BigNumber } from "bignumber.js"; import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import Fees from "../../src/components/Fees"; @@ -33,10 +34,20 @@ describe("Fees component", () => { signals.setReverse(false); expect(setMinimum).toHaveBeenLastCalledWith( - calculateSendAmount(cfg["BTC/BTC"].limits.minimal), + calculateSendAmount( + BigNumber(cfg["BTC/BTC"].limits.minimal), + signals.boltzFee(), + signals.minerFee(), + signals.reverse(), + ).toNumber(), ); expect(setMaximum).toHaveBeenLastCalledWith( - calculateSendAmount(cfg["BTC/BTC"].limits.maximal), + calculateSendAmount( + BigNumber(cfg["BTC/BTC"].limits.maximal), + signals.boltzFee(), + signals.minerFee(), + signals.reverse(), + ).toNumber(), ); }); }); diff --git a/tests/utils/calculate.spec.ts b/tests/utils/calculate.spec.ts index 117a8b3d..7fa51dba 100644 --- a/tests/utils/calculate.spec.ts +++ b/tests/utils/calculate.spec.ts @@ -1,12 +1,6 @@ import { BigNumber } from "bignumber.js"; -import { beforeAll, describe, expect, test } from "vitest"; +import { describe, expect, test } from "vitest"; -import { - minerFee, - setBoltzFee, - setMinerFee, - setReverse, -} from "../../src/signals"; import { calculateBoltzFeeOnSend, calculateReceiveAmount, @@ -14,142 +8,175 @@ import { } from "../../src/utils/calculate"; describe("Calculate amounts", () => { - const setSwapFees = () => { - setReverse(false); - setMinerFee(147); - setBoltzFee(0.1); + const swapFees = { + reverse: false, + minerFee: 147, + boltzFee: 0.1, }; - const setReverseSwapFees = () => { - setReverse(true); - setMinerFee(428); - setBoltzFee(0.25); + const reverseSwapFees = { + reverse: true, + minerFee: 428, + boltzFee: 0.25, }; describe("should calculate Swap amounts", () => { - beforeAll(() => { - setSwapFees(); - }); - test.each` - sendAmount | receiveAmount - ${10157} | ${10000} - ${12473} | ${12313} - ${4299409} | ${4294967} - ${62531} | ${62321} + sendAmount | receiveAmount + ${BigNumber(10157)} | ${BigNumber(10000)} + ${BigNumber(12473)} | ${BigNumber(12313)} + ${BigNumber(4299409)} | ${BigNumber(4294967)} + ${BigNumber(62531)} | ${BigNumber(62321)} `( "calculate amounts $sendAmount <-> $receiveAmount", ({ sendAmount, receiveAmount }) => { - expect(calculateReceiveAmount(sendAmount)).toEqual( - receiveAmount, - ); - expect(calculateSendAmount(receiveAmount)).toEqual(sendAmount); + expect( + calculateReceiveAmount( + sendAmount, + swapFees.boltzFee, + swapFees.minerFee, + swapFees.reverse, + ), + ).toEqual(receiveAmount); + expect( + calculateSendAmount( + receiveAmount, + swapFees.boltzFee, + swapFees.minerFee, + swapFees.reverse, + ), + ).toEqual(sendAmount); }, ); - test("should return correct types", () => { - expect(typeof calculateReceiveAmount(1000000)).toEqual("number"); - expect(typeof calculateSendAmount(1000000)).toEqual("number"); - }); - test("should not return negative numbers", () => { - expect(calculateReceiveAmount(0)).toEqual(0); + expect( + calculateReceiveAmount( + BigNumber(0), + swapFees.boltzFee, + swapFees.minerFee, + swapFees.reverse, + ), + ).toEqual(BigNumber(0)); }); }); describe("should calculate Reverse Swap amounts", () => { - beforeAll(() => { - setReverseSwapFees(); - }); - test.each` - sendAmount | receiveAmount - ${1000000} | ${997072} - ${10000} | ${9547} - ${122344} | ${121610} - ${4294967} | ${4283801} + sendAmount | receiveAmount + ${BigNumber(1000000)} | ${BigNumber(997072)} + ${BigNumber(10000)} | ${BigNumber(9547)} + ${BigNumber(122344)} | ${BigNumber(121610)} + ${BigNumber(4294967)} | ${BigNumber(4283801)} `( "calculate amounts $sendAmount <-> $receiveAmount", ({ sendAmount, receiveAmount }) => { - expect(calculateReceiveAmount(sendAmount)).toEqual( - receiveAmount, - ); - expect(calculateSendAmount(receiveAmount)).toEqual(sendAmount); + expect( + calculateReceiveAmount( + sendAmount, + reverseSwapFees.boltzFee, + reverseSwapFees.minerFee, + reverseSwapFees.reverse, + ), + ).toEqual(receiveAmount); + expect( + calculateSendAmount( + receiveAmount, + reverseSwapFees.boltzFee, + reverseSwapFees.minerFee, + reverseSwapFees.reverse, + ), + ).toEqual(sendAmount); }, ); - test("should return correct types", () => { - expect(typeof calculateReceiveAmount(1000000)).toEqual("number"); - expect(typeof calculateSendAmount(1000000)).toEqual("number"); - }); - test("should not return negative numbers", () => { - expect(calculateReceiveAmount(0)).toEqual(0); + expect( + calculateReceiveAmount( + BigNumber(0), + reverseSwapFees.boltzFee, + reverseSwapFees.minerFee, + reverseSwapFees.reverse, + ), + ).toEqual(BigNumber(0)); }); }); describe("should calculate Boltz fee based on send amount", () => { test.each` - sendAmount | receiveAmount | fee - ${BigNumber(10157)} | ${10000} | ${10} - ${BigNumber(12473)} | ${12313} | ${13} - ${BigNumber(4299409)} | ${4294967} | ${4295} - ${BigNumber(62531)} | ${62321} | ${63} - ${BigNumber(100)} | ${-47} | ${0} + sendAmount | receiveAmount | fee + ${BigNumber(10157)} | ${BigNumber(10000)} | ${BigNumber(10)} + ${BigNumber(12473)} | ${BigNumber(12313)} | ${BigNumber(13)} + ${BigNumber(4299409)} | ${BigNumber(4294967)} | ${BigNumber(4295)} + ${BigNumber(62531)} | ${BigNumber(62321)} | ${BigNumber(63)} + ${BigNumber(100)} | ${BigNumber(-47)} | ${BigNumber(0)} `( "should calculate fee for Swaps $sendAmount -> $fee", ({ sendAmount, receiveAmount, fee }) => { - setSwapFees(); - - expect(calculateBoltzFeeOnSend(sendAmount)).toEqual(fee); + expect( + calculateBoltzFeeOnSend( + sendAmount, + swapFees.boltzFee, + swapFees.minerFee, + swapFees.reverse, + ), + ).toEqual(fee); expect( sendAmount - .minus(calculateBoltzFeeOnSend(sendAmount)) - .minus(minerFee()) - .toNumber(), + .minus( + calculateBoltzFeeOnSend( + sendAmount, + swapFees.boltzFee, + swapFees.minerFee, + swapFees.reverse, + ), + ) + .minus(swapFees.minerFee), ).toEqual(receiveAmount); }, ); test.each` - sendAmount | receiveAmount | fee - ${BigNumber(1000000)} | ${997072} | ${2500} - ${BigNumber(10000)} | ${9547} | ${25} - ${BigNumber(122344)} | ${121610} | ${306} - ${BigNumber(4294967)} | ${4283801} | ${10738} + sendAmount | receiveAmount | fee + ${BigNumber(1000000)} | ${BigNumber(997072)} | ${BigNumber(2500)} + ${BigNumber(10000)} | ${BigNumber(9547)} | ${BigNumber(25)} + ${BigNumber(122344)} | ${BigNumber(121610)} | ${BigNumber(306)} + ${BigNumber(4294967)} | ${BigNumber(4283801)} | ${BigNumber(10738)} `( "should calculate fee for Reverse Swaps $sendAmount -> $fee", ({ sendAmount, receiveAmount, fee }) => { - setReverseSwapFees(); - - expect(calculateBoltzFeeOnSend(sendAmount)).toEqual(fee); expect( - BigNumber(sendAmount) - .minus(calculateBoltzFeeOnSend(sendAmount)) - .minus(minerFee()) - .toNumber(), + calculateBoltzFeeOnSend( + sendAmount, + reverseSwapFees.boltzFee, + reverseSwapFees.minerFee, + reverseSwapFees.reverse, + ), + ).toEqual(fee); + expect( + sendAmount + .minus( + calculateBoltzFeeOnSend( + sendAmount, + reverseSwapFees.boltzFee, + reverseSwapFees.minerFee, + reverseSwapFees.reverse, + ), + ) + .minus(reverseSwapFees.minerFee), ).toEqual(receiveAmount); }, ); test("should calculate negative fees", () => { - setSwapFees(); - setBoltzFee(-0.1); - expect(calculateBoltzFeeOnSend(BigNumber(1_000_000))).toEqual( - -1000, - ); - }); - - test("should return correct types", () => { - setReverse(true); - expect(typeof calculateBoltzFeeOnSend(BigNumber(1000000))).toEqual( - "number", - ); - - setReverse(false); - expect(typeof calculateBoltzFeeOnSend(BigNumber(1000000))).toEqual( - "number", - ); + expect( + calculateBoltzFeeOnSend( + BigNumber(1_000_000), + -0.1, + swapFees.minerFee, + swapFees.reverse, + ), + ).toEqual(BigNumber(-1000)); }); }); });