///
import { BigNumber } from '@waves/bignumber';
import path = require('ramda/src/path');
import {
IExchangeTransactionOrder,
TTransaction,
IDataTransaction,
IMassTransferTransaction,
IIssueTransaction
} from '@waves/ts-types';
import { DCC_ID } from './prepareTx';
export function find(some: Partial, list: Array) {
const keys = Object.keys(some);
//@ts-ignore
const isEqual = (a) => keys.every(n => a[n] === some[n]);
for (let i = 0; i < list.length; i++) {
if (isEqual(list[i])) {
return list[i];
}
}
return null;
}
export function isEmpty(some: unknown): some is undefined {
return some == null;
}
export function normalizeAssetId(assetId: string) {
return assetId || DCC_ID;
}
export function last(list: Array): T {
return list[list.length - 1];
}
export const TRANSACTION_TYPE = { // TODO Remove after refactor ts-types lib
GENESIS: 1 as 1,
PAYMENT: 2 as 2,
ISSUE: 3 as 3,
TRANSFER: 4 as 4,
REISSUE: 5 as 5,
BURN: 6 as 6,
EXCHANGE: 7 as 7,
LEASE: 8 as 8,
CANCEL_LEASE: 9 as 9,
ALIAS: 10 as 10,
MASS_TRANSFER: 11 as 11,
DATA: 12 as 12,
SET_SCRIPT: 13 as 13,
SPONSORSHIP: 14 as 14,
SET_ASSET_SCRIPT: 15 as 15,
SCRIPT_INVOCATION: 16 as 16,
UPDATE_ASSET_INFO: 17 as 17,
};
export function currentCreateOrderFactory(config: IFeeConfig, minOrderFee: BigNumber): (order: IExchangeTransactionOrder, hasMatcherScript?: boolean, smartAssetIdList?: Array) => BigNumber {
return (order, hasScript = false, smartAssetIdList = []) => {
const accountFee: BigNumber = hasScript ? new BigNumber(config.smart_account_extra_fee) : new BigNumber(0);
const extraFee: BigNumber = Object
.values(order.assetPair)
.map(id => {
return id && smartAssetIdList.includes(id as string) ? new BigNumber(config.smart_asset_extra_fee) : new BigNumber(0);
})
.reduce((sum, item) => sum.add(item), new BigNumber(0));
return minOrderFee.add(accountFee).add(extraFee);
};
}
export function currentFeeFactory(config: IFeeConfig): (tx: TTransaction, bytes: Uint8Array, hasAccountScript: boolean, smartAssetIdList?: Array) => BigNumber {
return (tx: TTransaction, bytes: Uint8Array, hasAccountScript: boolean, smartAssetIdList?: Array) => {
const accountFee = hasAccountScript ? new BigNumber(config.smart_account_extra_fee) : new BigNumber(0);
const minFee = accountFee.add(getConfigProperty(tx.type, 'fee', config));
switch (tx.type) {
case TRANSACTION_TYPE.CANCEL_LEASE:
case TRANSACTION_TYPE.ALIAS:
case TRANSACTION_TYPE.LEASE:
case TRANSACTION_TYPE.SET_ASSET_SCRIPT:
case TRANSACTION_TYPE.SET_SCRIPT:
case TRANSACTION_TYPE.SPONSORSHIP:
return minFee;
case TRANSACTION_TYPE.REISSUE:
case TRANSACTION_TYPE.BURN:
case TRANSACTION_TYPE.TRANSFER:
return minFee.add(getSmartAssetFeeByAssetId(tx.assetId, config, smartAssetIdList || []));
case TRANSACTION_TYPE.MASS_TRANSFER:
return minFee.add(getMassTransferFee(tx, config, smartAssetIdList || []));
case TRANSACTION_TYPE.DATA:
return accountFee.add(getDataFee(bytes, tx, config));
case TRANSACTION_TYPE.ISSUE:
return getIssueFee(tx, accountFee, config);
default:
throw new Error('Wrong transaction type!');
}
};
}
function isNFT(tx: IIssueTransaction & { precision?: number }): boolean {
const { quantity, precision, decimals, reissuable } = tx;
const nftQuantity = new BigNumber(quantity).eq(1);
const nftPrecision = new BigNumber(precision || decimals || 0).eq(0);
return !reissuable && nftPrecision && nftQuantity;
}
function getIssueFee(tx: IIssueTransaction & { precision?: number }, accountFee: BigNumber, config: IFeeConfig): BigNumber {
const minFee: BigNumber = accountFee.add(getConfigProperty(tx.type, 'fee', config));
if (isNFT(tx)) {
return accountFee.add(getConfigProperty(tx.type, 'nftFee', config));
} else {
return minFee;
}
}
function getSmartAssetFeeByAssetId(assetId: string | null, config: IFeeConfig, smartAssetIdList: Array): BigNumber {
return assetId && smartAssetIdList.includes(assetId) ? new BigNumber(config.smart_asset_extra_fee) : new BigNumber(0);
}
function getDataFee(bytes: Uint8Array, tx: IDataTransaction, config: IFeeConfig): BigNumber {
const kbPrice = getConfigProperty(tx.type, 'price_per_kb', config) || 0;
return new BigNumber(kbPrice).mul(Math.floor(1 + (bytes.length - 1) / 1024));
}
function getMassTransferFee(tx: IMassTransferTransaction, config: IFeeConfig, smartAssetIdList: Array): BigNumber {
const transferPrice = new BigNumber(getConfigProperty(tx.type, 'price_per_transfer', config) || 0);
const transfersCount: number = path(['transfers', 'length'], tx) || 0;
const smartAssetExtraFee = tx.assetId && smartAssetIdList.includes(tx.assetId) ? new BigNumber(config.smart_asset_extra_fee) : new BigNumber(0);
const minPriceStep = new BigNumber(getConfigProperty(tx.type, 'min_price_step', config));
let price = transferPrice.mul(transfersCount);
if (!price.div(minPriceStep).isInt()) {
price = price.div(minPriceStep).roundTo(0, BigNumber.ROUND_MODE.ROUND_UP).mul(minPriceStep);
}
return price.add(smartAssetExtraFee);
}
function getConfigProperty(type: number, propertyName: T, config: IFeeConfig): IFeeConfigItem[T] {
const value = path(['calculate_fee_rules', type, propertyName], config) as IFeeConfigItem[T];
return isEmpty(value) ? path(['calculate_fee_rules', 'default', propertyName], config) : value as any;
}
export interface IFeeConfig {
smart_asset_extra_fee: BigNumber;
smart_account_extra_fee: BigNumber;
calculate_fee_rules: Record> & { default: IFeeConfigItem }
}
export interface IFeeConfigItem {
price_per_transfer?: BigNumber;
price_per_kb?: BigNumber;
add_smart_asset_fee: boolean;
add_smart_account_fee: boolean;
min_price_step: BigNumber;
fee: BigNumber;
nftFee: BigNumber;
}