examples for using the cardano-api library
Instead of using signing/verifcation keys to create a stake address, we can also use a plutus script and hash it to get a stake address.
In PPP0310 we have such a script which is parameterised by an address and which we can serialise, convert to its cardano-api type and write to a file (as seen almost identically in other examples).
writeStakeValidator :: FilePath -> Plutus.Address -> IO (Either (FileError ()) ())
writeStakeValidator file = writeFileTextEnvelope @(PlutusScript PlutusScriptV1) file Nothing . PlutusScriptSerialised . SBS.toShort . LBS.toStrict . serialise . Plutus.getStakeValidator . stakeValidator
where stakeValidator
is of type Address -> ValidatorScript
.
The logic of the script is explained in the lecture, basically it just demands that in order to withdraw the rewards, half of the withdrawal has to go to a specified address (the one used as argument for the typed script)
So, once the cardano-cli has the script, using the cardano-api it can produce a StakeCredential
with value StakeCredentialByScript (ScriptHash sh)
, where sh
is the script hash obtained with scriptHash
.
finally, the cardano-api provides the makeStakeAddress
function to build the address.
makeStakeAddress :: NetworkId -> StakeCredential -> StakeAddress
makeStakeAddress nw sc =
StakeAddress
(toShelleyNetwork nw)
(toShelleyStakeCredential sc)
the corresponding cardano-cli command is cardano-cli stake-address build
.
The stake address itself will only be used for withdrawals. To register and delegate the address to a pool, we only need the underlying StakeCredential
.
the cardano-cli offers the commands stake-address registration-certificate
and stake-address delegation-certificate
, both taking the script as argument. For the delegation we additionally need a PoolId
which in the example is obtained with the query stake-pools
command (there is only one pool on the testnet).
As shown below, in the cardano-api such a certificates are just wrapped StakeCredential
s (plus the poolId info in case of the delegation).
data Certificate =
StakeAddressRegistrationCertificate StakeCredential
| StakeAddressDeregistrationCertificate StakeCredential
| StakeAddressDelegationCertificate StakeCredential PoolId
...
having the required certificates ($registration
and $delegation
below), we can build the transaction to register and delegate onchain.
registration does not require witnessing, but delegation (and also deregistration) does, so we need the --certificate-script-file
and --certificate-redeemer-file
to build the script witness.
cardano-cli transaction build \
--testnet-magic 42 \
--change-address $(cat $script_payment_addr) \
--out-file $raw \
--tx-in $txin \
--tx-in-collateral $txin \
--certificate-file $registration \
--certificate-file $delegation \
--certificate-script-file $script \
--certificate-redeemer-file unit.json \
--protocol-params-file $pp
the cardano-cli converts the info above into the cardano-api TxCertificates
type, needed for the txCertificates
field of the transaction body content.
data TxCertificates build era where
TxCertificatesNone :: TxCertificates build era
TxCertificates :: CertificatesSupportedInEra era
-> [Certificate]
-> BuildTxWith build
(Map StakeCredential (Witness WitCtxStake era))
-> TxCertificates build era
The [Certificate]
list in this example contains both the registration and delegation certificate. And, as delegation requires witnessing, the Map StakeCredential (Witness WitCtxStake era)
above contains our stake credential mapped with the appropriate witness, which is a ScriptWitness ScriptWitnessForStakeAddr sWit
, where the sWit
is a PlutusScriptWitness
containing the usual information (such as the script itself and the redeemer).
The execution units for the stake script will, as in the other examples, again be calculated and subsituted into the witness by makeTransactionBodyAutoBalance
.
Finally the transaction has to be signed with the signing key associated with the --tx-in
value (which is not only needed for the transaction fees, but also for the registraion deposit).
the --change-address
given above is a key address, which not only has a payment component (as seen in the previous examples), but also a staking component which is of course our staking script.
so through the transaction above, this address got already funded and delegates theses funds to the pool with the poolId defined in the delegation certificate.
Building an address with a staking component can be done by adding the --stake-script-file
argument to the cardano-cli address build
command.
The only change under the hood is that the argument of type StakeAddressReference
for the cardano-api makeShelleyAddress
function is now a StakeAddressByValue stakeCredential
instead of a NoStakeAddress
So, once the stake address has accumulated rewards, they can be withdrawn with the following transaction:
cardano-cli transaction build \
--testnet-magic 42 \
--change-address $(cat tmp/user1-script.addr) \
--out-file $raw \
--tx-in $txin \
--tx-in-collateral $txin \
--tx-out "$(cat tmp/user2.addr)+$amt2 lovelace" \
--withdrawal "$(cat tmp/user1-script-stake.addr)+$amt1" \
--withdrawal-script-file tmp/stake-validator.script \
--withdrawal-redeemer-file unit.json \
--protocol-params-file $pp
The input specific to withdrawing rewards for the cardano-cli function runTxBuild
is of type:
[(StakeAddress, Lovelace, Maybe (ScriptWitness WitCtxStake era))]
All the information necessery to build a value of this type is there; the protocol parameters, script-file and redeemer-file for the ScriptWitness
; the $amt1
for the Lovelace
and the user1-script-stake.addr
for the StakeAddress
.
Additionally, the Just scriptWitness
value must be converted into the witness type Witness WitCtxStake era
needed for the txWithdrawals
field of the transaction body content.
TxWithdrawals :: WithdrawalsSupportedInEra era
-> [(StakeAddress, Lovelace,
BuildTxWith build (Witness WitCtxStake era))]
-> TxWithdrawals build era
this conversion is done with the cardano-cli function validateTxWithdrawals
. What this function is doing is basically transforming Nothing
s into key witnesses (tagged with KeyWitnessForStakeAddr
) and Just
s into script witnesses (tagged with ScriptWitnessForStakeAddr
), both of cardano-api type Witness
.
So, having built the value for the txWithdrawals
field of the transaction body content, the cardano-cli again uses the makeTransactionBodyAutoBalance
function, as seen in the other examples, to balance the transaction.
Also as seen before, this function calculates the execution units, this time of the stake validator script, and replaces the default value of 0 execution units in the script witness with the new value. Then, after calculating the fee and the change going to the change address, the transaction body is built by applying the final transaction body content to createAndValidateTransactionBody
Note: The --tx-out
in above transaction is only specified because we need this payment to satisfy the conditions declared in the stake validator script.
signing and submitting the transaction then is nothing special, we just need to provide the signing key to spend the --tx-in
and the --collateral
, which in this case are the same input.
#!/bin/bash
txin=$1
amt1=$(scripts/query-stake-address-info-user1-script.sh | jq .[0].rewardAccountBalance)
amt2=$(expr $amt1 / 2 + 1)
pp=tmp/protocol-params.json
raw=tmp/tx.raw
signed=tmp/tx.signed
echo "txin = $1"
echo "amt1 = $amt1"
echo "amt2 = $amt2"
export CARDANO_NODE_SOCKET_PATH=cardano-private-testnet-setup/private-testnet/node-bft1/node.sock
cardano-cli query protocol-parameters \
--testnet-magic 42 \
--out-file $pp
cardano-cli transaction build \
--testnet-magic 42 \
--change-address $(cat tmp/user1-script.addr) \
--out-file $raw \
--tx-in $txin \
--tx-in-collateral $txin \
--tx-out "$(cat tmp/user2.addr)+$amt2 lovelace" \
--withdrawal "$(cat tmp/user1-script-stake.addr)+$amt1" \
--withdrawal-script-file tmp/stake-validator.script \
--withdrawal-redeemer-file unit.json \
--protocol-params-file $pp
cardano-cli transaction sign \
--testnet-magic 42 \
--tx-body-file $raw \
--out-file $signed \
--signing-key-file cardano-private-testnet-setup/private-testnet/addresses/user1.skey
cardano-cli transaction submit \
--testnet-magic 42 \
--tx-file $signed