I'm trying to build a transaction using cardano-serialization-lib with Vanilla JS to my dApp wallet

I’ve written the following code:

async function buildAndSignTransaction(api, recipientAddressStr, amount, fixedFee) {
try {
// Convert recipient address from Bech32
const recipientAddress = wasm.Address.from_bech32(recipientAddressStr);

          // Step 1: Get used address
          const usedAddresses = await api.getUsedAddresses();
          const senderAddressHex = usedAddresses.length > 0 
              ? usedAddresses[0] 
              : null;

          if (!senderAddressHex) {
              throw new Error("No sender address found.");
          }

          console.log("Sender Address Hex:", senderAddressHex);

          // Step 2: Create transaction builder
          const linearFee = wasm.LinearFee.new(
              wasm.BigNum.from_str("1000"), // constant fee
              wasm.BigNum.from_str("100"),    // fee per byte
          );

          // Initialize required parameters for TransactionBuilder
          const minUtxoValue = wasm.BigNum.from_str("1000000"); // minimum UTXO value
          const poolDeposit = wasm.BigNum.from_str("500000"); // pool deposit
          const keyDeposit = wasm.BigNum.from_str("2000000"); // key deposit
          const maxValueSize = 1000000; // maximum value size
          const maxTxSize = 1000000; // maximum transaction size

          const txBuilder = wasm.TransactionBuilder.new(
              linearFee,
              minUtxoValue,
              poolDeposit,
              keyDeposit,
              maxValueSize,
              maxTxSize
          );

          // Step 3: Get UTXOs
          const utxos = await api.getUtxos();
          if (utxos.length === 0) {
              throw new Error("No UTXOs found for the sender.");
          }

          // Use the first UTXO for simplicity
          const utxoHex = utxos[0]; 
          const utxo = wasm.TransactionUnspentOutput.from_bytes(hexToBytes(utxoHex));
          const txInput = utxo.input();
          
          // Extract the value from the UTXO
          const outputValue = utxo.output().amount();

          console.log('Before Add Input');
          console.log('UTXO:', utxo);
          console.log('txInput:', txInput);

          // Add input to the transaction builder
          txBuilder.add_input(
              wasm.Address.from_bytes(hexToBytes(senderAddressHex)), 
              txInput,
              outputValue // Pass the UTXO's value directly
          );

          console.log('After Add Input');

          // Step 4: Add output
          try {
              const lovelace = wasm.BigNum.from_str(amount.toString());
              if (lovelace.to_str() === "0") {
                  throw new Error("The output amount must be greater than zero.");
              }

              // Calculate adjusted value manually
              const adjustedValueStr = (parseInt(amount) - fixedFee).toString();
              if (parseInt(adjustedValueStr) < 0) {
                  throw new Error("Insufficient funds to cover transaction fee.");
              }

              const adjustedValue = wasm.BigNum.from_str(adjustedValueStr);
              const outputAddress = wasm.Address.from_bytes(recipientAddress.to_bytes());
              console.log("Output Address (Bech32):", recipientAddress.to_bech32());
              console.log("Output Value (Lovelace):", adjustedValue.to_str());

              txBuilder.add_output(wasm.TransactionOutput.new(outputAddress, wasm.Value.new(adjustedValue)));
          } catch (error) {
              console.error("Error adding output:", error);
              throw error; // Rethrow to be caught in the outer try-catch
          }

          console.log('After Add Output');
          console.log(txBuilder);

          // Step 5: Build the transaction
          const transaction = txBuilder.build();

          // Step 6: Sign the transaction
          const signedTx = await api.signTx(transaction.to_bytes());

          // Step 7: Submit the signed transaction
          const txHash = await api.submitTx(signedTx);
          console.log("Transaction submitted. TX Hash:", txHash);
      } catch (error) {
          console.error("Error building and signing transaction:", error);
      }
    }

I’m getting the error: “Error building and signing transaction: Fee not specified”

Please help me how to fix this error. All I want is to build a trasnsaction and submit so that the user can sign the transaction using dApp wallet.

1 Like

