I have the same issue and I write a comment in the cardano-serialization-lib repo. I hope it helps:
Reference: https://github.com/Emurgo/cardano-serialization-lib/issues/443#issuecomment-1195619916
There are two problems; first, the extra data isn't taken into account in the fee calculation and the second one is that you can't calculate automatically the script data hash with the tx builder if you are not calling a script redeemer.
For the fee calculation, I create a dummy WitnessSet and put the datums inside. When the witness set is ready, I use it to calculate the final length and I add this "extra fee" to the transaction builder configuration.
const plutusList = CSL.PlutusList.new()
selection.outputs.forEach(output => {
if (output.plutusData) {
plutusList.add(output.plutusData)
}
})
const witness = CSL.TransactionWitnessSet.new()
witness.set_vkeys(CSL.Vkeywitnesses.new())
witness.set_plutus_data(plutusList)
const txBuilder = CSL.TransactionBuilder.new(CSL.TransactionBuilderConfigBuilder.new()
.coins_per_utxo_word(CSL.BigNum.from_str(protocolParameters.coinsPerUtxoWord.toString()))
.fee_algo(CSL.LinearFee.new(CSL.BigNum.from_str(protocolParameters.minFeeCoefficient.toString()),
CSL.BigNum.from_str((protocolParameters.minFeeConstant + protocolParameters.minFeeCoefficient * witness.to_bytes().length).toString())))
.key_deposit(CSL.BigNum.from_str(protocolParameters.stakeKeyDeposit.toString()))
.max_tx_size(protocolParameters.maxTxSize)
.max_value_size(protocolParameters.maxValueSize)
.pool_deposit(CSL.BigNum.from_str(protocolParameters.poolDeposit.toString()))
.build())
In the fee_algo method, I multiply wintess.to_bytes().length by minFeeCoefficient to add the extra fee necessary. The good part of this solution is that you can continue using the transaction builder as before.
The final step is to add the script data hash at the end just before calling buildTx(). The challenge is that you can't use the method calc_script_data_hash, so you have to calculate and set it. In my case, I use this lines:
if (plutusList.len() > 0 ) {
const scriptDataHash = CSL.hash_script_data(CSL.Redeemers.new(), CSL.TxBuilderConstants.plutus_vasil_cost_models(), plutusList)
txBuilder.set_script_data_hash(scriptDataHash)
}
txBuilder.add_change_if_needed(CSL.Address.from_bech32(address))
Remember to set the script data hash before calling add_change_if_needed, otherwise the hash will not be included in the fee.