Cardano offers a way to create native tokens on the blockchain. We recommend reading the guide Discover native tokens before continuing.
Token minting and burning is controlled by a monetary policy script, i.e. a script which says who is eligible to mint and burn tokens.
In this guide we will create a policy which will require two signatures, one will be a public key derived on a hardware wallet, and one will be a public key created via cardano-cli
.
To generate the public key on a hardware wallet a 1855H
path should be used as described in CIP-1855:
cardano-hw-cli address key-gen \
--path 1855H/1815H/0H \
--verification-key-file mint-hw.vkey \
--hw-signing-file mint-hw.hwsfile
The second key for the purpose of this demo will be a cardano-cli
generated one.
cardano-cli address key-gen \
--normal-key \
--verification-key-file mint-cli.vkey \
--signing-key-file mint-cli.skey
With our keys generated we can create the policy native script:
cat >policy.script <<EOF
{
"type": "all",
"scripts": [
{
"type": "sig",
"keyHash": "$(cardano-cli address key-hash --payment-verification-key-file mint-hw.vkey)"
},
{
"type": "sig",
"keyHash": "$(cardano-cli address key-hash --payment-verification-key-file mint-cli.vkey)"
}
]
}
EOF
In the transaction we are about to compute we will also be using the hash of the native script, also called the policy id. To create it we can use this command:
cardano-hw-cli transaction policyid \
--script-file policy.script \
--hw-signing-file mint-hw.hwsfile >policyId
The --hw-signing-file
argument is optional, but this will make the hardware wallet show you the path of the keyHash
where the hash is identical with the one in the .hwsfile
.
It is also possible to compute the hash with the regular cardano-cli
:
cardano-cli transaction policyid --script-file policy.script
Note: This part of the guide assumes you already have some funded address from which you are going to make this transaction, located in a file named payment.addr
.
In this transaction we are going to mint 1000000
tokens named C0in
. First, we need to encode the token name:
TOKEN_NAME=$(echo -n "C0in" | xxd -b -ps -c 80 | tr -d '\n')
First as with every transaction you need to query for the UTxO (substitute --mainnet
for --testnet-magic 1097911063
if you are using testnet):
cardano-cli query utxo \
--address $(cat payment.addr) \
--mainnet
Example return:
TxHash TxIx Amount
--------------------------------------------------------------------------------------
1626d02eac494bfbbf0b725ad617fca9fa9b6c9f5b6dcd5e62aa1f9cc3efe731 0 995124450 lovelace + TxOutDatumHashNone
Now we can draft the transaction:
cardano-cli transaction build-raw \
--alonzo-era \
--tx-in "1626d02eac494bfbbf0b725ad617fca9fa9b6c9f5b6dcd5e62aa1f9cc3efe731#0" \
--tx-out "$(cat payment.addr)"+0+"1000000 $(cat policyId).$TOKEN_NAME" \
--mint "1000000 $(cat policyId).$TOKEN_NAME" \
--minting-script-file policy.script \
--fee 0 \
--out-file tx.draft \
--cddl-format
Note: if you don't have a protocol.json
file available you can obtain it with cardano-cli
:
cardano-cli query protocol-parameters \
--out-file protocol.json \
--mainnet
Then we can calculate the fee (substitute --mainnet
for --testnet-magic 1097911063
if you are using testnet):
cardano-cli transaction calculate-min-fee \
--tx-body-file tx.draft \
--tx-in-count 1 \
--tx-out-count 1 \
--witness-count 3 \
--mainnet \
--protocol-params-file ./protocol.json
Example output:
200701 Lovelace
Note that our policy script is of type "all" and therefore requires to be signed with all the nested scripts. In this case it means it needs signatures from both the public keys, therefore we need 2 witnesses only for the policy script plus witnesses needed for the payment address (here we assumed the payment address is an ordinary one, but if it was multisig address it could require more signatures).
Then we can calculate the output amount (input minus the fee) and create the unsigned transaction.
expr 995124450 - 200701
cardano-cli transaction build-raw \
--alonzo-era \
--tx-in "1626d02eac494bfbbf0b725ad617fca9fa9b6c9f5b6dcd5e62aa1f9cc3efe731#0" \
--tx-out "$(cat payment.addr)"+994923749+"1000000 $(cat policyId).$TOKEN_NAME" \
--mint "1000000 $(cat policyId).$TOKEN_NAME" \
--minting-script-file policy.script \
--fee 200701 \
--out-file tx.raw \
--cddl-format
HW wallets expect the transaction CBOR to be in canonical format (see CIP-0021). Unfortunately, cardano-cli sometimes produces tx files not compliant with CIP-0021. Use the following command to fix the formatting issues.
cardano-hw-cli transaction transform \
--tx-file tx.raw \
--out-file tx.transformed
Now we can proceed to signing the transaction. Aside from the witness needed for the payment address, our transaction needs to be signed by the cardano-cli
generated key and hardware wallet generated one:
cardano-cli transaction witness \
--tx-body-file tx.transformed \
--signing-key-file mint-cli.skey \
--out-file mint-cli.witness \
--mainnet
cardano-hw-cli transaction witness \
--tx-file tx.transformed \
--hw-signing-file mint-hw.hwsfile \
--out-file mint-hw.witness \
--mainnet
We should now have two witness files available: mint-cli.witness
and mint-hw.witness
. Together with any other witnesses we can assemble them together into a signed transaction:
cardano-cli transaction assemble \
--tx-body-file tx.transformed \
--witness-file payment.witness \
--witness-file mint-hw.witness \
--witness-file mint-cli.witness \
--out-file tx.signed
The --witness-file payment.witness
is just an example, you might have more of other required witnesses or have them named differently.
We can now submit the signed transaction to the blockchain:
cardano-cli transaction submit \
--tx-file tx.signed \
--mainnet