cardano-serialization-lib
cardano-serialization-lib copied to clipboard
Mint tokens in the "plutus way"
As I understand, in order to mint a token, you must use the function set_mint_scripts
, providing a NativeScripts
object. This NativeScript
object determines until what time a token can be minted and by whom.
With the introduction of Plutus, though, we have a new (and better) way of minting assets, which is using Plutus scripts that allow or not the minting and burning of the assets based on arbitrary logic. I need to use this approach for my project, but I can't find any similar (to set_mint_scripts
) function that require a PlutusScript
(not native one).
Is there a workaround I could use (or maybe an actual implementation that I missed)? If not, are there plans to add support for minting of tokens using Plutus scripts?
As I understand, in order to mint a token, you must use the function
set_mint_scripts
, providing aNativeScripts
object. ThisNativeScript
object determines until what time a token can be minted and by whom.With the introduction of Plutus, though, we have a new (and better) way of minting assets, which is using Plutus scripts that allow or not the minting and burning of the assets based on arbitrary logic. I need to use this approach for my project, but I can't find any similar (to
set_mint_scripts
) function that require aPlutusScript
(not native one).Is there a workaround I could use (or maybe an actual implementation that I missed)? If not, are there plans to add support for minting of tokens using Plutus scripts?
Hi, @mateusap1! Right now there's no separate support for any Plutus patterns specifically, only fundamental protocol types for Plutus are supported at the moment, and the TransactionBuilder
has no special support for Plutus features yet at all.
By Plutus-controlled minting do you mean you would have posted some UTxO with your script and then every time you want to mint mroe of that token you would have to spend that UTxO in the tx for the script to validate the tx and the included mint, or something like that? I am curious how would you protect the minting policy from being mintable without havign to include that script input anyways, by just providing the key-hash witnesses.
Overall at the moment you can produce script outputs with no problem even by using TransactionBuilder
if I am not mistaken as it does not care about the output types explicitly, so you can "deploy" a Plutus script with no problem. But support for spending script inputs is still being worked on for the transaction-builder
Thank you for your answer.
By Plutus-controlled minting do you mean you would have posted some UTxO with your script and then every time you want to mint mroe of that token you would have to spend that UTxO in the tx for the script to validate the tx and the included mint, or something like that?
Well, kind of, the way it's implemented you don't have to spend any UTxO, it just executes the minting policy script (it has no datum as well). If you've seen the plutus pioneer program lectures, you saw that we are presented with a new way of minting tokens. You can make a minting policy script that runs every time someone tries to mint (or burn) this token (represented by an arbitrary token name and with the currency symbol corresponding to the script hash).
For example, you can make a script that only validates if the transaction is signed by someone (see mkPolicy function here). But, of course, there are many more interesting applications to this new way of handling minting policies (like making sure a certain value is sent to a certain address before minting), and this approach is fundamental for the application I'm trying to build.
I am curious how would you protect the minting policy from being mintable without havign to include that script input anyways, by just providing the key-hash witnesses.
In fact, this is already possible with cardano-cli and this blog post goes over how to mint tokens in this new way using cardano-cli. The mint_nft.sh
script he uses is this one and, as you can see, mint
requires a script file and a redeemer. So, to answer your question, you would actually need a script file (not as an input though, since it's not an UTxO) as one of your parameters.
If you could point me in a direction of how I would be able to implement this using the serialization-lib (even if I needed to modify the low-level stuff), I would greatly appreciate (of course, if it's actually possible). Also, it might be a good idea to add support for this in the future (if I'm right in assuming it's not supported yet)
Thank you for your answer.
By Plutus-controlled minting do you mean you would have posted some UTxO with your script and then every time you want to mint mroe of that token you would have to spend that UTxO in the tx for the script to validate the tx and the included mint, or something like that?
Well, kind of, the way it's implemented you don't have to spend any UTxO, it just executes the minting policy script (it has no datum as well). If you've seen the plutus pioneer program lectures, you saw that we are presented with a new way of minting tokens. You can make a minting policy script that runs every time someone tries to mint (or burn) this token (represented by an arbitrary token name and with the currency symbol corresponding to the script hash).
For example, you can make a script that only validates if the transaction is signed by someone (see mkPolicy function here). But, of course, there are many more interesting applications to this new way of handling minting policies (like making sure a certain value is sent to a certain address before minting), and this approach is fundamental for the application I'm trying to build.
I am curious how would you protect the minting policy from being mintable without havign to include that script input anyways, by just providing the key-hash witnesses.
In fact, this is already possible with cardano-cli and this blog post goes over how to mint tokens in this new way using cardano-cli. The
mint_nft.sh
script he uses is this one and, as you can see,mint
requires a script file and a redeemer. So, to answer your question, you would actually need a script file (not as an input though, since it's not an UTxO) as one of your parameters.If you could point me in a direction of how I would be able to implement this using the serialization-lib (even if I needed to modify the low-level stuff), I would greatly appreciate (of course, if it's actually possible). Also, it might be a good idea to add support for this in the future (if I'm right in assuming it's not supported yet)
Hi, I've had this issue myself and have recently found a workaround to mint tokens using plutus scripts. It uses the 'SpaceBudz' modified version of the Serialization Lib that can be found here: https://github.com/Berry-Pool/spacebudz/tree/main/src/cardano/market/custom_modules/%40emurgo.
There are some slight differences to how the transaction builder is used, but you can follow the example here: https://github.com/Berry-Pool/spacebudz/blob/main/src/cardano/market/index.js
The idea is to create a transaction as you normally would with the transaction builder and build the minting part of the script manually using the Mint
and MintAsset
classes of the serialization lib. Note that a downside of this method is that you need to manually set the fee, either by setting it to a larger value or by reliably estimating it from a different source (e.g maybe a dummy transaction in the CLI itself).
To mimic the --mint
line of the cli, the minted assets need to be specified. Create the minted value as a multi-asset (to be included as output in the transaction builder) eg:
const mintedAssets = Cardano.Assets.new();
mintedAssets.insert(
Cardano.AssetName.new(Buffer.from("MONT")), // Name
Cardano.BigNum.from_str("1") // Quantity
);
// Add to multi asset with the policyId of the policy script
// (you can just hardcode value returned by the CLI)
const multiAsset = Cardano.MultiAsset.new();
multiAsset.insert(
Cardano.ScriptHash.from_bytes(
Cardano.Ed25519KeyHash.from_bytes(
fromHex(policyId)
).to_bytes()
),
mintedAssets
);
Add this multi asset to a Value object (this is the value object that you use in one of your outputs):
let mintedValue = Cardano.Value.new(
Cardano.BigNum.from_str("0")
); // Tally minted value
mintedValue.set_multiasset(multiAsset);
Create a 'Mint' object, which signals that this transaction includes a minting operation, balancing the output. Add the minted assets to this object (the code is essentially identical to above):
const mint = Cardano.Mint.new();
const mintAssets = Cardano.MintAssets.new();
mintAssets.insert(
Cardano.AssetName.new(Buffer.from("MONT")), // Name
Cardano.Int.new(Cardano.BigNum.from_str("1")) // Quantity
);
mint.insert(
Cardano.ScriptHash.from_bytes(
Cardano.Ed25519KeyHash.from_bytes(
fromHex(policyId)
).to_bytes()
),
mintAssets
);
Create a redeemer for the transaction in a similar way to the following:
function mintRedeemer(index) {
// Create the redeemer data
const redeemerData = Cardano.PlutusData.new_constr_plutus_data(
Cardano.ConstrPlutusData.new(
Cardano.Int.new_i32(REDEEMER_VALUE),
Cardano.PlutusList.new()
)
);
// redeemer itself
const redeemer = Cardano.Redeemer.new(
Cardano.RedeemerTag.new_mint(),
Cardano.BigNum.from_str(index),
redeemerData,
Cardano.ExUnits.new(
Cardano.BigNum.from_str(EX_UNIT_A),
Cardano.BigNum.from_str(EX_UNIT_B)
)
);
return redeemer;
}
The difference here is the redeemer tag, which you set to Cardano.RedeemerTag.new_mint()
. The index should correspond to the order of your inputs (if this is incorrectly set you will get a missing or extra redeemer error). If you are not spending any other script inputs, this is just 0.
You can then create your transaction in the normal way using the transaction builder, making sure all the inputs and outputs balance and setting the fee using TransactionBuilder.set_fee()
. You include the policy script the same was as a validator script with transactionBuilder.set_plutus_scripts(YOUR_SCRIPTS)
. The minting redeemer with transactionBuilder.set_redeemers(YOUR_REDEEMERS)
. The witnesses need to be made manually, the example link I included shows how this is done.
You build the transaction body with TransactionBuilder.build()
which returns the generated body. Using this returned object, the final part is to do transactionBody.set_mint(mint);
, where mint is the Mint object you created earlier.
I know this was brief and maybe not ideal for your situation but let me know if you need any clarification!
Thank you very much @Montel98, that was very clear.
Thank you very much @Montel98, that was very clear.
No problem, also I forgot to mention to add collateral by using transactionBuilder.set_collateral(inputs)
.
I am getting an "INPUTS_EXHAUSTED"
error when the app tries to do coin selection. Am I missing something? Do I need to add ADA to an additional output?
Oh I see now, I added the output with the minted values before making the coin selection, so the app went crazy trying to find an input with the assets I provided (which will be minted) and, of course, couldn't find any. So, for anyone that may find this thread, don't be stupid like me and add the minted value to the output after the coin selection is made.
The index should correspond to the order of your inputs (if this is incorrectly set you will get a missing or extra redeemer error). If you are not spending any other script inputs, this is just 0.
Sorry to bother you @Montel98, but when we mint a token we don't necessarily spend a script input, do we? In that case I am not sure what value my index should be, I'm using "0", but the console is giving me a web assembly "unreachable" error
The index should correspond to the order of your inputs (if this is incorrectly set you will get a missing or extra redeemer error). If you are not spending any other script inputs, this is just 0.
Sorry to bother you @Montel98, but when we mint a token we don't necessarily spend a script input, do we? In that case I am not sure what value my index should be, I'm using "0", but the console is giving me a web assembly "unreachable" error
No you're right you don't spend a script input, but the index is still required. Are you able to post the full error message? I might be able to help. I used 0 for my initial script, but that was without any script inputs.
Of course, I don't think it will be very helpful, but here it is:
Uncaught (in promise) RuntimeError: unreachable
at 35c481a3c170e5a6ce28.wasm:0xb3eee
at 35c481a3c170e5a6ce28.wasm:0xd494f
at 35c481a3c170e5a6ce28.wasm:0xe5927
at 35c481a3c170e5a6ce28.wasm:0xe4580
at 35c481a3c170e5a6ce28.wasm:0xe598b
at 35c481a3c170e5a6ce28.wasm:0xe33c1
at 35c481a3c170e5a6ce28.wasm:0x3ebd7
at 35c481a3c170e5a6ce28.wasm:0x98f58
at 35c481a3c170e5a6ce28.wasm:0xa6527
at 35c481a3c170e5a6ce28.wasm:0xe4717
at 35c481a3c170e5a6ce28.wasm:0x106b4
at 35c481a3c170e5a6ce28.wasm:0xd02ec
at TransactionBuilder.add_change_if_needed (cardano_serialization_lib_bg.js?45ba:10344)
at _callee2$ (buy.js?5930:349)
at tryCatch (runtime.js?96cf:63)
at Generator.invoke [as _invoke] (runtime.js?96cf:294)
at Generator.eval [as next] (runtime.js?96cf:119)
at asyncGeneratorStep (asyncToGenerator.js?1bd0:3)
at _next (asyncToGenerator.js?1bd0:25)
I know this error is related to the redeemer, because when I comment the redeemer lines, the error disappears
This is the code I'm using
const redeemers = Loader.Cardano.Redeemers.new();
redeemers.add(mintRedeemer("0"));
txBuilder.set_redeemers(Loader.Cardano.Redeemers.from_bytes(redeemers.to_bytes()));
Where mintRedeemer
is defined as
function mintRedeemer(index) {
// Create the redeemer data
const redeemerData = Loader.Cardano.PlutusData.new_constr_plutus_data(
Loader.Cardano.ConstrPlutusData.new(
Loader.Cardano.Int.new_i32(0),
Loader.Cardano.PlutusList.new()
)
);
// redeemer itself
const redeemer = Loader.Cardano.Redeemer.new(
Loader.Cardano.RedeemerTag.new_mint(),
Loader.Cardano.BigNum.from_str(index),
redeemerData,
Loader.Cardano.ExUnits.new(
Loader.Cardano.BigNum.from_str(EX_UNIT_A),
Loader.Cardano.BigNum.from_str(EX_UNIT_B)
)
);
return redeemer;
}
Of course, I don't think it will be very helpful, but here it is:
Uncaught (in promise) RuntimeError: unreachable at 35c481a3c170e5a6ce28.wasm:0xb3eee at 35c481a3c170e5a6ce28.wasm:0xd494f at 35c481a3c170e5a6ce28.wasm:0xe5927 at 35c481a3c170e5a6ce28.wasm:0xe4580 at 35c481a3c170e5a6ce28.wasm:0xe598b at 35c481a3c170e5a6ce28.wasm:0xe33c1 at 35c481a3c170e5a6ce28.wasm:0x3ebd7 at 35c481a3c170e5a6ce28.wasm:0x98f58 at 35c481a3c170e5a6ce28.wasm:0xa6527 at 35c481a3c170e5a6ce28.wasm:0xe4717 at 35c481a3c170e5a6ce28.wasm:0x106b4 at 35c481a3c170e5a6ce28.wasm:0xd02ec at TransactionBuilder.add_change_if_needed (cardano_serialization_lib_bg.js?45ba:10344) at _callee2$ (buy.js?5930:349) at tryCatch (runtime.js?96cf:63) at Generator.invoke [as _invoke] (runtime.js?96cf:294) at Generator.eval [as next] (runtime.js?96cf:119) at asyncGeneratorStep (asyncToGenerator.js?1bd0:3) at _next (asyncToGenerator.js?1bd0:25)
I'm not 100% sure but it looks like you are using add_change_if_needed()
? You can't use that function as the transaction builder technically doesn't support minting and will freak out. You have to manually calculate the change and add that as an output. It's strange that it goes away when you comment out the redeemer though, the code is basically identical to how I did it.
Thank you again for your answer, I did change to set_fee
and manually created the outputs (simulating add_change
), but I still receive the same error (but in a different method).
Uncaught (in promise) RuntimeError: unreachable
at 35c481a3c170e5a6ce28.wasm:0xb3eee
at 35c481a3c170e5a6ce28.wasm:0xd494f
at 35c481a3c170e5a6ce28.wasm:0xe5927
at 35c481a3c170e5a6ce28.wasm:0xe4580
at 35c481a3c170e5a6ce28.wasm:0xe598b
at 35c481a3c170e5a6ce28.wasm:0xe33c1
at 35c481a3c170e5a6ce28.wasm:0x3ebd7
at 35c481a3c170e5a6ce28.wasm:0x98f58
at 35c481a3c170e5a6ce28.wasm:0xc542b
at TransactionBuilder.build (cardano_serialization_lib_bg.js?45ba:10374)
at _callee2$ (buy.js?5930:378)
at tryCatch (runtime.js?96cf:63)
at Generator.invoke [as _invoke] (runtime.js?96cf:294)
at Generator.eval [as next] (runtime.js?96cf:119)
at asyncGeneratorStep (asyncToGenerator.js?1bd0:3)
at _next (asyncToGenerator.js?1bd0:25)
The full createAccount
function:
export async function createAccount(endpoint, project_id) {
// Get Nami handler
const cardano = window.cardano;
// Get protocol parameters
const protocolParameters = await getProtocolParameters(endpoint, project_id);
// Converts the wallet address into a BECH32 format
const paymentAddr = Loader.Cardano.Address.from_bytes(
fromHex(await cardano.getChangeAddress())
).to_bech32();
// Get's a list of UTxOs that belong to our user
const rawUtxo = await cardano.getUtxos();
// Go over each of these raw UTxOs and convert them to a better format
const utxos = rawUtxo.map((u) =>
Loader.Cardano.TransactionUnspentOutput.from_bytes(fromHex(u))
);
const mintedValue = getMintedValue(freeAddr, "token", 100);
// Create an empty outputs variable
const fakeOutputs = Loader.Cardano.TransactionOutputs.new();
fakeOutputs.add(
createOutput(
Loader.Cardano.Address.from_bech32(testAddr),
Loader.Cardano.Value.new(
Loader.Cardano.min_ada_required(mintedValue, protocolParameters.minUtxo)
),
protocolParameters.minUtxo
)
);
const MULTIASSET_SIZE = 5848;
const VALUE_SIZE = 5860;
const totalAssets = 0;
CoinSelection.setProtocolParameters(
protocolParameters.minUtxo.to_str(),
protocolParameters.linearFee.coefficient().to_str(),
protocolParameters.linearFee.constant().to_str(),
protocolParameters.maxTxSize.toString()
);
const selection = await CoinSelection.randomImprove(
utxos,
fakeOutputs,
20 + totalAssets,
protocolParameters.minUtxo.to_str()
);
const inputs = selection.input;
const txBuilder = Loader.Cardano.TransactionBuilder.new(
protocolParameters.linearFee,
protocolParameters.minUtxo,
protocolParameters.poolDeposit,
protocolParameters.keyDeposit,
protocolParameters.maxValSize,
protocolParameters.maxTxSize
);
const collateral = (await window.cardano.getCollateral()).map((utxo) =>
Loader.Cardano.TransactionUnspentOutput.from_bytes(fromHex(utxo))
);
if (collateral.length <= 0) throw new Error("NO_COLLATERAL");
let value = Loader.Cardano.Value.new(Loader.Cardano.BigNum.from_str("0"));
for (let i = 0; i < inputs.length; i++) {
const utxo = inputs[i];
txBuilder.add_input(
utxo.output().address(),
utxo.input(),
utxo.output().amount()
);
value = value.checked_add(utxo.output().amount());
}
// Create an empty outputs variable
const realOutputs = Loader.Cardano.TransactionOutputs.new();
value = value.checked_sub(
Loader.Cardano.Value.new(Loader.Cardano.BigNum.from_str(FEE))
);
value = value.checked_add(mintedValue);
realOutputs.add(
createOutput(
Loader.Cardano.Address.from_bech32(paymentAddr),
value,
protocolParameters.minUtxo
)
);
txBuilder.add_output(realOutputs.get(0));
const redeemers = Loader.Cardano.Redeemers.new();
redeemers.add(mintRedeemer("0"));
txBuilder.set_redeemers(
Loader.Cardano.Redeemers.from_bytes(redeemers.to_bytes())
);
const scripts = Loader.Cardano.PlutusScripts.new();
scripts.add(serializeScript(freeScript));
txBuilder.set_plutus_scripts(scripts);
addCollateral(txBuilder, collateral);
txBuilder.set_fee(Loader.Cardano.BigNum.from_str(FEE));
const txBody = txBuilder.build();
const mint = getMint(freeAddr, "token", 100);
txBody.set_mint(mint);
const transaction = Loader.Cardano.Transaction.new(
txBody,
Loader.Cardano.TransactionWitnessSet.new()
);
const size = transaction.to_bytes().length * 2;
if (size > protocolParameters.maxTxSize)
throw new Error("Transaction too big");
const signatureWitness = await cardano.signTx(
Buffer.from(transaction.to_bytes(), "hex").toString("hex")
);
const witnessSet = Loader.Cardano.TransactionWitnessSet.from_bytes(
Buffer.from(signatureWitness, "hex")
);
witnessSet.set_plutus_scripts(scripts);
witnessSet.set_redeemers(redeemers);
const signedTx = Loader.Cardano.Transaction.new(
transaction.body(),
witnessSet
);
const txhash = await cardano.submitTx(
Buffer.from(signedTx.to_bytes(), "hex").toString("hex")
);
return txhash;
}
Thank you again for your answer, I did change to
set_fee
and manually created the outputs (simulatingadd_change
), but I still receive the same error (but in a different method).Uncaught (in promise) RuntimeError: unreachable at 35c481a3c170e5a6ce28.wasm:0xb3eee at 35c481a3c170e5a6ce28.wasm:0xd494f at 35c481a3c170e5a6ce28.wasm:0xe5927 at 35c481a3c170e5a6ce28.wasm:0xe4580 at 35c481a3c170e5a6ce28.wasm:0xe598b at 35c481a3c170e5a6ce28.wasm:0xe33c1 at 35c481a3c170e5a6ce28.wasm:0x3ebd7 at 35c481a3c170e5a6ce28.wasm:0x98f58 at 35c481a3c170e5a6ce28.wasm:0xc542b at TransactionBuilder.build (cardano_serialization_lib_bg.js?45ba:10374) at _callee2$ (buy.js?5930:378) at tryCatch (runtime.js?96cf:63) at Generator.invoke [as _invoke] (runtime.js?96cf:294) at Generator.eval [as next] (runtime.js?96cf:119) at asyncGeneratorStep (asyncToGenerator.js?1bd0:3) at _next (asyncToGenerator.js?1bd0:25)
Did you manage to get it working? It looks like you aren't using the custom serialisation lib used in SpaceBudz? It won't work without it and is most likely what is causing the error as the rest of your code looks fine to me.
I just gave up and I'm trying to use the PAB now, I'll wait for official support. I am sure I am using the custom module since I even removed serialization-lib with npm. In any case, thanks for all the help.
I'll leave the issue open since I think integration with plutus minting policies is still a critical feature, which is not currently supported (officially)
I agree with this. I am using the spacebudz custom implementation, but I would rather be using the officially supported libraries to make sure updates are included. However, currently, there is no option in this fork.
Is there any way to mint token with plutus script and serialization-lib by Nami wallet for now
Is there any way to mint token with plutus script and serialization-lib by Nami wallet for now
Not yet, but we will add it right in the next version
Got it, but is there any way to mint nft with plutus script by Nami wallet right now (any lib can be oke)
how to add metadata.json and mint NFT?
Plutus minting now available in the new CSL version 11.2.0