cardano-serialization-lib
cardano-serialization-lib copied to clipboard
What does redeemer index mean
When I pass index of redeemer is 0, sometimes it works, and sometimes I have to change it to 2 to make it works (like the code below) So what does that mean and how could I know what should I put to index param
const redeemer = CardanoWasm.Redeemer.new(
CardanoWasm.RedeemerTag.new_spend(),
CardanoWasm.BigNum.from_str('2'),
redeemerData,
CardanoWasm.ExUnits.new(
CardanoWasm.BigNum.from_str(BUY_CARDANO_MEM.toString()),
CardanoWasm.BigNum.from_str(BUY_CARDANO_CPU.toString()),
),
);
@nmaddp1995 , the redeemer index is pointing to the input the specific redeemer is for in your input list in the transaction. For example, if you add inputs like: plutus input, some other input, some other input - the index must be zero because the plutus input is the first one in the list; but if you add inputs like: some regular input, plutus input, some other regular input - then the index must be one, and so forth.
In case you have multiple plutus inputs each redeemer must have a correct index pointing to the correct corresponding input. If you were to use the new TxInputsBuilder.add_plutus_script_input
functions, they would set the proper index automatically when you build the transaction, because the inputs also must be canonically sorted by the referenced transaction ID, so it might be tricky to get right. Using the new APIs to add script inputs is the best option. Check the release notes for versions 10.1.0
and 10.2.0
here - https://github.com/Emurgo/cardano-serialization-lib/releases
@vsubhuman I make buy function with txin is tx put on sale and utxo of buyer
txBuilder.add_input(ScriptAddress, txPutOnSale, assetValPutOnsale);
for (let i = 0; i < hexInputUtxos.length; i++) {
const wasmUtxo = CardanoWasm.TransactionUnspentOutput.from_bytes(hexToBytes(hexInputUtxos[i]));
txBuilder.add_input(wasmUtxo.output().address(), wasmUtxo.input(), wasmUtxo.output().amount());
}
And I use set_pluts_scripts for txWitness:
const transactionWitnessSet = CardanoWasm.TransactionWitnessSet.new();
transactionWitnessSet.set_plutus_scripts(scripts);
transactionWitnessSet.set_plutus_data(datums);
transactionWitnessSet.set_redeemers(redeemers);
Also I set script datahash for txBody
const scriptDataHash = CardanoWasm.hash_script_data(redeemers, costModels, datums);
txBody.set_script_data_hash(scriptDataHash);
So I am not sure how could I get the right index of redeemer
txBuilder.add_input(ScriptAddress, txPutOnSale, assetValPutOnsale);
@nmaddp1995 , replace this with this:
const plutusWitness = CardanoWasm.PlutusWitness.new(plutusScript, plutusDatum, plutusRedeemer);
txBuilder.add_plutus_script_input(plutusWitness, txPutOnSale, assetValPutOnsale);
Then replace txBody.set_script_data_hash
with auto-calculation using this:
txBuilder.calc_script_data_hash(costModels);
(And make sure to call it before you are calling the build function, of course)
Then make sure to use the proper function .build_tx()
instead of the deprecated .build()
, it will return you the full transaction which includes the script, the data, and the redeemers, so you don't need to put them into the witness set by yourself:
// This transaction already contains the plutus witness data for the script input
// Only have to add vkey witness signatures for regular inputs
const transaction = txBuilder.build_tx();
In the resulting transaction the redeemer IS already present in the witness set and also it has the correct index that matches the position of the input after the canonical sorting.
Hi @nmaddp1995 here is a reference implementation that works for most cases https://github.com/MartifyLabs/mesh/blob/smart-contracts/packages/module/src/transaction/transaction.service.ts I hope that help 😉
Tks, I got the index of Plutus transaction by this code
const allInputs = txInputsBuilder.inputs();
const numberOfInput = allInputs.len();
let indexRedeemer = 0;
for (let i = 0; i < numberOfInput; i++) {
const curInput = allInputs.get(i);
const txId = bytesToHex(curInput.transaction_id().to_bytes());
if (txId?.toString() === resellOrderData.txIdPutOnSale?.toString()) {
indexRedeemer = i;
}
}
BTW, I am making the buy NFT function but I have to define the gas fee to calculate the res coin for buyer and make tx out for that. Is that the right approach for that? Or is there any solution to calculate the exact fee for a transaction or solution to not have to fixed fee
is there any solution to calculate the exact fee for a transaction
@nmaddp1995 , if you use the txBuilder.add_change_if_needed(changeAddress)
function it will calculate the required fee and create the change outputs automatically, but in order for that to work you need to use the APIs the way I have described them here: https://github.com/Emurgo/cardano-serialization-lib/issues/499#issuecomment-1219239234
Because the builder then must know all the information about your inputs and which witnesses they will require, before you try estimating the fee. If you keep adding the scripts, datums, and redeemers manually the fee estimation will not work correctly.
@vsubhuman @abdelkrimdev Hi, I change my function to the same as your purchase function
` const txPutOnSale = CardanoWasm.TransactionInput.new( CardanoWasm.TransactionHash.from_bytes(Buffer.from(resellOrderData.txIdPutOnSale, 'hex')), get(resellOrderData, 'metadata.output_index'), );
const serviceFee = resellOrderData.fee;
const { priceServiceFee, priceRoyaltyFee, priceForSeller } = getPriceData({
sellOrderPrice: resellOrderData.price,
coinDecimal: TOKEN_ADA_DECIMAL,
serviceFee,
royaltyFee: nftData.royaltyFee,
});
const datum = getDatumFromData();
const datums = CardanoWasm.PlutusList.new();
const outputs = CardanoWasm.TransactionOutputs.new();
datums.add(datum);
const hexInputUtxos = await cardanoApi.getUtxos();
const utxos = hexInputUtxos.map((item: any) => CardanoWasm.TransactionUnspentOutput.from_bytes(hexToBytes(item)));
const assetValPutOnsale = CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(LOCK_AMOUNT_PUT_ON_SALE.toString()));
const assetMAPutOnsale = CardanoWasm.MultiAsset.new();
const assetPutOnsale = CardanoWasm.Assets.new();
assetPutOnsale.insert(
CardanoWasm.AssetName.new(hexToBytes(convertStringToHex(tokenId))),
CardanoWasm.BigNum.from_str('1'),
);
assetMAPutOnsale.insert(CardanoWasm.ScriptHash.from_bytes(hexToBytes(nftPolicy)), assetPutOnsale);
assetValPutOnsale.set_multiasset(assetMAPutOnsale);
// nft to buyer
outputs.add(CardanoWasm.TransactionOutput.new(shelleyChangeAddress, assetValPutOnsale));
// service fee
outputs.add(
CardanoWasm.TransactionOutput.new(
walletAdminAddress,
CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(priceServiceFee.toString())),
),
);
// money to seller
outputs.add(
CardanoWasm.TransactionOutput.new(
sellerAddress,
CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(priceForSeller)),
),
);
// money to creator (royalty)
outputs.add(
CardanoWasm.TransactionOutput.new(
creatorAddress,
CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(priceRoyaltyFee)),
),
);
const transactionWitnessSet = CardanoWasm.TransactionWitnessSet.new();
CoinSelection.setProtocolParameters(
Parameters.coinsPerUtxoWord,
Parameters.linearFee.minFeeA,
Parameters.linearFee.minFeeB,
Parameters.maxTxSize.toString(),
);
const inputs = [...utxos];
const inputPutOnSale = CardanoWasm.TransactionUnspentOutput.new(
txPutOnSale,
CardanoWasm.TransactionOutput.new(ScriptAddress, assetValPutOnsale),
);
inputs.push(inputPutOnSale);
console.log('inputs', inputs);
console.log('outputs', outputs);
const { input, change } = CoinSelection.randomImprove(inputs, outputs, 16);
input.forEach((utxo: any) => {
txBuilder.add_input(utxo.output().address(), utxo.input(), utxo.output().amount());
});
for (let i = 0; i < outputs.len(); i++) {
txBuilder.add_output(outputs.get(i));
}
const redeemers = CardanoWasm.Redeemers.new();
const indexRedeemer = 0;
const plutusListRedeemer = CardanoWasm.PlutusList.new();
plutusListRedeemer.add(CardanoWasm.PlutusData.new_bytes(hexToBytes(resellOrderData._id))); // sell order id
const redeemerData = CardanoWasm.PlutusData.new_constr_plutus_data(
CardanoWasm.ConstrPlutusData.new(CardanoWasm.BigNum.from_str(CONTRACT_CARDANO_EVENT.BUY), plutusListRedeemer),
);
const redeemer = CardanoWasm.Redeemer.new(
CardanoWasm.RedeemerTag.new_spend(),
CardanoWasm.BigNum.from_str(indexRedeemer.toString()),
redeemerData,
CardanoWasm.ExUnits.new(
CardanoWasm.BigNum.from_str(BUY_CARDANO_MEM.toString()),
CardanoWasm.BigNum.from_str(BUY_CARDANO_CPU.toString()),
),
);
redeemers.add(redeemer);
const scripts = CardanoWasm.PlutusScripts.new();
scripts.add(CardanoWasm.PlutusScript.from_bytes(Buffer.from(cborHexContract, 'hex'))); //from cbor of plutus script
transactionWitnessSet.set_redeemers(redeemers);
transactionWitnessSet.set_plutus_scripts(scripts);
transactionWitnessSet.set_plutus_data(datums);
const inputsCollateral = CardanoWasm.TxInputsBuilder.new();
const hexCollateralUtxos = await window.cardano.getCollateral(COLLATERAL_NAMI_AMOUNT);
for (let i = 0; i < hexCollateralUtxos.length; i++) {
const wasmUtxo = CardanoWasm.TransactionUnspentOutput.from_bytes(hexToBytes(hexCollateralUtxos[i]));
inputsCollateral.add_input(wasmUtxo.output().address(), wasmUtxo.input(), wasmUtxo.output().amount());
}
txBuilder.set_collateral(inputsCollateral);
// const plutusWitness = CardanoWasm.PlutusWitness.new(
// CardanoWasm.PlutusScript.from_bytes(Buffer.from(cborHexContract, 'hex')),
// datum,
// redeemer,
// );
// txBuilder.add_plutus_script_input(plutusWitness, txPutOnSale, assetValPutOnsale);
// console.log('5');
const changeMultiAssets = change.multiasset();
console.log('changeMultiAssets', changeMultiAssets);
// const cost_model_vals = COST_MODEL_VAL;
// const costModel = CardanoWasm.CostModel.new();
// cost_model_vals.forEach((x, i) => costModel.set(i, CardanoWasm.Int.new_i32(x)));
// const costModels = CardanoWasm.Costmdls.new();
// costModels.insert(CardanoWasm.Language.new_plutus_v1(), costModel);
// txBuilder.calc_script_data_hash(costModels);
txBuilder.add_change_if_needed(shelleyChangeAddress);
const txBody = txBuilder.build();
const tx = CardanoWasm.Transaction.new(
txBody,
CardanoWasm.TransactionWitnessSet.from_bytes(transactionWitnessSet.to_bytes()),
);
// const tx = txBuilder.build_tx();
const size = tx.to_bytes().length * 2;
console.log('size', size);
// if (size > Parameters.maxTxSize) throw new Error(ErrorTypes.MAX_SIZE_REACHED);
let txVkeyWitnesses = await cardanoApi.signTx(bytesToHex(tx.to_bytes()), true);
txVkeyWitnesses = CardanoWasm.TransactionWitnessSet.from_bytes(hexToBytes(txVkeyWitnesses));
transactionWitnessSet.set_vkeys(txVkeyWitnesses.vkeys());
const signedTx = CardanoWasm.Transaction.new(tx.body(), transactionWitnessSet, tx.auxiliary_data());
const txHash = await cardanoApi.submitTx(bytesToHex(signedTx.to_bytes()));
`
But this auto calculate fee is 0.2 ADA, it return error
UtxowFailure (FromAlonzoUtxowFail (PPViewHashesDontMatch SNothing (SJust (SafeHash \"abebcdc89a8c403d4a5eac1e9cea6a0722aac60fdaacb2f8f9b89d507868caa7\")))),UtxowFailure (UtxoFailure (FromAlonzoUtxoFail (FeeTooSmallUTxO (Coin 1098996) (Coin 191813))))])
If I use txBuilder.add_plutus_script_input and txBuilder.calc_script_data_hash, it return error when call add_change_if_needed
missing input or output for some native asset
Do you have any idea about that
Hi, @nmaddp1995 we managed to solve all the issues related to locking and unlocking an asset from a Smart Contract. and we create a package called Mesh I highly encourage you to check it out at https://mesh.martify.io/ it's under active development and we are adding new features constantly.
The fee calculation is wrong because you are adding the plutusWitness after add_change_if_needed
Hi, @nmaddp1995 we managed to solve all the issues related to locking and unlocking an asset from a Smart Contract. and we create a package called Mesh I highly encourage you to check it out at https://mesh.martify.io/ it's under active development and we are adding new features constantly.
The fee calculation is wrong because you are adding the plutusWitness after
add_change_if_needed
@abdelkrimdev Hi, about the adding the plutusWitness, can you clarify for me how to add plutusWitness in the correct way to add_change_if_needed. If I use txBuilder.add_plutus_script_input, it return the error missing input or output for some native asset
I am in the way to release app to product so I can't change the lib to Mesh on a sort time
Hi @nmaddp1995. Actually the error missing input or output for some native asset
can be connected with adding plutusWitness, because each script witness can increase tx fee. You can read about reasons of the error in this issue https://github.com/Emurgo/cardano-serialization-lib/issues/285#issuecomment-124361685 , probably you don't have enough ada or assets in the tx_builder
's inputs to cover amount of all outputs + fee. And anyway I strongly recommend to use way that @vsubhuman offered to you https://github.com/Emurgo/cardano-serialization-lib/issues/499#issuecomment-1219239234
.
const tx = CardanoWasm.Transaction.new( txBody, CardanoWasm.TransactionWitnessSet.from_bytes(transactionWitnessSet.to_bytes()), );
The way that you use to create the tx
is wrong because you get TransactionWitnessSet not from tx_builder
and tx_builder
doesn't know about your scripts, that can affect correct fee and change calculation. If you choose the build_tx()
instead build()
you can get TransactionWitnessSet from tx
by tx.witness_set()
it can help you with signed tx
creation. But all scripts must be in the tx_builder
before calling the add_change_if_needed
.
More about redeemer index propose you can find in the Alonzo spec https://github.com/input-output-hk/cardano-ledger