diff --git a/docs/deployment.md b/docs/deployment.md index 371c94b38..3ee5e9272 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -92,6 +92,8 @@ otpsecretpath = "/home/boltz/.boltz/otpSecret.dat" # - Kraken # - Poloniex # - "fee": percentage of the swapped amount that should be charged as fee +# - "swapInFee" (optional): percentage of the swapped in amount that should be charged as fee +# "fee" is applied if this is not configured [[pairs]] base = "LTC" @@ -109,6 +111,7 @@ base = "LTC" quote = "LTC" rate = 1 fee = 0.5 +swapInFee = 1.0 timeoutDelta = 300 # The array "currencies" configures the chain and LND clients for the "pairs" diff --git a/lib/consts/Types.ts b/lib/consts/Types.ts index 90264a676..d43715f12 100644 --- a/lib/consts/Types.ts +++ b/lib/consts/Types.ts @@ -31,6 +31,7 @@ export type PairConfig = { // Percentage of the amount that will be charged as fee fee?: number; + swapInFee?: number; // If there is a hardcoded rate the APIs of the exchanges will not be queried rate?: number; diff --git a/lib/rates/FeeProvider.ts b/lib/rates/FeeProvider.ts index 15ad65032..f5432f3de 100644 --- a/lib/rates/FeeProvider.ts +++ b/lib/rates/FeeProvider.ts @@ -19,6 +19,7 @@ type MinerFees = { class FeeProvider { // A map between the symbols of the pairs and their percentage fees public percentageFees = new Map(); + public percentageSwapInFees = new Map(); public minerFees = new Map(); @@ -65,6 +66,9 @@ class FeeProvider { } this.percentageFees.set(getPairId(pair), percentage / 100); + + const percentageSwapIn = pair.swapInFee !== undefined ? pair.swapInFee : 0; + this.percentageSwapInFees.set(getPairId(pair), percentageSwapIn / 100); }); this.logger.debug(`Prepared data for fee estimations: ${stringify(mapToObject(this.percentageFees))}`); @@ -74,6 +78,10 @@ class FeeProvider { return this.percentageFees.get(pair) || 0; }; + public getPercentageSwapInFee = (pair: string): number => { + return this.percentageSwapInFees.get(pair) || 0; + }; + public getFees = ( pair: string, rate: number, @@ -83,18 +91,25 @@ class FeeProvider { ): { baseFee: number, percentageFee: number, + percentageSwapInFee: number, } => { let percentageFee = this.getPercentageFee(pair); + let percentageSwapInFee = this.getPercentageSwapInFee(pair); if (percentageFee !== 0) { percentageFee = percentageFee * amount * rate; } + if (percentageSwapInFee !== 0) { + percentageSwapInFee = percentageSwapInFee * amount * rate; + } + const { base, quote } = splitPairId(pair); const chainCurrency = getChainCurrency(base, quote, orderSide, type !== BaseFeeType.NormalClaim); return { percentageFee: Math.ceil(percentageFee), + percentageSwapInFee: Math.ceil(percentageSwapInFee), baseFee: this.getBaseFee(chainCurrency, type), }; }; diff --git a/lib/rates/RateProvider.ts b/lib/rates/RateProvider.ts index 5c579e8c2..43bd42a11 100644 --- a/lib/rates/RateProvider.ts +++ b/lib/rates/RateProvider.ts @@ -37,6 +37,7 @@ type PairType = { }; fees: { percentage: number; + swapInFee: number; minerFees: { baseAsset: MinerFees, quoteAsset: MinerFees, @@ -64,6 +65,7 @@ class RateProvider { // A copy of the "percentageFees" Map in the FeeProvider but all values are multiplied with 100 private percentageFees = new Map(); + private percentageSwapInFees = new Map(); private timer!: any; @@ -83,6 +85,11 @@ class RateProvider { this.percentageFees.set(pair, percentage * 100); }); + this.feeProvider.percentageSwapInFees.forEach((swapInFee, pair) => { + // Multiply with 100 to get the percentage + this.percentageSwapInFees.set(pair, swapInFee * 100); + }); + await this.updateMinerFees(); pairs.forEach((pair) => { @@ -99,6 +106,7 @@ class RateProvider { limits: this.getLimits(id, pair.base, pair.quote, pair.rate), fees: { percentage: this.percentageFees.get(id)!, + swapInFee: this.percentageSwapInFees.get(id)!, minerFees: { baseAsset: emptyMinerFees, quoteAsset: emptyMinerFees, @@ -189,6 +197,7 @@ class RateProvider { hash: '', fees: { percentage: this.percentageFees.get(pairId)!, + swapInFee: this.percentageSwapInFees.get(pairId)!, minerFees: { baseAsset: this.feeProvider.minerFees.get(base)!, quoteAsset: this.feeProvider.minerFees.get(quote)!, diff --git a/lib/service/Service.ts b/lib/service/Service.ts index 489e7a983..74f7be96a 100644 --- a/lib/service/Service.ts +++ b/lib/service/Service.ts @@ -674,9 +674,13 @@ class Service { const rate = getRate(swap.rate!, swap.orderSide, false); - const percentageFee = this.rateProvider.feeProvider.getPercentageFee(swap.pair); + let percentageFee = this.rateProvider.feeProvider.getPercentageFee(swap.pair); + const percentageSwapInFee = this.rateProvider.feeProvider.getPercentageSwapInFee(swap.pair); const baseFee = this.rateProvider.feeProvider.getBaseFee(onchainCurrency, BaseFeeType.NormalClaim); + if (percentageSwapInFee !== 0) { + percentageFee = percentageSwapInFee; + } const invoiceAmount = this.calculateInvoiceAmount(swap.orderSide, rate, swap.onchainAmount, baseFee, percentageFee); this.verifyAmount(swap.pair, rate, invoiceAmount, swap.orderSide, false); @@ -730,14 +734,19 @@ class Service { this.verifyAmount(swap.pair, rate, invoiceAmount, swap.orderSide, false); - const { baseFee, percentageFee } = this.rateProvider.feeProvider.getFees( + const { baseFee, percentageFee, percentageSwapInFee } = this.rateProvider.feeProvider.getFees( swap.pair, rate, swap.orderSide, invoiceAmount, BaseFeeType.NormalClaim, ); - const expectedAmount = Math.floor(invoiceAmount * rate) + baseFee + percentageFee; + + let serviceFee = percentageFee; + if (percentageSwapInFee !== 0) { + serviceFee = percentageSwapInFee; + } + const expectedAmount = Math.floor(invoiceAmount * rate) + baseFee + serviceFee; if (swap.onchainAmount && expectedAmount > swap.onchainAmount) { const maxInvoiceAmount = this.calculateInvoiceAmount( @@ -745,7 +754,7 @@ class Service { rate, swap.onchainAmount, baseFee, - this.rateProvider.feeProvider.getPercentageFee(swap.pair), + Math.max(this.rateProvider.feeProvider.getPercentageFee(swap.pair),this.rateProvider.feeProvider.getPercentageSwapInFee(swap.pair)), ); throw Errors.INVALID_INVOICE_AMOUNT(maxInvoiceAmount); @@ -757,7 +766,7 @@ class Service { swap, invoice, expectedAmount, - percentageFee, + serviceFee, acceptZeroConf, this.eventHandler.emitSwapInvoiceSet, ); diff --git a/test/unit/rates/FeeProvider.spec.ts b/test/unit/rates/FeeProvider.spec.ts index 6bd53f608..595360969 100644 --- a/test/unit/rates/FeeProvider.spec.ts +++ b/test/unit/rates/FeeProvider.spec.ts @@ -34,11 +34,13 @@ describe('FeeProvider', () => { base: 'LTC', quote: 'BTC', fee: 2, + swapInFee: -1, }, { base: 'BTC', quote: 'BTC', fee: 0, + swapInFee: -1, }, { base: 'LTC', @@ -46,6 +48,7 @@ describe('FeeProvider', () => { // The FeeProvider should set this to 1 fee: undefined, + swapInFee: undefined, }, ]); @@ -112,11 +115,13 @@ describe('FeeProvider', () => { expect(feeProvider.getFees('LTC/BTC', 2, OrderSide.BUY, amount, BaseFeeType.NormalClaim)).toEqual({ baseFee: 6120, percentageFee: 4000000, + percentageSwapInFee: -2000000, }); expect(feeProvider.getFees('LTC/BTC', 2, OrderSide.BUY, amount, BaseFeeType.ReverseLockup)).toEqual({ baseFee: 459, percentageFee: 4000000, + percentageSwapInFee: -2000000, }); }); diff --git a/test/unit/rates/RateProvider.spec.ts b/test/unit/rates/RateProvider.spec.ts index 65da402c6..f588f8b34 100644 --- a/test/unit/rates/RateProvider.spec.ts +++ b/test/unit/rates/RateProvider.spec.ts @@ -25,6 +25,11 @@ const percentageFees = new Map([ ['BTC/BTC', 0.005], ]); +const percentageSwapInFees = new Map([ + ['LTC/BTC', 0.01], + ['BTC/BTC', -0.01], +]); + const minerFees = new Map([ [ 'BTC', @@ -70,6 +75,7 @@ jest.mock('../../../lib/rates/FeeProvider', () => { return { minerFees, percentageFees, + percentageSwapInFees, getBaseFee: mockGetBaseFee, updateMinerFees: mockUpdateMinerFees, }; @@ -196,6 +202,14 @@ describe('RateProvider', () => { }); }); + test('should get percentage fees for swapin', () => { + const { pairs } = rateProvider; + + percentageSwapInFees.forEach((_, pairId) => { + expect(pairs.get(pairId)!.fees.swapInFee).toEqual(percentageSwapInFees.get(pairId)! * 100); + }); + }); + test('should get miner fees', () => { const { pairs } = rateProvider; diff --git a/test/unit/service/Service.spec.ts b/test/unit/service/Service.spec.ts index 019e026fd..77ae8ff7e 100644 --- a/test/unit/service/Service.spec.ts +++ b/test/unit/service/Service.spec.ts @@ -240,6 +240,7 @@ const mockInitFeeProvider = jest.fn().mockReturnValue(undefined); const mockGetFees = jest.fn().mockReturnValue({ baseFee: 1, percentageFee: 1, + percentageSwapInFee: 0, }); const mockGetBaseFeeResult = 320; @@ -248,12 +249,16 @@ const mockGetBaseFee = jest.fn().mockReturnValue(mockGetBaseFeeResult); const mockGetPercentageFeeResult = 0.02; const mockGetPercentageFee = jest.fn().mockReturnValue(mockGetPercentageFeeResult); +const mockGetPercentageSwapInFeeResult = 0.02; +const mockGetPercentageSwapInFee = jest.fn().mockReturnValue(mockGetPercentageSwapInFeeResult); + jest.mock('../../../lib/rates/FeeProvider', () => { return jest.fn().mockImplementation(() => ({ init: mockInitFeeProvider, getFees: mockGetFees, getBaseFee: mockGetBaseFee, getPercentageFee: mockGetPercentageFee, + getPercentageSwapInFee: mockGetPercentageSwapInFee, })); }); @@ -965,6 +970,7 @@ describe('Service', () => { })).rejects.toEqual(Errors.SWAP_WITH_PREIMAGE_EXISTS()); }); + // TODO: Add anohter test for swapInFee test('should get swap rates', async () => { const id = 'id'; @@ -993,6 +999,7 @@ describe('Service', () => { await expect(service.getSwapRates(id)).rejects.toEqual(Errors.SWAP_NOT_FOUND(id)); }); + // TODO: Add anohter test for swapInFee test('should set invoices of swaps', async () => { mockGetSwapResult = { id: 'invoiceId', diff --git a/tools/pyproject.toml b/tools/pyproject.toml index 317c7b403..accb045b2 100644 --- a/tools/pyproject.toml +++ b/tools/pyproject.toml @@ -10,7 +10,8 @@ python-bitcoinrpc = "^1.0" pyotp = "^2.8.0" sseclient = "^0.0.27" ruff = "^0.0.188" - +jinja2 = "<3.1.0" +Markdown = "<3.2" [build-system] requires = ["poetry-core"]