@aeternity/aepp-sdk
Version:
SDK for the æternity blockchain
160 lines (155 loc) • 5.32 kB
JavaScript
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