UNPKG

@aeternity/aepp-sdk

Version:

SDK for the æternity blockchain

160 lines (155 loc) 5.32 kB
import BigNumber from 'bignumber.js'; import { ArgumentError, IllegalArgumentError } from '../../../utils/errors.js'; import { MIN_GAS_PRICE, Tag } from '../constants.js'; import uInt from './u-int.js'; import coinAmount from './coin-amount.js'; import { getCachedIncreasedGasPrice } from './gas-price.js'; import { isKeyOfObject } from '../../../utils/other.js'; import { decode } from '../../../utils/encoder.js'; const BASE_GAS = 15000; const GAS_PER_BYTE = 20; const KEY_BLOCK_INTERVAL = 3; /** * Calculate the base gas * @see {@link https://github.com/aeternity/protocol/blob/master/consensus/README.md#gas} * @param txType - The transaction type * @returns The base gas * @example * ```js * TX_BASE_GAS(Tag.ChannelForceProgressTx) => 30 * 15000 * ``` */ const TX_BASE_GAS = txType => { var _feeFactors; const feeFactors = { [Tag.ChannelForceProgressTx]: 30, [Tag.ChannelOffChainTx]: 0, [Tag.ContractCreateTx]: 5, [Tag.ContractCallTx]: 12, [Tag.GaAttachTx]: 5, [Tag.GaMetaTx]: 5, [Tag.PayingForTx]: 1 / 5 }; const factor = (_feeFactors = feeFactors[txType]) !== null && _feeFactors !== void 0 ? _feeFactors : 1; return factor * BASE_GAS; }; /** * Calculate gas for other types of transactions * @see {@link https://github.com/aeternity/protocol/blob/master/consensus/README.md#gas} * @param txType - The transaction type * @param txSize - The transaction size * @returns parameters - The transaction parameters * @returns parameters.relativeTtl - The relative ttl * @returns parameters.innerTxSize - The size of the inner transaction * @returns The other gas * @example * ```js * TX_OTHER_GAS(Tag.OracleRespondTx, 10, { relativeTtl: 12, innerTxSize: 0 }) * => 10 * 20 + Math.ceil(32000 * 12 / Math.floor(60 * 24 * 365 / 3)) * ``` */ const TX_OTHER_GAS = (txType, txSize, { relativeTtl, innerTxSize }) => { switch (txType) { case Tag.OracleRegisterTx: case Tag.OracleExtendTx: case Tag.OracleQueryTx: case Tag.OracleRespondTx: return txSize * GAS_PER_BYTE + Math.ceil(32000 * relativeTtl / Math.floor(60 * 24 * 365 / KEY_BLOCK_INTERVAL)); case Tag.GaMetaTx: case Tag.PayingForTx: return (txSize - innerTxSize) * GAS_PER_BYTE; default: return txSize * GAS_PER_BYTE; } }; function getOracleRelativeTtl(params) { const ttlKeys = { [Tag.OracleRegisterTx]: 'oracleTtlValue', [Tag.OracleExtendTx]: 'oracleTtlValue', [Tag.OracleQueryTx]: 'queryTtlValue', [Tag.OracleRespondTx]: 'responseTtlValue' }; const { tag } = params; if (!isKeyOfObject(tag, ttlKeys)) return 1; return params[ttlKeys[tag]]; } /** * Calculate gas based on tx type and params */ export function buildGas(builtTx, unpackTx, buildTx) { const { length } = decode(builtTx); const txObject = unpackTx(builtTx); let innerTxSize = 0; if (txObject.tag === Tag.GaMetaTx || txObject.tag === Tag.PayingForTx) { innerTxSize = decode(buildTx(txObject.tx.encodedTx)).length; } return TX_BASE_GAS(txObject.tag) + TX_OTHER_GAS(txObject.tag, length, { relativeTtl: getOracleRelativeTtl(txObject), innerTxSize }); } /** * Calculate min fee * @category transaction builder * @param rebuildTx - Callback to get built transaction with specific fee */ function calculateMinFee(rebuildTx, unpackTx, buildTx) { let fee = new BigNumber(0); let previousFee; do { previousFee = fee; fee = new BigNumber(MIN_GAS_PRICE).times(buildGas(rebuildTx(fee), unpackTx, buildTx)); } while (!fee.eq(previousFee)); return fee; } // TODO: Get rid of this workaround. Transaction builder can't accept/return gas price instead of // fee because it may get a decimal gas price. So, it should accept the optional `gasPrice` even // if it is not a contract-related transaction. And use this `gasPrice` to calculate `fee`. const gasPricePrefix = '_gas-price:'; export default { ...coinAmount, async prepare(value, params, { onNode }) { if (value != null) return value; if (onNode == null) { throw new ArgumentError('onNode', 'provided (or provide `fee` instead)', onNode); } const gasPrice = await getCachedIncreasedGasPrice(onNode); if (gasPrice === 0n) return undefined; return gasPricePrefix + gasPrice; }, serializeAettos(_value, { rebuildTx, unpackTx, buildTx, _computingMinFee }, { _canIncreaseFee }) { if (_computingMinFee != null) return _computingMinFee.toFixed(); const minFee = calculateMinFee(fee => rebuildTx({ _computingMinFee: fee }), unpackTx, buildTx); const value = _value?.startsWith(gasPricePrefix) === true ? minFee.dividedBy(MIN_GAS_PRICE).times(_value.replace(gasPricePrefix, '')) : new BigNumber(_value !== null && _value !== void 0 ? _value : minFee); if (minFee.gt(value)) { if (_canIncreaseFee === true) return minFee.toFixed(); throw new IllegalArgumentError(`Fee ${value.toString()} must be bigger than ${minFee}`); } return value.toFixed(); }, serialize(value, params, options) { if (typeof value === 'string' && value.startsWith(gasPricePrefix)) { return uInt.serialize(this.serializeAettos(value, params, options)); } return coinAmount.serialize.call(this, value, params, options); } }; //# sourceMappingURL=fee.js.map