Hi, I'm getting the min fee for a tx that will mint a token but sometimes the functions return a fee too small. This is the function:
/**
* @param {Transaction} tx
* @param {LinearFee} linear_fee
* @returns {BigNum}
*/
declare export function min_fee(tx: Transaction, linear_fee: LinearFee): BigNum;
My code takes a structure coming from the cardano-wallet (coinSelection
) and builds a tx using TxBuilder
, then I set the mint object using set_mint
, then sign it and get the fee with min_fee
.
To get the min fee I call set_mint
this way:
min_fee(tx, LinearFee.new(BigNum.from_str("44"), BigNum.from_str("155381")));
This is how I'm constructing the TransactionBody from the coinSelection:
buildTransaction(coinSelection: CoinSelectionWallet, ttl: number, data?: TransactionMetadata, startSlot = 0, config = Config.Mainnet): TransactionBody {
const protocolParams = config.protocolParams;
let txBuilder = TransactionBuilder.new(
// all of these are taken from the mainnet genesis settings
// linear fee parameters (a*size + b)
LinearFee.new(BigNum.from_str("44"), BigNum.from_str("155381")),
// minimum utxo value
BigNum.from_str("1000000"),
// pool deposit
BigNum.from_str("500000000"),
// key deposit
BigNum.from_str("2000000")
);
// add tx inputs
coinSelection.inputs.forEach((input, i) => {
let address = Address.from_bech32(input.address);
let txInput = TransactionInput.new(
TransactionHash.from_bytes(Buffer.from(input.id, 'hex')),
input.index
);
let amount = Value.new(
BigNum.from_str(input.amount.quantity.toString())
);
txBuilder.add_input(address, txInput, amount);
});
// add tx outputs
coinSelection.outputs.forEach(output => {
let address = Address.from_bech32(output.address);
let amount = Value.new(
BigNum.from_str(output.amount.quantity.toString())
);
// add tx assets
if(output.assets && output.assets.length > 0){
let multiAsset = Seed.buildMultiAssets(output.assets);
amount.set_multiasset(multiAsset);
}
let txOutput = TransactionOutput.new(
address,
amount
);
txBuilder.add_output(txOutput);
});
// add tx change
coinSelection.change.forEach(change => {
let address = Address.from_bech32(change.address);
let amount = Value.new(
BigNum.from_str(change.amount.quantity.toString())
);
// add tx assets
if(change.assets && change.assets.length > 0){
let multiAsset = Seed.buildMultiAssets(change.assets);
amount.set_multiasset(multiAsset);
}
let txOutput = TransactionOutput.new(
address,
amount
);
txBuilder.add_output(txOutput);
});
// add tx metadata
if (data) {
txBuilder.set_metadata(data);
}
// set tx validity start interval
txBuilder.set_validity_start_interval(startSlot);
// set tx ttl
txBuilder.set_ttl(ttl);
// calculate fee
let fee = coinSelection.inputs.reduce((acc, c) => c.amount.quantity + acc, 0)
- coinSelection.outputs.reduce((acc, c) => c.amount.quantity + acc, 0)
- coinSelection.change.reduce((acc, c) => c.amount.quantity + acc, 0);
// set tx fee
txBuilder.set_fee(BigNum.from_str(fee.toString()));
let txBody = txBuilder.build();
return txBody;
}
This is Seed.buildMultiAssets()
:
static buildMultiAssets(assets: WalletsAssetsAvailable[]): MultiAsset {
let multiAsset = MultiAsset.new();
assets.forEach(a => {
let asset = Assets.new();
let scriptHash = Seed.getScriptHashFromPolicy(a.policy_id);
asset.insert(AssetName.new(Buffer.from(a.asset_name, 'hex')), BigNum.from_str(a.quantity.toString()));
multiAsset.insert(scriptHash, asset);
});
return multiAsset;
}
Here the code to create the Mint
object:
static buildTransactionMint(tokens: TokenWallet[]): Mint {
let mint = Mint.new();
tokens.forEach(t => {
let mintAssets = MintAssets.new();
let scriptHash = Seed.getScriptHashFromPolicy(t.asset.policy_id);
mintAssets.insert(AssetName.new(Buffer.from(t.asset.asset_name)), Int.new_i32(t.asset.quantity));
mint.insert(scriptHash, mintAssets);
});
return mint;
}
After the TransactionBody
is built I set the mint object this way:
let txBody = buildTransaction(coinSelection, ttl, metadata); // metadata is of type `TransactionMetadata`
txBody.set_mint(mint);
After that I sign the tx with txBody
the signingKeys
, metadata
and the scripts // are of type NativeScripts[]
.
To finally get the min fee using min_fee
.
The thing is I'm not sure when specifically the issue shows up (it works sometimes) but there are some cases that could be a starting point. For example, the min_fee is wrong when I have 3 assets in one UTXO plus metadata like this:
{
inputs: [
{
amount: {
quantity: 1444443,
unit: "lovelace",
},
address: "addr1qyuxc75xmzzy7sy955pyz4tqg0ycgttjcv2u39ay929q2yp59kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqya2xl3",
id: "ef4911bd3ac3f45ad69a21b933a5c7cb9470cb77661f41f50e07476eb87c1b2a",
derivation_path: [
"1852H",
"1815H",
"0H",
"0",
"0",
],
assets: [
{
asset_name: "54616e676f32",
quantity: 1000000,
policy_id: "a716bcf749e3afc27674da23c15fb24a63435f837760dcc823bca995",
},
],
index: 0,
},
{
amount: {
quantity: 1444443,
unit: "lovelace",
},
address: "addr1qyuxc75xmzzy7sy955pyz4tqg0ycgttjcv2u39ay929q2yp59kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqya2xl3",
id: "b9f927b44c0905697c1767d77f6ceb798091d6a854c3f04543e23c688277ff01",
derivation_path: [
"1852H",
"1815H",
"0H",
"0",
"0",
],
assets: [
{
asset_name: "54616e676f35",
quantity: 1000000,
policy_id: "19933db2e7c74625ab0a4174bab9a27170f97555634082c2f16d3bea",
},
],
index: 0,
},
{
amount: {
quantity: 1444443,
unit: "lovelace",
},
address: "addr1qyuxc75xmzzy7sy955pyz4tqg0ycgttjcv2u39ay929q2yp59kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqya2xl3",
id: "4b5efe05d035d510bac0fa9dd84db70912c13a95efd97e2fdd9afb362438dc0a",
derivation_path: [
"1852H",
"1815H",
"0H",
"0",
"0",
],
assets: [
{
asset_name: "54616e676f33",
quantity: 1000000,
policy_id: "fcd84d1acc1ad2780f0a231779ea5d9da20826c3c13c7a3dda6efa5a",
},
],
index: 0,
},
],
outputs: [
{
amount: {
quantity: 1444443,
unit: "lovelace",
},
address: "addr1qyuxc75xmzzy7sy955pyz4tqg0ycgttjcv2u39ay929q2yp59kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqya2xl3",
assets: [
{
policy_id: "7e634c9d8edfd106326767cf58fdf6666a2db4f3179cadb2b9054122",
asset_name: "54616e676f",
quantity: 1000000,
},
],
},
],
change: [
{
amount: {
quantity: 2696486,
unit: "lovelace",
},
address: "addr1qxqlyjyrqcgar3xx8ayyhxphkmnqnt2w524hwg8y555vtkf59kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqhxnp7w",
derivation_path: [
"1852H",
"1815H",
"0H",
"1",
"7",
],
assets: [
{
asset_name: "54616e676f35",
quantity: 1000000,
policy_id: "19933db2e7c74625ab0a4174bab9a27170f97555634082c2f16d3bea",
},
{
asset_name: "54616e676f32",
quantity: 1000000,
policy_id: "a716bcf749e3afc27674da23c15fb24a63435f837760dcc823bca995",
},
{
asset_name: "54616e676f33",
quantity: 1000000,
policy_id: "fcd84d1acc1ad2780f0a231779ea5d9da20826c3c13c7a3dda6efa5a",
},
],
},
],
withdrawals: [
],
certificates: undefined,
deposits: [
],
metadata: "oQCheDg3ZTYzNGM5ZDhlZGZkMTA2MzI2NzY3Y2Y1OGZkZjY2NjZhMmRiNGYzMTc5Y2FkYjJiOTA1NDEyMqFlVGFuZ2+laWFyd2VhdmVJZGphcndlYXZlLWlkZmlwZnNJZGdpcGZzLWlkZG5hbWVlVGFuZ29rZGVzY3JpcHRpb25xVGFuZ28gY3J5cHRvIGNvaW5kdHlwZWRDb2lu",
}
In this case, the error is this (min_fee
returns 204177):
HardForkApplyTxErrFromEra S (S (S (Z (WrapApplyTxErr {unwrapApplyTxErr = ApplyTxError [LedgerFailure (UtxowFailure (UtxoFailure (FeeTooSmallUTxO (Coin 210900) (Coin 204177))))]})))).
If no metadata is present the tx for 3 assets in one UTXO just works but when the assets are 5 the error happens again:
{
inputs: [
{
amount: {
quantity: 2692805,
unit: "lovelace",
},
address: "addr1qxuq6szwm8gdmxzvv7c0r2sgkyva69tmh2h8dseyg8uzdv359kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqvjjcev",
id: "ffc81613c24bb158858fdf4008934a6917b2772206603f9cb617432a1719f446",
derivation_path: [
"1852H",
"1815H",
"0H",
"1",
"9",
],
assets: [
{
asset_name: "54616e676f32",
quantity: 1000000,
policy_id: "9a8565c9bd0fcf3add4fdd08956e40e1607a762b92f17dfaab2dfec5",
},
{
asset_name: "54616e676f34",
quantity: 1000000,
policy_id: "a1c81493ce562eba9a5c6c6ed3e6a0514d9d1196afd5677d43c655e5",
},
{
asset_name: "54616e676f33",
quantity: 1000000,
policy_id: "fcd84d1acc1ad2780f0a231779ea5d9da20826c3c13c7a3dda6efa5a",
},
],
index: 1,
},
{
amount: {
quantity: 1444443,
unit: "lovelace",
},
address: "addr1qyuxc75xmzzy7sy955pyz4tqg0ycgttjcv2u39ay929q2yp59kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqya2xl3",
id: "ffc81613c24bb158858fdf4008934a6917b2772206603f9cb617432a1719f446",
derivation_path: [
"1852H",
"1815H",
"0H",
"0",
"0",
],
assets: [
{
asset_name: "54616e676f33",
quantity: 1000000,
policy_id: "472b254819d86a3393726eb118b1ba860dd04fa630810b38c2e553fc",
},
],
index: 0,
},
{
amount: {
quantity: 1444443,
unit: "lovelace",
},
address: "addr1qyuxc75xmzzy7sy955pyz4tqg0ycgttjcv2u39ay929q2yp59kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqya2xl3",
id: "b9f927b44c0905697c1767d77f6ceb798091d6a854c3f04543e23c688277ff01",
derivation_path: [
"1852H",
"1815H",
"0H",
"0",
"0",
],
assets: [
{
asset_name: "54616e676f35",
quantity: 1000000,
policy_id: "19933db2e7c74625ab0a4174bab9a27170f97555634082c2f16d3bea",
},
],
index: 0,
},
],
outputs: [
{
amount: {
quantity: 1444443,
unit: "lovelace",
},
address: "addr1qyuxc75xmzzy7sy955pyz4tqg0ycgttjcv2u39ay929q2yp59kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqya2xl3",
assets: [
{
policy_id: "001ff1c3d7cc7083d2d569748ad941910246a58e460287f708f3c137",
asset_name: "54616e676f34",
quantity: 1000000,
},
],
},
],
change: [
{
amount: {
quantity: 3937119,
unit: "lovelace",
},
address: "addr1q87p0ml3mj8xaxjrmcr7dgulkg7ylt0cgkh5l0l0twr2p6359kkfl90wf7f9vlm99fek6e9l5zh65td8jhw63hn9skqqu5wdxs",
derivation_path: [
"1852H",
"1815H",
"0H",
"1",
"10",
],
assets: [
{
asset_name: "54616e676f35",
quantity: 1000000,
policy_id: "19933db2e7c74625ab0a4174bab9a27170f97555634082c2f16d3bea",
},
{
asset_name: "54616e676f33",
quantity: 1000000,
policy_id: "472b254819d86a3393726eb118b1ba860dd04fa630810b38c2e553fc",
},
{
asset_name: "54616e676f32",
quantity: 1000000,
policy_id: "9a8565c9bd0fcf3add4fdd08956e40e1607a762b92f17dfaab2dfec5",
},
{
asset_name: "54616e676f34",
quantity: 1000000,
policy_id: "a1c81493ce562eba9a5c6c6ed3e6a0514d9d1196afd5677d43c655e5",
},
{
asset_name: "54616e676f33",
quantity: 1000000,
policy_id: "fcd84d1acc1ad2780f0a231779ea5d9da20826c3c13c7a3dda6efa5a",
},
],
},
],
withdrawals: [
],
certificates: undefined,
deposits: [
],
metadata: undefined,
}
This is the error (min_fee
returns 200129):
HardForkApplyTxErrFromEra S (S (S (Z (WrapApplyTxErr {unwrapApplyTxErr = ApplyTxError [LedgerFailure (UtxowFailure (UtxoFailure (FeeTooSmallUTxO (Coin 201700) (Coin 200129))))]}))))
I also test it with 4 assets in one UTXO and it works. Maybe I'm doing something intrinsically wrong but if I'm not I hope these examples help you find the issue.