Skip to content

Commit

Permalink
feat: FeeProvider for chain swaps
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Mar 20, 2024
1 parent 02906af commit f444b11
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 85 deletions.
1 change: 1 addition & 0 deletions lib/consts/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type PairConfig = {
// Percentage of the amount that will be charged as fee
fee?: number;
swapInFee?: number;
chainSwapFee?: number;

// If there is a hardcoded rate the APIs of the exchanges will not be queried
rate?: number;
Expand Down
141 changes: 104 additions & 37 deletions lib/rates/FeeProvider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import Logger from '../Logger';
import { getChainCurrency, getPairId, splitPairId, stringify } from '../Utils';
import {
getChainCurrency,
getPairId,
getReceivingChain,
getSendingChain,
mapToObject,
splitPairId,
stringify,
} from '../Utils';
import ElementsClient from '../chain/ElementsClient';
import { etherDecimals, gweiDecimals } from '../consts/Consts';
import {
BaseFeeType,
CurrencyType,
OrderSide,
SwapType,
SwapVersion,
swapTypeToString,
} from '../consts/Enums';
import { PairConfig } from '../consts/Types';
import WalletManager from '../wallet/WalletManager';
Expand All @@ -26,8 +36,9 @@ type TransactionSizes = {
};

type PercentageFees = {
percentage: number;
percentageSwapIn: number;
[SwapType.Chain]: number;
[SwapType.Submarine]: number;
[SwapType.ReverseSubmarine]: number;
};

type ReverseMinerFees = {
Expand All @@ -40,18 +51,17 @@ type MinerFeesForVersion = {
reverse: ReverseMinerFees;
};

type ChainSwapMinerFees = {
server: number;
user: ReverseMinerFees;
};

type MinerFees = {
[SwapVersion.Legacy]: MinerFeesForVersion;
[SwapVersion.Taproot]: MinerFeesForVersion;
};

class FeeProvider {
// A map between the symbols of the pairs and their percentage fees
public percentageFees = new Map<string, number>();
public percentageSwapInFees = new Map<string, number>();

public minerFees = new Map<string, MinerFees>();

public static transactionSizes: {
[CurrencyType.BitcoinLike]: TransactionSizes;
[CurrencyType.Liquid]: TransactionSizes;
Expand Down Expand Up @@ -106,6 +116,11 @@ class FeeProvider {

private static readonly defaultFee = 1;

// A map between the symbols of the pairs and their percentage fees
public percentageFees = new Map<string, PercentageFees>();

public minerFees = new Map<string, MinerFees>();

constructor(
private logger: Logger,
private walletManager: WalletManager,
Expand All @@ -114,8 +129,6 @@ class FeeProvider {
) {}

public init = (pairs: PairConfig[]): void => {
const feesToPrint = {};

pairs.forEach((pair) => {
const pairId = getPairId(pair);

Expand All @@ -129,37 +142,46 @@ class FeeProvider {
);
}

this.percentageFees.set(pairId, percentage / 100);

if (pair.swapInFee) {
this.percentageSwapInFees.set(pairId, pair.swapInFee / 100);
}

feesToPrint[pairId] = this.getPercentageFees(pairId);
this.percentageFees.set(pairId, {
[SwapType.ReverseSubmarine]: percentage,
[SwapType.Chain]: pair.chainSwapFee || percentage,
[SwapType.Submarine]: pair.swapInFee || percentage,
});
});

this.logger.debug(
`Prepared data for fee estimations: ${stringify(feesToPrint)}`,
`Using fees: ${stringify(
mapToObject(
new Map<string, any>(
Array.from(this.percentageFees.entries()).map(([pair, fees]) => [
pair,
{
[swapTypeToString(SwapType.Chain)]: fees[SwapType.Chain],
[swapTypeToString(SwapType.Submarine)]:
fees[SwapType.Submarine],
[swapTypeToString(SwapType.ReverseSubmarine)]:
fees[SwapType.ReverseSubmarine],
},
]),
),
),
)}`,
);
};

public getPercentageFees = (pairId: string): PercentageFees => {
const percentage = this.percentageFees.get(pairId)!;
const percentageSwapIn =
this.percentageSwapInFees.get(pairId) || percentage;
const percentages = this.percentageFees.get(pairId)!;

return {
percentage: percentage * 100,
percentageSwapIn: percentageSwapIn * 100,
[SwapType.Chain]: percentages[SwapType.Chain] * 100,
[SwapType.Submarine]: percentages[SwapType.Submarine] * 100,
[SwapType.ReverseSubmarine]: percentages[SwapType.ReverseSubmarine] * 100,
};
};

public getPercentageFee = (pair: string, isReverse: boolean): number => {
if (!isReverse && this.percentageSwapInFees.has(pair)) {
return this.percentageSwapInFees.get(pair)!;
}

return this.percentageFees.get(pair) || 0;
public getPercentageFee = (pair: string, type: SwapType): number => {
const percentages = this.percentageFees.get(pair);
return percentages ? percentages[type] : 0;
};

public getFees = (
Expand All @@ -168,25 +190,29 @@ class FeeProvider {
rate: number,
orderSide: OrderSide,
amount: number,
type: BaseFeeType,
type: SwapType,
feeType: BaseFeeType,
): {
baseFee: number;
percentageFee: number;
} => {
const isReverse = type !== BaseFeeType.NormalClaim;

let percentageFee = this.getPercentageFee(pair, isReverse);
let percentageFee = this.getPercentageFee(pair, type);

if (percentageFee !== 0) {
percentageFee = percentageFee * amount * rate;
}

const { base, quote } = splitPairId(pair);
const chainCurrency = getChainCurrency(base, quote, orderSide, isReverse);
const chainCurrency = getChainCurrency(
base,
quote,
orderSide,
type === SwapType.ReverseSubmarine,
);

return {
percentageFee: Math.ceil(percentageFee),
baseFee: this.getBaseFee(chainCurrency, swapVersion, type),
baseFee: this.getBaseFee(chainCurrency, swapVersion, feeType),
};
};

Expand All @@ -209,6 +235,41 @@ class FeeProvider {
}
};

public getSwapBaseFees = <
T extends number | ReverseMinerFees | ChainSwapMinerFees,
>(
pairId: string,
orderSide: OrderSide,
type: SwapType,
version: SwapVersion,
): T => {
const { base, quote } = splitPairId(pairId);

if (type === SwapType.Chain) {
const sendingMinerfees = this.minerFees.get(
getSendingChain(base, quote, orderSide),
)![SwapVersion.Taproot].reverse;
const receivingMinerFees = this.minerFees.get(
getReceivingChain(base, quote, orderSide),
)![SwapVersion.Taproot].reverse;

return {
server: sendingMinerfees.lockup + receivingMinerFees.claim,
user: {
claim: sendingMinerfees.claim,
lockup: receivingMinerFees.lockup,
},
} as ChainSwapMinerFees as T;
} else {
const isReverse = type === SwapType.ReverseSubmarine;

const minerFeesObj = this.minerFees.get(
getChainCurrency(base, quote, orderSide, isReverse),
)![version];
return (isReverse ? minerFeesObj.reverse : minerFeesObj.normal) as T;
}
};

public updateMinerFees = async (chainCurrency: string): Promise<void> => {
const feeMap = await this.getFeeEstimation(chainCurrency);

Expand Down Expand Up @@ -332,4 +393,10 @@ class FeeProvider {
}

export default FeeProvider;
export { ReverseMinerFees, MinerFees, PercentageFees, MinerFeesForVersion };
export {
MinerFees,
PercentageFees,
ReverseMinerFees,
ChainSwapMinerFees,
MinerFeesForVersion,
};
49 changes: 12 additions & 37 deletions lib/rates/providers/RateProviderTaproot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import { PairConfig } from '../../consts/Types';
import Errors from '../../service/Errors';
import NodeSwitch from '../../swap/NodeSwitch';
import { Currency } from '../../wallet/WalletManager';
import FeeProvider, { ReverseMinerFees } from '../FeeProvider';
import FeeProvider, {
ChainSwapMinerFees,
ReverseMinerFees,
} from '../FeeProvider';
import RateProviderBase from './RateProviderBase';

type PairLimits = {
Expand Down Expand Up @@ -50,10 +53,7 @@ type ChainPairTypeTaproot = PairTypeTaproot & {
limits: PairLimitWithZeroConf;
fees: {
percentage: number;
minerFees: {
server: number;
user: ReverseMinerFees;
};
minerFees: ChainSwapMinerFees;
};
};

Expand Down Expand Up @@ -290,45 +290,20 @@ class RateProviderTaproot extends RateProviderBase<SwapTypes> {
}

if (minerFees === undefined) {
const { base, quote } = splitPairId(pairId);

if (type === SwapType.Chain) {
const sendingMinerfees = this.feeProvider.minerFees.get(
getSendingChain(base, quote, orderSide),
)![SwapVersion.Taproot].reverse;
const receivingMinerFees = this.feeProvider.minerFees.get(
getReceivingChain(base, quote, orderSide),
)![SwapVersion.Taproot].reverse;

minerFees = {
server: sendingMinerfees.lockup + receivingMinerFees.claim,
user: {
claim: sendingMinerfees.claim,
lockup: receivingMinerFees.lockup,
},
} as ChainPairTypeTaproot['fees']['minerFees'];
} else {
const isReverse = type === SwapType.ReverseSubmarine;

const minerFeesObj = this.feeProvider.minerFees.get(
getChainCurrency(base, quote, orderSide, isReverse),
)![SwapVersion.Taproot];
minerFees = isReverse ? minerFeesObj.reverse : minerFeesObj.normal;
}
minerFees = this.feeProvider.getSwapBaseFees<T['fees']['minerFees']>(
pairId,
orderSide,
type,
SwapVersion.Taproot,
);
}

const percentageFees = this.feeProvider.getPercentageFees(pairId);

const pair: T = {
hash: '',
rate: rate,
limits: this.getLimits(pairId, orderSide, type, rate) as T['limits'],
fees: {
// TODO: separate onchain fee for chain swaps
percentage:
type === SwapType.Submarine
? percentageFees.percentageSwapIn
: percentageFees.percentage,
percentage: this.feeProvider.getPercentageFees(pairId)[type],
minerFees,
},
} as T;
Expand Down
27 changes: 16 additions & 11 deletions lib/service/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
GetInfoResponse,
LightningInfo,
} from '../proto/boltzrpc_pb';
import { ChainSwapMinerFees } from '../rates/FeeProvider';
import RateProvider from '../rates/RateProvider';
import { PairTypeLegacy } from '../rates/providers/RateProviderLegacy';
import ErrorsSwap from '../swap/Errors';
Expand Down Expand Up @@ -1075,7 +1076,7 @@ class Service {

const percentageFee = this.rateProvider.feeProvider.getPercentageFee(
swap.pair,
false,
SwapType.Submarine,
);
const baseFee = this.rateProvider.feeProvider.getBaseFee(
onchainCurrency,
Expand Down Expand Up @@ -1212,6 +1213,7 @@ class Service {
rate,
swap.orderSide,
swap.invoiceAmount,
SwapType.Submarine,
BaseFeeType.NormalClaim,
);

Expand All @@ -1224,7 +1226,10 @@ class Service {
rate,
swap.onchainAmount,
baseFee,
this.rateProvider.feeProvider.getPercentageFee(swap.pair, false),
this.rateProvider.feeProvider.getPercentageFee(
swap.pair,
SwapType.Submarine,
),
);

throw Errors.INVALID_INVOICE_AMOUNT(maxInvoiceAmount);
Expand Down Expand Up @@ -1503,7 +1508,7 @@ class Service {
const rate = getRate(pairRate, side, true);
const feePercent = this.rateProvider.feeProvider.getPercentageFee(
args.pairId,
true,
SwapType.ReverseSubmarine,
);
const baseFee = this.rateProvider.feeProvider.getBaseFee(
sendingCurrency.symbol,
Expand Down Expand Up @@ -1755,17 +1760,17 @@ class Service {
);

const rate = getRate(pairRate, side, true);
// TODO: separate configurable percentage fee for chain swaps
const feePercent = this.rateProvider.feeProvider.getPercentageFee(
args.pairId,
true,
);
// TODO: base fee calculator in fee provider
const baseFee = this.rateProvider.feeProvider.getBaseFee(
sendingCurrency.symbol,
SwapVersion.Taproot,
BaseFeeType.ReverseLockup,
SwapType.Chain,
);
const baseFee =
this.rateProvider.feeProvider.getSwapBaseFees<ChainSwapMinerFees>(
args.pairId,
side,
SwapType.Chain,
SwapVersion.Taproot,
).server;

let percentageFee: number;

Expand Down

0 comments on commit f444b11

Please sign in to comment.