Skip to content

Commit

Permalink
add new tx types, rework fee module to constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
shrpne committed Jan 26, 2021
1 parent 6b483f9 commit 4fa90d8
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 115 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Expand Up @@ -62,6 +62,8 @@ module.exports = {
}
}],
'import/extensions': ['error', 'always', {ignorePackages: true} ],
// named exports are not bad
'import/prefer-default-export': 0,
},
overrides: [
{
Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
@@ -1,5 +1,8 @@
## WIP
- add v2.0 tx types and fees
- add v2.0 tx types
- **BREAKING** fee module now designed as constructor, which accepts current network fee values and returns instance with method `getFeeValue`
- add `payloadLength` param to `getFeeValue`
- **BREAKING** TX_TYPE.EDIT_COIN_OWNER renamed to TX_TYPE.EDIT_TICKER_OWNER

## 0.20.0 - 2020.12.30
- **BREAKING** change fee calculation for Create Coin tx (now it depends on unit value)
Expand Down
10 changes: 7 additions & 3 deletions README.md
Expand Up @@ -25,20 +25,24 @@ or from browser
<script src="https://unpkg.com/minterjs-util"></script>
<script>
const pips = minterUtil.convertFromPip(1);
const fee = minterUtil.getFeeValue('0x01');
const fee = (new minterUtil.BaseCoinFee({/* ... */})).getFeeValue('0x01');
</script>
```

### getFeeValue()
### BaseCoinFee.getFeeValue()
Params:
- txType: number or string, tx type
- options: object
- options.payload: string or Buffer, tx payload
- options.payloadLength: number, length of payload
- options.coinSymbol: string, coin symbol if tx is coin creation (can be replaced with `coinSymbolLength`)
- options.coinSymbolLength: number, coin symbol length if tx is coin creation (can be replaced with `coinSymbol`)
- options.multisendCount: number, count of recipients if tx is multisend

Full example: [github.com/MinterTeam/minterjs-util/blob/master/test/fee.test.js](https://github.com/MinterTeam/minterjs-util/blob/master/test/fee.test.js)
```
import { getFeeValue, TX_TYPE } from 'minterjs-util';
import { BaseCoinFee, TX_TYPE } from 'minterjs-util';
const getFeeValue = (new BaseCoinFee({/* ... */})).getFeeValue;
getFeeValue(1);
// 0.01
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "minterjs-util",
"version": "0.21.0-aplha.3",
"version": "0.21.0-aplha.4",
"description": "Utils for Minter",
"main": "dist/cjs/index.js",
"module": "src/index.js",
Expand Down
177 changes: 80 additions & 97 deletions src/fee.js
@@ -1,112 +1,95 @@
import {padToEven, isHexString, getBinarySize} from 'ethjs-util';
import {TX_TYPE} from './tx-types.js';
import Big from 'big.js';
import {getBinarySize} from 'ethjs-util';
import {TX_TYPE, normalizeTxType} from './tx-types.js';
import {convertFromPip} from './converter.js';

/**
* Accept current network fee values in pips.
* Provide instance to calculate fee for particular transaction based on its params.
*
* @param txType
* @param {string|Buffer} [payload]
* @param {string} [coinSymbol]
* @param {number} [coinSymbolLength]
* @param {number} [multisendCount]
* @return {boolean|number}
* @param {BaseFeeList} baseFeeList
* @param {TickerFeeLest} tickerFeeList
* @param {number|string} payloadByteFee
* @param {number|string} multisendRecipientFee
* @constructor
*/
export function getFeeValue(txType, {payload, coinSymbol, coinSymbolLength, multisendCount} = {}) {
// txType to string
if (!isHexString(txType)) {
txType = `0x${padToEven(txType.toString(16)).toUpperCase()}`;
export function BaseCoinFee({baseFeeList, tickerFeeList, payloadByteFee, multisendRecipientFee}) {
if (typeof baseFeeList !== 'object') {
throw new TypeError('Invalid baseFeeList specified');
}

if (txType === TX_TYPE.MULTISEND && !(multisendCount >= 1)) {
throw new Error('`multisendCount` should be positive integer when tx type is TX_TYPE.MULTISEND');
if (typeof tickerFeeList !== 'object') {
throw new TypeError('Invalid tickerFeeList specified');
}

let payloadLength;
if (!payload) {
payloadLength = 0;
} else if (Buffer.isBuffer(payload)) {
payloadLength = payload.length;
} else {
payloadLength = getBinarySize(payload.toString());
if (typeof payloadByteFee === 'undefined') {
throw new TypeError('payloadByteFee is undefined');
}
if (typeof multisendRecipientFee === 'undefined') {
throw new TypeError('multisendRecipientFee is undefined');
}

const baseUnits = BASE_FEES[txType];
// commission multiplier
const COIN_UNIT = 0.1;
const COIN_UNIT_PART = 1 / COIN_UNIT; // negate js math quirks, ex.: 18 * 0.001 = 0.018000000000000002
// multisend fee = base fee + extra fee based on count
const multisendExtraCountFee = txType === TX_TYPE.MULTISEND ? (multisendCount - 1) * MULTISEND_FEE_DELTA : 0;
// coin symbol extra fee, value in units (not in base coin)
const coinSymbolFee = txType === TX_TYPE.CREATE_COIN ? getCoinSymbolFee(coinSymbol, coinSymbolLength) : 0;
return (baseUnits + payloadLength * 2 + multisendExtraCountFee + coinSymbolFee) / COIN_UNIT_PART;
}
this.baseFeeList = baseFeeList;
this.tickerFeeList = tickerFeeList;
this.payloadByteFee = payloadByteFee;
this.multisendRecipientFee = multisendRecipientFee;

//
/**
* @param {string} ticker
* @param {number} [length]
* @return {number} - value in base coin (not in units)
*/
export function getCoinSymbolFee(ticker, length) {
if (!length) {
length = ticker?.length;
}
if (!isValidLength(length)) {
length = 7;
}
return COIN_SYMBOL_FEES[length];
/**
* @param txType
* @param {string|Buffer} [payload]
* @param {number} [payloadLength]
* @param {string} [coinSymbol]
* @param {number} [coinSymbolLength]
* @param {number} [multisendCount]
* @return {number|string}
*/
this.getFeeValue = (txType, {payload, payloadLength = 0, coinSymbol, coinSymbolLength, multisendCount} = {}) => {
// txType to string
txType = normalizeTxType(txType);

// eslint-disable-next-line unicorn/consistent-function-scoping, no-shadow
function isValidLength(length) {
return length >= 3 && length <= 7;
}
}
if (txType === TX_TYPE.MULTISEND && !(multisendCount >= 1)) {
throw new Error('`multisendCount` should be positive integer when tx type is TX_TYPE.MULTISEND');
}

if (Buffer.isBuffer(payload)) {
payloadLength = payload.length;
} else if (payload) {
payloadLength = getBinarySize(payload.toString());
}

const baseFee = this.baseFeeList[txType];
// multisend fee = base fee + extra fee based on count
const multisendExtraCountFee = txType === TX_TYPE.MULTISEND ? new Big(multisendCount - 1).times(this.multisendRecipientFee) : 0;
// coin symbol extra fee
const tickerLengthFee = txType === TX_TYPE.CREATE_COIN || txType === TX_TYPE.CREATE_TOKEN ? this.getCoinSymbolFee(coinSymbol, coinSymbolLength) : 0;
const payloadFee = new Big(this.payloadByteFee).times(payloadLength);

return convertFromPip(new Big(baseFee).plus(payloadFee).plus(multisendExtraCountFee).plus(tickerLengthFee));
};

// value in units (not in base coin)
// @See https://github.com/MinterTeam/minter-go-node/blob/master/core/transaction/create_coin.go#L93
export const COIN_SYMBOL_FEES = {
3: 1_000_000_000,
4: 100_000_000,
5: 10_000_000,
6: 1_000_000,
7: 100_000,
};
/**
* @param {string} [ticker]
* @param {number} [length]
* @return {number|string} - value in pip
*/
this.getCoinSymbolFee = (ticker, length) => {
length = ticker ? ticker.length : length;
if (!isValidLength(length)) {
length = 7;
}
return this.tickerFeeList[length];

export const MULTISEND_FEE_DELTA = 5;
// eslint-disable-next-line unicorn/consistent-function-scoping, no-shadow
function isValidLength(length) {
return length >= 3 && length <= 7;
}
};
}

/**
* @typedef {Object} TickerFeeLest
* @type {{'3': number|string, '4': number|string, '5': number|string, '6': number|string, '7': number|string}}
*/

/**
* Tx fees in units
* @type {{string: number}}
* @typedef {Object} BaseFeeList
* @type {{TX_TYPE: number|string}}
*/
export const BASE_FEES = {
[TX_TYPE.SEND]: 10,
[TX_TYPE.SELL]: 100,
[TX_TYPE.SELL_ALL]: 100, // same as SELL
[TX_TYPE.BUY]: 100, // same as SELL
[TX_TYPE.CREATE_COIN]: 0,
[TX_TYPE.DECLARE_CANDIDACY]: 10000,
[TX_TYPE.DELEGATE]: 200,
[TX_TYPE.UNBOND]: 200, // same as DELEGATE
[TX_TYPE.REDEEM_CHECK]: 30, // SEND * 3
[TX_TYPE.SET_CANDIDATE_ON]: 100,
[TX_TYPE.SET_CANDIDATE_OFF]: 100,
[TX_TYPE.CREATE_MULTISIG]: 100,
[TX_TYPE.MULTISEND]: 10, // 10+(n-1)*5 units
[TX_TYPE.EDIT_CANDIDATE]: 10000,
[TX_TYPE.SET_HALT_BLOCK]: 1000,
[TX_TYPE.RECREATE_COIN]: 10000000,
[TX_TYPE.EDIT_COIN_OWNER]: 10000000,
[TX_TYPE.EDIT_MULTISIG]: 1000,
[TX_TYPE.PRICE_VOTE]: 10,
[TX_TYPE.EDIT_CANDIDATE_PUBLIC_KEY]: 100000000,
[TX_TYPE.ADD_LIQUIDITY]: 100,
[TX_TYPE.REMOVE_LIQUIDITY]: 100,
[TX_TYPE.SELL_SWAP_POOL]: 100, // same as SELL
[TX_TYPE.BUY_SWAP_POOL]: 100, // same as SELL
[TX_TYPE.SELL_ALL_SWAP_POOL]: 100, // same as SELL
[TX_TYPE.EDIT_CANDIDATE_COMMISSION]: 10000,
[TX_TYPE.MOVE_STAKE]: 200, // DELEGATE * 3
[TX_TYPE.MINT_TOKEN]: 100, // same as SELL
[TX_TYPE.BURN_TOKEN]: 100, // same as SELL
[TX_TYPE.CREATE_TOKEN]: 0, // same as CREATE_COIN
[TX_TYPE.RECREATE_TOKEN]: 10000000, // same as RECREATE_COIN
};
5 changes: 2 additions & 3 deletions src/index.js
Expand Up @@ -4,7 +4,7 @@ import {convert, convertFromPip, convertToPip, numberToBig, numToBig} from './co
import {
mPrefixToHex, mPrefixStrip, mToBuffer, toBuffer, addressToString, checkToString, privateToAddressString, isMinterPrefixed, isValidAddress, isValidCheck, isValidTransaction, isValidPublicKeyString,
} from './prefix.js';
import {getFeeValue, BASE_FEES} from './fee.js';
import {BaseCoinFee} from './fee.js';
import {
sellCoin, sellCoinByBip, buyCoin, buyCoinByCoin,
} from './coin-math.js';
Expand Down Expand Up @@ -33,7 +33,7 @@ export {
isValidTransaction,
isValidPublicKeyString,
isValidPublic,
getFeeValue,
BaseCoinFee,
sellCoin,
sellCoinByBip,
buyCoin,
Expand All @@ -43,7 +43,6 @@ export {
TX_TYPE,
txTypeList,
normalizeTxType,
BASE_FEES,
COIN_MAX_AMOUNT,
COIN_MAX_MAX_SUPPLY,
COIN_MIN_MAX_SUPPLY,
Expand Down
22 changes: 13 additions & 9 deletions src/tx-types.js
@@ -1,7 +1,7 @@
import {padToEven} from 'ethjs-util';

/**
* @enum {string}
* @enum {string} TX_TYPE
*/
export const TX_TYPE = {
SEND: '0x01',
Expand All @@ -20,7 +20,7 @@ export const TX_TYPE = {
EDIT_CANDIDATE: '0x0E',
SET_HALT_BLOCK: '0x0F',
RECREATE_COIN: '0x10',
EDIT_COIN_OWNER: '0x11',
EDIT_TICKER_OWNER: '0x11',
EDIT_MULTISIG: '0x12',
PRICE_VOTE: '0x13',
EDIT_CANDIDATE_PUBLIC_KEY: '0x14',
Expand All @@ -29,12 +29,14 @@ export const TX_TYPE = {
SELL_SWAP_POOL: '0x17',
BUY_SWAP_POOL: '0x18',
SELL_ALL_SWAP_POOL: '0x19',
EDIT_CANDIDATE_COMMISSION: '0x20',
MOVE_STAKE: '0x21',
MINT_TOKEN: '0x22',
BURN_TOKEN: '0x23',
CREATE_TOKEN: '0x24',
RECREATE_TOKEN: '0x25',
EDIT_CANDIDATE_COMMISSION: '0x1A',
MOVE_STAKE: '0x1B',
MINT_TOKEN: '0x1C',
BURN_TOKEN: '0x1D',
CREATE_TOKEN: '0x1E',
RECREATE_TOKEN: '0x1F',
PRICE_COMMISSION: '0x20',
UPDATE_NETWORK: '0x21',
};

/** @type {Array<{hex: string, name: string, number: number}>} */
Expand Down Expand Up @@ -71,7 +73,7 @@ fillList(TX_TYPE.MULTISEND, 'multisend');
fillList(TX_TYPE.EDIT_CANDIDATE, 'edit candidate');
fillList(TX_TYPE.SET_HALT_BLOCK, 'set halt block');
fillList(TX_TYPE.RECREATE_COIN, 'recreate coin');
fillList(TX_TYPE.EDIT_COIN_OWNER, 'edit coin owner');
fillList(TX_TYPE.EDIT_TICKER_OWNER, 'edit ticker owner');
fillList(TX_TYPE.EDIT_MULTISIG, 'edit multisig');
fillList(TX_TYPE.PRICE_VOTE, 'price vote');
fillList(TX_TYPE.EDIT_CANDIDATE_PUBLIC_KEY, 'edit candidate public key');
Expand All @@ -86,6 +88,8 @@ fillList(TX_TYPE.MINT_TOKEN, 'mint token');
fillList(TX_TYPE.BURN_TOKEN, 'burn token');
fillList(TX_TYPE.CREATE_TOKEN, 'create token');
fillList(TX_TYPE.RECREATE_TOKEN, 'recreate token');
fillList(TX_TYPE.PRICE_COMMISSION, 'PRICE_COMMISSION');
fillList(TX_TYPE.UPDATE_NETWORK, 'UPDATE_NETWORK');

export {txTypeList};

Expand Down

0 comments on commit 4fa90d8

Please sign in to comment.