async function buildAndSignTransaction(api, recipientAddressStr, amount) {
try {
// Convert recipient address from Bech32
const recipientAddress = wasm.Address.from_bech32(recipientAddressStr);

    // Step 1: Get used address (sender)
    const usedAddresses = await api.getUsedAddresses();
    const senderAddressHex = usedAddresses.length > 0 ? usedAddresses[0] : null;

    if (!senderAddressHex) {
        throw new Error("No sender address found.");
    }

    console.log("Sender Address Hex:", senderAddressHex);

    // Step 2: Create transaction builder
    const linearFee = wasm.LinearFee.new(
        wasm.BigNum.from_str("44"), // constant fee
        wasm.BigNum.from_str("155381")    // fee per byte
    );

    const minUtxoValue = wasm.BigNum.from_str("34482"); // minimum UTXO value of 1 ADA
    const poolDeposit = wasm.BigNum.from_str("500000000");
    const keyDeposit = wasm.BigNum.from_str("2000000");
    const maxValueSize = 5000;
    const maxTxSize = 16384;

    const txBuilder = wasm.TransactionBuilder.new(
        linearFee,
        minUtxoValue,
        poolDeposit,
        keyDeposit,
        maxValueSize,
        maxTxSize
    );

    // Step 3: Get UTXOs and log them
    const utxos = await api.getUtxos();
    console.log("UTXOs: ", utxos);

    if (utxos.length === 0) {
        throw new Error("No UTXOs found for the sender.");
    }

    let totalInputValue = wasm.BigNum.from_str("0");
    let requiredAmount = wasm.BigNum.from_str(amount.toString());

    // Step 4: Select UTXOs
    for (const utxoHex of utxos) {
        const utxo = wasm.TransactionUnspentOutput.from_bytes(hexToBytes(utxoHex));
        const txInput = utxo.input();
        const outputValue = utxo.output().amount();

        console.log("Adding UTXO:", utxo);
        console.log("UTXO Value (Lovelace):", outputValue.coin().to_str());

        // Ensure UTXO is valid and meets minimum value
        if (outputValue.coin().compare(minUtxoValue) < 0) {
            console.log("Skipping UTXO below minimum UTXO value:", outputValue.coin().to_str());
            continue; // Skip UTXOs that do not meet minimum value
        }

        txBuilder.add_input(
            wasm.Address.from_bytes(hexToBytes(senderAddressHex)),
            txInput,
            outputValue
        );

        totalInputValue = totalInputValue.checked_add(outputValue.coin());
    }

    console.log("Total Input Value:", totalInputValue.to_str());
    console.log("Required Amount (Lovelace):", requiredAmount.to_str());

    // Step 5: Check if total input is enough
    const minFee = txBuilder.min_fee();
    console.log("Minimum Fee:", minFee.to_str());

    if (totalInputValue.compare(requiredAmount.checked_add(minFee)) < 0) {
        throw new Error('Not enough UTXO balance to cover the amount and fees.');
    }

    // Step 6: Add output for recipient
    const recipientAmount = wasm.BigNum.from_str(amount.toString());
    const outputAddress = wasm.Address.from_bytes(recipientAddress.to_bytes());

    console.log("Recipient Amount (Lovelace):", recipientAmount.to_str());
    console.log("Minimum UTXO Value (Lovelace):", minUtxoValue.to_str());

    if (recipientAmount.compare(minUtxoValue) < 0) {
        throw new Error(`Output amount ${recipientAmount.to_str()} is less than the minimum UTXO value ${minUtxoValue.to_str()}.`);
    }

    txBuilder.add_output(wasm.TransactionOutput.new(outputAddress, wasm.Value.new(recipientAmount)));
    console.log("Added Output - Recipient Address:", recipientAddress.to_bech32(), "Amount:", recipientAmount.to_str());

    // Step 7: Set the change (if any)
    const totalOutputValue = recipientAmount.checked_add(minFee);
    const changeAmount = totalInputValue.checked_sub(totalOutputValue);

    if (changeAmount.compare(wasm.BigNum.from_str("0")) > 0) {
        txBuilder.add_output(wasm.TransactionOutput.new(
            wasm.Address.from_bytes(hexToBytes(senderAddressHex)),
            wasm.Value.new(changeAmount)
        ));
        const senderAddress = wasm.Address.from_bytes(hexToBytes(senderAddressHex)).to_bech32().toString();
        console.log("Added Change - Address:", senderAddress, "Change Amount:", changeAmount.to_str());
    }

    // Step 8: Set fee
    txBuilder.set_fee(minFee);

    // Step 9: Build transaction
    const txBody = txBuilder.build();
    console.log("Transaction body built:", txBody);

    // Log the outputs manually
    const txOutputs = txBody.outputs();
    for (let i = 0; i < txOutputs.len(); i++) {
        const output = txOutputs.get(i);
        console.log(`Output ${i + 1}: Address: ${output.address().to_bech32()}, Amount: ${output.amount().coin().to_str()}`);
    }

    // Step 10: Build the full transaction with witness set
    const witnesses = wasm.TransactionWitnessSet.new();
    const transaction = wasm.Transaction.new(txBody, witnesses);
    console.log("Transaction with witnesses built:", transaction);

    // Step 11: Sign the transaction
    const signedTx = await api.signTx(transaction.to_bytes());
    console.log("Signed Transaction:", signedTx);

    // Step 12: Submit the signed transaction
    const txHash = await api.submitTx(signedTx);
    console.log("Transaction submitted. TX Hash:", txHash);
} catch (error) {
    console.error("Error building and signing transaction:", error);
}

}

I’m getting following error:

Inputs do not conform to this spec or are otherwise invalid.