@ledgerhq/coin-tezos
Version:
282 lines • 14.5 kB
JavaScript
"use strict";
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.estimateFees = estimateFees;
const logs_1 = require("@ledgerhq/logs");
const taquito_1 = require("@taquito/taquito");
const utils_1 = require("@taquito/utils");
const errors_1 = require("../types/errors");
const utils_2 = require("../utils");
const tezosToolkit_1 = require("./tezosToolkit");
/**
* Fetch the transaction fees for a transaction
*
* @param {Account} account
* @param {Transaction} transaction
*/
async function estimateFees(context, { account, transaction, }) {
const config = await context.config();
// Normalize public key (hex -> base58) when provided (may be undefined for unrevealed accounts)
// before the device is connected
const encodedPubKey = account.xpub
? (0, utils_2.normalizePublicKeyForAddress)(account.xpub, account.address)
: undefined;
const tezosToolkit = (0, tezosToolkit_1.getTezosToolkit)(config);
if (encodedPubKey && (0, utils_1.validatePublicKey)(encodedPubKey) === utils_1.ValidationResult.VALID) {
tezosToolkit.setProvider({ signer: (0, utils_2.createMockSigner)(account.address, encodedPubKey) });
}
else {
tezosToolkit.setProvider({ signer: (0, utils_2.createMockSigner)(account.address, '') });
}
const estimation = {
fees: 0n,
gasLimit: 0n,
storageLimit: 0n,
estimatedFees: 0n,
};
// For legacy compatibility
if (account.balance === 0n) {
return transaction.useAllAmount ? { ...estimation, amount: 0n } : estimation;
}
const spendableForMax = Number((0, utils_2.partitionNativeBalance)(account.balance, account.stakedBalance ?? 0n, account.unstakedBalance ?? 0n).spendable);
let amount = transaction.amount;
const coerceMinAmountForEstimation = (transaction.useAllAmount &&
(transaction.mode === 'send' ||
transaction.mode === 'stake' ||
transaction.mode === 'unstake')) ||
(amount === 0n && transaction.mode !== 'send_token');
if (coerceMinAmountForEstimation) {
amount = 1n; // send/stake max or zero-amount pre-estimation (taquito refuses 0); not used for FA2 send_token
}
try {
if ((transaction.mode === 'send' || transaction.mode === 'send_token') &&
!transaction.recipient) {
return {
...estimation,
...(0, utils_2.createFallbackEstimation)(config),
};
}
let estimate;
switch (transaction.mode) {
case 'send':
estimate = await tezosToolkit.estimate.transfer({
mutez: true,
to: transaction.recipient,
amount: Number(amount),
source: account.address, // avoid requiring signer for estimation
storageLimit: taquito_1.ORIGINATION_SIZE, // https://github.com/TezTech/eztz/blob/master/PROTO_003_FEES.md for originating an account
});
break;
case 'delegate':
estimate = await tezosToolkit.estimate.setDelegate({
source: account.address,
delegate: transaction.recipient,
});
break;
case 'undelegate':
estimate = await tezosToolkit.estimate.setDelegate({
source: account.address,
});
break;
case 'stake':
estimate = await tezosToolkit.estimate.stake({
amount: Number(amount),
mutez: true,
});
break;
case 'unstake':
estimate = await tezosToolkit.estimate.unstake({
amount: Number(amount),
mutez: true,
});
break;
case 'finalize_unstake':
estimate = await tezosToolkit.estimate.finalizeUnstake({});
break;
case 'send_token': {
if (!transaction.contractAddress || transaction.tokenId === undefined) {
throw new Error('FA2 transfer requires contractAddress and tokenId');
}
const tokenContract = await tezosToolkit.contract.at(transaction.contractAddress);
const transferParams = tokenContract.methods
.transfer([
{
from_: account.address,
txs: [
{
to_: transaction.recipient,
token_id: transaction.tokenId,
amount: amount,
},
],
},
])
.toTransferParams({ mutez: true });
estimate = await tezosToolkit.estimate.transfer({
...transferParams,
source: account.address,
});
break;
}
default:
throw new errors_1.UnsupportedTransactionMode('unsupported mode', { mode: transaction.mode });
}
const minFees = config.fees.minFees ?? 0;
const mainOpFee = Math.max(minFees, estimate.suggestedFeeMutez);
const revealFee = account.revealed
? 0n
: BigInt(getRevealFeeForEstimation(config, account.address));
// NOTE: send-max only applies to native XTZ transfer, not FA2
if (transaction.useAllAmount && transaction.mode === 'send') {
// Reserve `mainOpFee`, not the raw taquito suggestion: `estimatedFees` below reports the
// `minFees` floor, so subtracting the (lower) suggestion here yields an amount whose
// `amount + estimatedFees` exceeds the spendable balance, and validateIntent then rejects
// the very max it was handed (LIVE-28506).
// NOTE: from https://github.com/ecadlabs/taquito/blob/master/integration-tests/__tests__/contract/empty-implicit-account-into-new-implicit-account.spec.ts#L37
const totalFees = estimate.burnFeeMutez > 0
? mainOpFee + estimate.burnFeeMutez - 20 * taquito_1.COST_PER_BYTE // 20 is storage buffer
: mainOpFee;
const maxAmount = spendableForMax - (totalFees + Number(revealFee));
// NOTE: from https://github.com/ecadlabs/taquito/blob/a70c64c4b105381bb9f1d04c9c70e8ef26e9241c/integration-tests/contract-empty-implicit-account-into-new-implicit-account.spec.ts#L33
// Temporary fix, see https://gitlab.com/tezos/tezos/-/issues/1754
// we need to increase the gasLimit and fee returned by the estimation
const MINIMAL_FEE_PER_GAS_MUTEZ = 0.1;
const incr = utils_2.DUST_MARGIN_MUTEZ * MINIMAL_FEE_PER_GAS_MUTEZ + Number(estimate.opSize);
const maxMinusBuff = maxAmount - (utils_2.DUST_MARGIN_MUTEZ - incr);
estimation.amount = maxMinusBuff > 0 ? BigInt(maxMinusBuff) : 0n;
(0, logs_1.log)('tezos-send-max', 'send-max fee inputs', {
minFees,
mainOpFee,
suggestedFeeMutez: estimate.suggestedFeeMutez,
burnFeeMutez: estimate.burnFeeMutez,
opSize: Number(estimate.opSize),
revealFee: Number(revealFee),
});
}
else if (transaction.useAllAmount && transaction.mode === 'stake') {
estimation.amount = (0, utils_2.computeMaxStakeAmount)(BigInt(account.balance), account.stakedBalance ?? 0n, account.unstakedBalance ?? 0n, BigInt(mainOpFee) + revealFee);
}
else if (transaction.useAllAmount && transaction.mode === 'unstake') {
// unstake-max draws from the staked balance, not the spendable balance
estimation.amount = account.stakedBalance ?? 0n;
}
else {
estimation.amount = transaction.amount;
}
estimation.fees = BigInt(mainOpFee);
estimation.gasLimit = BigInt(estimate.gasLimit);
estimation.storageLimit = BigInt(estimate.storageLimit);
estimation.estimatedFees = estimation.fees + revealFee;
}
catch (e) {
if (typeof e !== 'object' || !e)
throw e;
if ('id' in e) {
estimation.taquitoError = e.id;
(0, logs_1.log)('taquito-error', 'taquito got error ' + e.id);
}
else if ('status' in e) {
const errorMessage = String(e.message || '');
if (errorMessage.includes('Public key not found') ||
errorMessage.includes('wallet or contract API')) {
(0, logs_1.log)('taquito-network-error', 'Recipient address not found (new account), using default fees', {
transaction: transaction,
});
const fallback = (0, utils_2.createFallbackEstimation)(config);
estimation.fees = fallback.fees;
estimation.gasLimit = fallback.gasLimit;
estimation.storageLimit = fallback.storageLimit;
estimation.estimatedFees = fallback.fees;
if (!account.revealed) {
estimation.estimatedFees =
estimation.estimatedFees + BigInt(getRevealFeeForEstimation(config, account.address));
}
// Handle useAllAmount also for send mode when estimation falls back
if (transaction.useAllAmount && transaction.mode === 'send') {
// Approximate Taquito behavior for send-max using stable constants
const suggestedFee = transaction.mode === 'send' ? utils_2.MIN_SUGGESTED_FEE_SMALL_TRANSFER : Number(estimation.fees);
// For display consistency in tests, align fees to suggestedFee in send-max
if (transaction.mode === 'send') {
estimation.fees = BigInt(suggestedFee);
estimation.estimatedFees = BigInt(suggestedFee);
if (!account.revealed) {
estimation.estimatedFees =
estimation.estimatedFees +
BigInt(getRevealFeeForEstimation(config, account.address));
}
}
const burnFeeMutez = Number(estimation.storageLimit) * taquito_1.COST_PER_BYTE;
const totalFees = suggestedFee + (burnFeeMutez > 0 ? burnFeeMutez - 20 * taquito_1.COST_PER_BYTE : 0);
const revealFee = account.revealed
? 0
: getRevealFeeForEstimation(config, account.address);
const maxAmount = spendableForMax - (totalFees + revealFee);
const MINIMAL_FEE_PER_GAS_MUTEZ = 0.1;
const incr = utils_2.OP_SIZE_XTZ_TRANSFER + utils_2.DUST_MARGIN_MUTEZ * MINIMAL_FEE_PER_GAS_MUTEZ;
const maxMinusBuff = maxAmount - (utils_2.DUST_MARGIN_MUTEZ - incr);
estimation.amount = maxMinusBuff > 0 ? BigInt(Math.floor(maxMinusBuff)) : 0n;
}
else {
// preserve input amount in fallback for readability/tests
estimation.amount = transaction.amount;
}
}
else {
(0, logs_1.log)('taquito-network-error', errorMessage, {
transaction: transaction,
});
throw e;
}
}
else {
const msg = String(e.message || '');
if (msg.includes('No signer has been configured')) {
const fallback = (0, utils_2.createFallbackEstimation)(config);
estimation.fees = fallback.fees;
estimation.gasLimit = fallback.gasLimit;
estimation.storageLimit = fallback.storageLimit;
estimation.estimatedFees = fallback.estimatedFees;
if (!account.revealed) {
estimation.estimatedFees =
estimation.estimatedFees + BigInt(getRevealFeeForEstimation(config, account.address));
}
if (transaction.useAllAmount && transaction.mode === 'send') {
const suggestedFee = transaction.mode === 'send' ? utils_2.MIN_SUGGESTED_FEE_SMALL_TRANSFER : Number(estimation.fees);
if (transaction.mode === 'send') {
estimation.fees = BigInt(suggestedFee);
estimation.estimatedFees = BigInt(suggestedFee);
if (!account.revealed) {
estimation.estimatedFees =
estimation.estimatedFees +
BigInt(getRevealFeeForEstimation(config, account.address));
}
}
const burnFeeMutez = Number(estimation.storageLimit) * taquito_1.COST_PER_BYTE;
const totalFees = suggestedFee + (burnFeeMutez > 0 ? burnFeeMutez - 20 * taquito_1.COST_PER_BYTE : 0);
const revealFee = account.revealed
? 0
: getRevealFeeForEstimation(config, account.address);
const maxAmount = spendableForMax - (totalFees + revealFee);
const MINIMAL_FEE_PER_GAS_MUTEZ = 0.1;
const incr = utils_2.OP_SIZE_XTZ_TRANSFER + utils_2.DUST_MARGIN_MUTEZ * MINIMAL_FEE_PER_GAS_MUTEZ;
const maxMinusBuff = maxAmount - (utils_2.DUST_MARGIN_MUTEZ - incr);
estimation.amount = maxMinusBuff > 0 ? BigInt(Math.floor(maxMinusBuff)) : 0n;
}
else {
// preserve input amount in fallback for readability/tests
estimation.amount = transaction.amount;
}
}
else {
throw e;
}
}
}
return estimation;
}
function getRevealFeeForEstimation(config, address) {
const minFees = config.fees.minFees ?? 0;
return Math.max(minFees, (0, taquito_1.getRevealFee)(address));
}
//# sourceMappingURL=estimateFees.js.map