UNPKG

@aeternity/aepp-sdk

Version:

SDK for the æternity blockchain

143 lines (139 loc) 6.14 kB
import { buildTx, buildTxHash, unpackTx } from './builder/index.js'; import { Tag } from './builder/constants.js'; import { verifySignature } from '../utils/crypto.js'; import { getBufferToSign } from '../account/Memory.js'; import { IllegalArgumentError, InternalError, TransactionError } from '../utils/errors.js'; import getTransactionSignerAddress from './transaction-signer.js'; /** * Calculates the cost of transaction execution * Provides an upper cost of contract-call-related transactions because of `gasLimit`. * Also assumes that oracle query fee is 0 unless it is provided in options. * * The idea is that if you need to show transaction details with some accuracy you can define * expense fields that you want to show separately. And to show `getExecutionCost` result as a fee, * subtracting all fields shown separately. * * @example * ```vue * <template> * Amount: {{ txUnpacked.amount }} * Name fee: {{ txUnpacked.nameFee }} * Other fees: {{ getExecutionCost(txEncoded) - txUnpacked.amount - txUnpacked.nameFee }} * </template> * ``` * * Doing this way you won't worry to show wrong fee for a transaction you may not support. Because * the SDK calculates the overall price of any transaction on its side. * * @param transaction - Transaction to calculate the cost of * @param options - Options * @param options.innerTx - Should be provided if transaction wrapped with Tag.PayingForTx * @param options.gasUsed - Amount of gas actually used to make calculation more accurate * @param options.queryFee - Oracle query fee * @param options.isInitiator - Is transaction signer an initiator of state channel * @category utils */ export function getExecutionCost(transaction, { innerTx, gasUsed, queryFee, isInitiator } = {}) { const params = unpackTx(transaction); if (params.tag === Tag.SignedTx) { throw new IllegalArgumentError("Transaction shouldn't be a SignedTx, use `getExecutionCostBySignedTx` instead"); } let res = 0n; if ('fee' in params && innerTx !== 'freeloader') { res += BigInt(params.fee); } if (params.tag === Tag.NameClaimTx) { res += BigInt(params.nameFee); } if (params.tag === Tag.OracleQueryTx) { res += BigInt(params.queryFee); } if (params.tag === Tag.OracleRespondTx) { res -= BigInt(queryFee !== null && queryFee !== void 0 ? queryFee : 0); } if (params.tag === Tag.ChannelSettleTx) { if (isInitiator === true) res -= BigInt(params.initiatorAmountFinal); if (isInitiator === false) res -= BigInt(params.responderAmountFinal); } if ((params.tag === Tag.SpendTx || params.tag === Tag.ContractCreateTx || params.tag === Tag.ContractCallTx || params.tag === Tag.ChannelDepositTx) && innerTx !== 'fee-payer') { res += BigInt(params.amount); } if (params.tag === Tag.ContractCreateTx) res += BigInt(params.deposit); if ((params.tag === Tag.ContractCreateTx || params.tag === Tag.ContractCallTx || params.tag === Tag.GaAttachTx || params.tag === Tag.GaMetaTx) && innerTx !== 'freeloader') { res += BigInt(params.gasPrice) * BigInt(gasUsed !== null && gasUsed !== void 0 ? gasUsed : params.gasLimit); } if (params.tag === Tag.GaMetaTx || params.tag === Tag.PayingForTx) { res += getExecutionCost(buildTx(params.tx.encodedTx), params.tag === Tag.PayingForTx ? { innerTx: 'fee-payer' } : {}); } return res; } /** * Calculates the cost of signed transaction execution * @param transaction - Transaction to calculate the cost of * @param networkId - Network id used to sign the transaction * @param options - Options * @category utils */ export function getExecutionCostBySignedTx(transaction, networkId, options) { const params = unpackTx(transaction, Tag.SignedTx); if (params.encodedTx.tag === Tag.GaMetaTx) { return getExecutionCost(buildTx(params.encodedTx), options); } const tx = buildTx(params.encodedTx); const address = getTransactionSignerAddress(tx); const [isInnerTx, isNotInnerTx] = [true, false].map(f => verifySignature(getBufferToSign(tx, networkId, f), params.signatures[0], address)); if (!isInnerTx && !isNotInnerTx) throw new TransactionError("Can't verify signature"); return getExecutionCost(buildTx(params.encodedTx), { ...(isInnerTx && { innerTx: 'freeloader' }), ...options }); } /** * Calculates the cost of signed and not signed transaction execution using node * @param transaction - Transaction to calculate the cost of * @param node - Node to use * @param options - Options * @param options.isMined - Is transaction already mined or not * @category utils */ export async function getExecutionCostUsingNode(transaction, node, { isMined, ...options } = {}) { let params = unpackTx(transaction); const isSignedTx = params.tag === Tag.SignedTx; const txHash = isSignedTx && isMined === true && buildTxHash(transaction); if (params.tag === Tag.SignedTx) params = params.encodedTx; // TODO: set gasUsed for PayingForTx after solving https://github.com/aeternity/aeternity/issues/4087 if (options.gasUsed == null && txHash !== false && [Tag.ContractCreateTx, Tag.ContractCallTx, Tag.GaAttachTx, Tag.GaMetaTx].includes(params.tag)) { const { callInfo, gaInfo } = await node.getTransactionInfoByHash(txHash); const combinedInfo = callInfo !== null && callInfo !== void 0 ? callInfo : gaInfo; if (combinedInfo == null) { throw new InternalError(`callInfo and gaInfo is not available for transaction ${txHash}`); } options.gasUsed = combinedInfo.gasUsed; } if (options.queryFee == null && Tag.OracleRespondTx === params.tag) { options.queryFee = (await node.getOracleByPubkey(params.oracleId)).queryFee.toString(); } if (options.isInitiator == null && Tag.ChannelSettleTx === params.tag && isMined !== true) { const { initiatorId } = await node.getChannelByPubkey(params.channelId); options.isInitiator = params.fromId === initiatorId; } return isSignedTx ? getExecutionCostBySignedTx(transaction, await node.getNetworkId(), options) : getExecutionCost(transaction, options); } //# sourceMappingURL=execution-cost.js.map