0

I am trying to implement the Unlocking of NFT from an "always true" smart contract. I am using the Cardano serialization library for this. I can lock the NFT successfully, but when i unlock the NFT then i get the issue of Fee imbalancing issue i think. Maybe i am missing something and stuck on this. I am facing this error ""transaction submit error ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (UtxoFailure (FromAlonzoUtxoFail (ValueNotConservedUTxO (Value 1297310 (fromList [(PolicyID {policyID = ScriptHash \"80786e0ffb709cf2da414f0496be530a129c23f18c7a493abbf8589c\"},fromList [(61727445796573,1)])])) (Value 2050770 (fromList [(PolicyID {policyID = ScriptHash \"80786e0ffb709cf2da414f0496be530a129c23f18c7a493abbf8589c\"},fromList [(61727445796573,1)])])))))])""

Given below is my code for unlocking NFT.

    assetName = undefined,
    assetPolicyId = undefined,
    storeWalletSelected = undefined,
    transactionHashLocked = undefined
  ) {
    
    const txBuilder = await this.initTransactionBuilder();
    const ScriptAddress = this.sLib.Address.from_bech32(this.addressScriptBech32);
    const walletAddress = await this.getAddress()
    const shelleyChangeAddress = this.sLib.Address.from_bech32(walletAddress)
    const protocolParams = this.getProtocolParameters();

    let multiAsset = this.sLib.MultiAsset.new();
    let assets = this.sLib.Assets.new()
    assets.insert(
      this.sLib.AssetName.new(Buffer.from(assetName, "hex")), // Asset Name
      this.sLib.BigNum.from_str(this.assetAmountToSend.toString()) // How much to send
    );

    multiAsset.insert(
      this.sLib.ScriptHash.from_bytes(Buffer.from(assetPolicyId, "hex")), // PolicyID
      assets
    );

    txBuilder.add_input(
      ScriptAddress,
      this.sLib.TransactionInput.new(
        this.sLib.TransactionHash.from_bytes(Buffer.from(transactionHashLocked, "hex")),
        this.transactionIndxLocked.toString()),
      this.sLib.Value.new_from_assets(multiAsset)
    ) // how much lovelace is at that UTXO


    txBuilder.set_fee(this.sLib.BigNum.from_str(Number(this.manualFee).toString()))

    const scripts = this.sLib.PlutusScripts.new();
    scripts.add(this.sLib.PlutusScript.from_bytes(Buffer.from(this.plutusScriptCborHex, "hex"))); //from cbor of plutus script

    // Add outputs
    const outputValStr = Number(this.manualFee).toString();

    let txOutputBuilder = this.sLib.TransactionOutputBuilder.new();
    txOutputBuilder = txOutputBuilder.with_address(shelleyChangeAddress);
    txOutputBuilder = txOutputBuilder.next();
    txOutputBuilder = txOutputBuilder.with_asset_and_min_required_coin(
      multiAsset,
      this.sLib.BigNum.from_str(protocolParams.coinsPerUtxoWord)
    )
    
    const txOutput = txOutputBuilder.build();
    txBuilder.add_output(txOutput)

    // once the transaction is ready, we build it to get the tx body without witnesses
    const txBody = txBuilder.build();

    const collateral = await this.getCollateral(storeWalletSelected);
    const inputs = this.sLib.TransactionInputs.new();
    collateral.forEach((utxo) => inputs.add(utxo.input()) );

    let datums = this.sLib.PlutusList.new();
    // datums.add(PlutusData.from_bytes(Buffer.from(this.state.datumStr, "utf8")))
    datums.add(this.sLib.PlutusData.new_integer(
      this.sLib.BigInt.from_str(this.datumStr)))

    const redeemers = this.sLib.Redeemers.new();

    const data = this.sLib.PlutusData.new_constr_plutus_data(
        this.sLib.ConstrPlutusData.new(
          this.sLib.BigNum.from_str("0"),
          this.sLib.PlutusList.new()
        )
    );

    const redeemer = this.sLib.Redeemer.new(
      this.sLib.RedeemerTag.new_spend(),
      this.sLib.BigNum.from_str("0"),
      data,
      this.sLib.ExUnits.new(
        this.sLib.BigNum.from_str("7000000"),
        this.sLib.BigNum.from_str("3000000000")
      )
    );

    redeemers.add(redeemer)

    //Tx witness
    const transactionWitnessSet = this.sLib.TransactionWitnessSet.new();

    transactionWitnessSet.set_plutus_scripts(scripts)
    transactionWitnessSet.set_plutus_data(datums)
    transactionWitnessSet.set_redeemers(redeemers)

    const costModel = this.sLib.TxBuilderConstants.plutus_vasil_cost_models().get(this.sLib.Language.new_plutus_v1())    

    const costModels = this.sLib.Costmdls.new();
    costModels.insert(this.sLib.Language.new_plutus_v1(), costModel);

    const scriptDataHash = this.sLib.hash_script_data(redeemers, costModels, datums);
    txBody.set_script_data_hash(scriptDataHash);

    txBody.set_collateral(inputs)

    const baseAddress = this.sLib.BaseAddress.from_address(shelleyChangeAddress)
    const requiredSigners = this.sLib.Ed25519KeyHashes.new();
    requiredSigners.add(baseAddress.payment_cred().to_keyhash())

    txBody.set_required_signers(requiredSigners);

    const tx = this.sLib.Transaction.new(
      txBody,
      this.sLib.TransactionWitnessSet.from_bytes(transactionWitnessSet.to_bytes())
    )

    let txVkeyWitnesses = await this.wallet.signTx(Buffer.from(tx.to_bytes(), "utf8").toString("hex"), true);
    txVkeyWitnesses = this.sLib.TransactionWitnessSet.from_bytes(Buffer.from(txVkeyWitnesses, "hex"));

    transactionWitnessSet.set_vkeys(txVkeyWitnesses.vkeys());
    const signedTx = this.sLib.Transaction.new(tx.body(), transactionWitnessSet);

    const submittedTxHash = await this.wallet.submitTx(Buffer.from(signedTx.to_bytes(), "utf8").toString("hex"));
    console.log('Tx Hash NFT Reedemed: ', submittedTxHash);
    return submittedTxHash
  }

The protocol Parameters used in this are

getProtocolParameters () {
   return {
     linearFee: {
         minFeeA: "44",
         minFeeB: "155381",
     },
     minUtxo: "34482",
     poolDeposit: "500000000",
     keyDeposit: "2000000",
     maxValSize: 5000,
     maxTxSize: 16384,
     priceMem: 0.0577,
     priceStep: 0.0000721,
     coinsPerUtxoWord: "34482",
   }
 }

The manual fee variable is

this.manualFee = 900000

Kindly suggest me some solution if you find any.

3 Answers 3

0

Looks like the error is telling you that you have a utxo problem. Your inputs must match your outputs. This could be that they're mismatched, or you're missing the input altogether. https://iohk.zendesk.com/hc/en-us/articles/900001220843-Transaction-errors-ValueNotConservedUTxO

I would also look at add_key_input as defined in the example here: https://github.com/Emurgo/cardano-serialization-lib/blob/master/doc/getting-started/minting-nfts.md

It seems like you're only setting the collateral with the inputs, but never actually setting the utxo's as input for the transaction.

3
  • Sorry i didn't get it what you are trying to mention. Like i do have set the txInput ` txBuilder.add_input( ScriptAddress, this.sLib.TransactionInput.new( this.sLib.TransactionHash.from_bytes(Buffer.from(transactionHashLocked, "hex")), this.transactionIndxLocked.toString()), this.sLib.Value.new_from_assets(multiAsset) ) ` Also i am setting the UTXO. and yes i know there is some imabalance in my inputs and outputs but i am unable to figure it out that how do i resolve this because i am new to cardano and this is the CSL modified code.
    – duaa azhar
    Commented Apr 20, 2023 at 5:26
  • @duaaazhar Ah ok, good. So you know it's a balance issue. To find the balance issue ... I would console.log() any time you set a value in the script, and keep a running sum so you can compare that with the utxo's you're adding in. Then you should be able to find the mismatch. However, maybe try something like txBuilder.add_change_if_needed(addr) and see if that doesn't just resolve the issue. I looked here for some reference. github.com/Emurgo/cardano-serialization-lib/issues/285
    – PREEB
    Commented Apr 20, 2023 at 12:41
  • Hi @PREEB , I have answered this in the reply to my question. Can you please help me out in that query? Thanks in Advance.
    – duaa azhar
    Commented May 5, 2023 at 12:46
0

I have tried the txBuilder.add_change_if_needed(addr) but it didnt work. Can you please tell me, that can a NFT alone be locked in a smart contract? Like i have tried that by sending the nft to smart contract by using function

txOutputBuilder.with_asset_and_min_required_coin(
      multiAsset,
      this.sLib.BigNum.from_str(protocolParams.coinsPerUtxoWord)
    )

So NFT gets locked with some minimal ADA of about 1.30593 (maybe its because of coinsPerUtxoWord). But when i wanna unlock that NFT from smart contract, how should i specify that minimal amount in my txInput as its according to coinsPerUtxoWord. And when i set the NFT alone in input, then the transaction gets unbalance, and shows the issue of imbalanced input and output of tx. I think this got imbalance because at output it requires some ADA for fee whereas i am passing NFT asset in input. To resolve this i have tried to pass UTXOS in txInput by using

const txUnspentOutputs = await this.getTxUnspentOutputs();
 txBuilder.add_inputs_from(txUnspentOutputs, 3)

and also i used later txBuilder.add_change_if_needed(shelleyChangeAddress) so that the change gets back to the changeAddress But again its showing me the issue of

""transaction submit error ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (UtxoFailure (FromAlonzoUtxoFail (ValueNotConservedUTxO ( Value 9996312330 (fromList [(PolicyID {policyID = ScriptHash \"80786e0ffb709cf2da414f0496be530a129c23f18c7a493abbf8589c\"},fromList [(617274426c61636b6e5768697465,1)])])) ( Value 9994984850 (fromList [(PolicyID {policyID = ScriptHash \"80786e0ffb709cf2da414f0496be530a129c23f18c7a493abbf8589c\"},fromList [(617274426c61636b6e5768697465,1)])]))))),UtxowFailure (UtxoFailure (FromAlonzoUtxoFail (FeeTooSmallUTxO (Coin 798505) (Coin 171969))))])""

I think the main issue is in this line of code i.e. txOutputBuilder = txOutputBuilder.with_asset_and_min_required_coin(multiAsset, this.sLib.BigNum.from_str(protocolParams.coinsPerUtxoWord)) of txOutput as given. But i dont know what to use instead of this nor i found any useful reading resource regarding it.

let txOutputBuilder = this.sLib.TransactionOutputBuilder.new();
    txOutputBuilder = txOutputBuilder.with_address(shelleyChangeAddress);
    txOutputBuilder = txOutputBuilder.next();
 
    txOutputBuilder = txOutputBuilder.with_asset_and_min_required_coin(multiAsset, this.sLib.BigNum.from_str(protocolParams.coinsPerUtxoWord))
  const txOutput = txOutputBuilder.build();
    
  
    txBuilder.add_output(txOutput)
    console.log("txBuilder after adding output: ", txBuilder)

Can you please help me out in this, I think i am a bit close just missing some points maybe. Your little guidance can be quite helpful. Thanks

0

As of today the Cardano Serialization Library is no longer maintained and you shouldn't rely on it.

The closest to the CSL is the Cardano Multiplatform library that still relies on Rust + WASM.

If you don't want to load 10 MB of WASM in your web app you can also try cardano-ledger-ts(for the types) + plu-ts-offchain(for the transaction builder)

npm install @harmoniclabs/cardano-ledger-ts @harmoniclabs/plu-ts-offchain

Not the answer you're looking for? Browse other questions tagged or ask your own question.