Skip to content

Instantly share code, notes, and snippets.

@feamcor
Last active August 9, 2023 20:23
Show Gist options
  • Save feamcor/7ad31b57ee386d233f39cfff6ba8f8e4 to your computer and use it in GitHub Desktop.
Save feamcor/7ad31b57ee386d233f39cfff6ba8f8e4 to your computer and use it in GitHub Desktop.
Script for common actions on Cardano node and CLI.
#!/usr/bin/env bash
#set -x
export CARDANO_HOME="${CARDANO_HOME:-${HOME}/cardano}"
if [ ! -d "${CARDANO_HOME}" ]; then
printf 'ERROR :: envar CARDANO_HOME directory does not exist: %s' "${CARDANO_HOME}"
exit 1
fi
export CARDANO_NODE_HOME="${CARDANO_NODE_HOME:-${CARDANO_HOME}/cardano-node}"
if [ ! -d "${CARDANO_NODE_HOME}" ]; then
printf 'ERROR :: envar CARDANO_NODE_HOME directory does not exist: %s' "${CARDANO_NODE_HOME}"
exit 1
fi
networks=(TESTNET PREVIEW PREPROD MAINNET)
export CARDANO_NETWORK="${CARDANO_NETWORK:-PREVIEW}"
if [[ ! " ${networks[*]} " =~ " ${CARDANO_NETWORK} " ]]; then
printf 'ERROR :: invalid CARDANO_NETWORK: %s' "${CARDANO_NETWORK}"
exit 1
fi
eras=('--byron-era' '--shelley-era' '--allegra-era' '--mary-era' '--alonzo-era' '--babbage-era')
export CARDANO_ERA="${CARDANO_ERA:---babbage-era}"
if [[ ! " ${eras[*]} " =~ " ${CARDANO_ERA} " ]]; then
printf 'ERROR :: invalid CARDANO_ERA: %s' "${CARDANO_ERA}"
exit 1
fi
declare -A configuration
configuration['TESTNET']="${CARDANO_HOME}/config/testnet"
configuration['PREVIEW']="${CARDANO_HOME}/config/preview"
configuration['PREPROD']="${CARDANO_HOME}/config/preprod"
configuration['MAINNET']="${CARDANO_HOME}/config/mainnet"
for directory in "${configuration[@]}"; do
if [ ! -d "${directory}" ]; then
printf 'ERROR :: config directory does not exist: %s' "${directory}"
exit 1
fi
done
declare -A database
database['TESTNET']="${CARDANO_HOME}/db/testnet"
database['PREVIEW']="${CARDANO_HOME}/db/preview"
database['PREPROD']="${CARDANO_HOME}/db/preprod"
database['MAINNET']="${CARDANO_HOME}/db/mainnet"
for directory in "${database[@]}"; do
if [ ! -d "${directory}" ]; then
\mkdir -p "${directory}"
printf 'WARNING :: created database directory: %s' "${directory}"
fi
done
declare -A magic
magic['TESTNET']='--testnet-magic=1097911063'
magic['PREVIEW']='--testnet-magic=2'
magic['PREPROD']='--testnet-magic=1'
magic['MAINNET']='--mainnet'
declare -A socket
socket['TESTNET']="${database[TESTNET]}/node.socket"
socket['PREVIEW']="${database[PREVIEW]}/node.socket"
socket['PREPROD']="${database[PREPROD]}/node.socket"
socket['MAINNET']="${database[MAINNET]}/node.socket"
declare -A port
port['TESTNET']='3333'
port['PREVIEW']='3002'
port['PREPROD']='3001'
port['MAINNET']='3000'
declare -A blockfrost
blockfrost['TESTNET']='https://cardano-testnet.blockfrost.io/api/v0'
blockfrost['PREVIEW']='https://cardano-preview.blockfrost.io/api/v0' # Endpoint not available yet
blockfrost['PREPROD']='https://cardano-preprod.blockfrost.io/api/v0' # Endpoint not available yet
blockfrost['MAINNET']='https://cardano-mainnet.blockfrost.io/api/v0'
if [ -z "${BLOCKFROST}" ]; then
printf 'ERROR :: envar BLOCKFROST is empty or unset.'
exit 1
fi
export CARDANO_CONFIG="${configuration[${CARDANO_NETWORK}]}"
export CARDANO_DB="${database[${CARDANO_NETWORK}]}"
export CARDANO_MAGIC="${magic[${CARDANO_NETWORK}]}"
export CARDANO_NODE_SOCKET_PATH="${socket[${CARDANO_NETWORK}]}"
export CARDANO_NODE_PORT="${port[${CARDANO_NETWORK}]}"
export BLOCKFROST_ENDPOINT="${blockfrost[${CARDANO_NETWORK}]}"
if [ -z "$(cardano-node --version)" ]; then
printf 'ERROR :: cardano-node is not installed or in the PATH.'
exit 1
fi
if [ -z "$(cardano-cli --version)" ]; then
printf 'ERROR :: cardano-cli is not installed or in the PATH.'
exit 1
fi
if [ -z "$(jq --version)" ]; then
printf 'ERROR :: jq is not installed or in the PATH.'
exit 1
fi
if [ -z "$(cbor-diag --version)" ]; then
printf 'ERROR :: cbor-diag is not installed or in the PATH.'
exit 1
fi
if [ -z "$(pgrep --version)" ]; then
printf 'ERROR :: pgrep is not installed or in the PATH.'
exit 1
fi
if [ -z "$(curl --version)" ]; then
printf 'ERROR :: curl is not installed or in the PATH.'
exit 1
fi
functions=(help symlink cbor execute run runquiet gettip getparams stop genpaymentkeypair genstakingkeypair genpaymentaddr getpaymentaddrhash genstakingaddr getutxo getallutxo buildtx signtx submittx witnesstx assembletx calctxfee gettxid viewrawtx viewsignedtx gettxmeta)
me=$(basename "$0")
function execute {
"$@"
}
function help {
cat <<__EOF__
${me} COMMAND [OPTIONS ...]
DEFAULTS
Network ${CARDANO_NETWORK}
Era ${CARDANO_ERA}
Protocol Magic ${CARDANO_MAGIC}
Configuration ${CARDANO_CONFIG}
Database ${CARDANO_DB}
Node Socket ${CARDANO_NODE_SOCKET_PATH}
Node Port ${CARDANO_NODE_PORT}
Node Version $(cardano-node --version | head -1)
CLI Version $(cardano-cli --version | head -1)
GENERAL
help show list of commands and options
symlink (re-)create symlinks to cardano-(node|cli) executables at ~/.local/bin directory
cbor FILE [PROPERTY] retrieve CBOR PROPERTY (default is .cborHex) value from JSON FILE, decode and print it out
NODE
run run node of Cardano on foreground
runquiet run node of Cardano on background (see ~/nohup.out)
gettip return tip (sync status) of the local Cardano node
getparams get current protocol params and store it on network.params.json
stop stop running Cardano node, no matter the network
ADDRESS
genpaymentkeypair [PREFIX] generate payment key pair named based on PREFIX,
otherwise named based on current Unix epoch
genstakingkeypair [PREFIX] generate staking key pair named based on PREFIX,
otherwise named based on current Unix epoch
genpaymentaddr PAYMENT [STAKING|NONE] generate a payment address out of PAYMENT (prefix)
and STAKING (prefix, unless if NONE) verification keys
gentstakingaddr STAKING generate a staking address out of STAKING (prefix)
verification key
ADDRESS DETAIL
getpaymentaddrhash PREFIX return hash of a payment verification key (prefix)
getutxo FILE return UTXOs of a Shelley payment address FILE
getallutxo return UTXOs of all Shelley payment addresses found in the current directory
TRANSACTION
buildtx SHELLEY [OPTIONS ...] build a transaction. SHELLEY (file) is the change payment address
Transaction inputs, outputs and witnesses must be provided
signtx PAYMENT TRANSACTION sign TRANSACTION (prefix) using PAYMENT signing key (prefix)
submittx TRANSACTION submit signed TRANSACTION (prefix) to the testnet
witnesstx PAYMENT TRANSACTION sign TRANSACTION (prefix) by a witness signing key (prefix)
assembletx TRANSACTION assembles and sign a TRANSACTION (prefix) based on its witnesses
calctxfee TRANSACTION #I #O #W calculate the minimum fee for a TRANSACTION (prefix) according to
number of inputs (#I), outputs (#O) and witnesses (#W)
TRANSACTION DETAIL
gettxid TRANSACTION get the id of a submitted TRANSACTION (prefix)
viewrawtx TRANSACTION show details of a raw TRANSACTION (prefix)
viewsignedtx TRANSACTION show details of a signed TRANSACTION (prefix)
gettxmeta TRANSACTION get TRANSACTION (prefix) metadata using Blockfrost.io API
__EOF__
}
function symlink {
cd "${CARDANO_NODE_HOME}" || exit 1
mkdir -p ~/.local/bin
rm -f ~/.local/bin/cardano-node
rm -f ~/.local/bin/cardano-cli
ln -s "$(./scripts/bin-path.sh cardano-node)" ~/.local/bin/cardano-node
ln -s "$(./scripts/bin-path.sh cardano-cli)" ~/.local/bin/cardano-cli
cardano-node --version
cardano-cli --version
cd "${OLDPWD}" || exit 1
}
function cbor {
local p1 p2
p1="${1:?'ERROR :: missing file name'}"
shift
p2="${1:-.cborHex}"
shift
jq -c -r -M "${p2}" "${p1}" | cbor-diag --from=hex --to=diag
}
function check_if_node_is_running {
if pgrep --list-full cardano-node | grep -q "${CARDANO_DB}"; then
printf 'ERROR :: cardano-node for %s network is already running.\n' "${CARDANO_NETWORK}"
exit 1
fi
}
function check_if_node_is_not_running {
if pgrep --list-full cardano-node | grep -q "${CARDANO_DB}"; then
:
else
printf 'ERROR :: cardano-node for %s network is not running.\n' "${CARDANO_NETWORK}"
exit 1
fi
}
function run {
check_if_node_is_running
cardano-node run \
--topology="${CARDANO_CONFIG}/topology.json" \
--database-path="${CARDANO_DB}" \
--socket-path="${CARDANO_NODE_SOCKET_PATH}" \
--port="${CARDANO_NODE_PORT}" \
--config="${CARDANO_CONFIG}/config.json" \
"$@"
}
function runquiet {
check_if_node_is_running
cd "${HOME}" || exit 1
nohup cardano-node run \
--topology="${CARDANO_CONFIG}/topology.json" \
--database-path="${CARDANO_DB}" \
--socket-path="${CARDANO_NODE_SOCKET_PATH}" \
--port="${CARDANO_NODE_PORT}" \
--config="${CARDANO_CONFIG}/config.json" \
"$@" &
cd "${OLDPWD}" || exit 1
}
function gettip {
check_if_node_is_not_running
cardano-cli query tip \
"${CARDANO_MAGIC}" \
"$@"
}
function getparams {
cardano-cli query protocol-parameters \
"${CARDANO_MAGIC}" \
--out-file=network.params.json
}
function stop {
local pid
pid="$(pgrep --list-full cardano-node | grep "${CARDANO_DB}" | cut -d ' ' -f 1)"
[ -n "${pid}" ] && kill "${pid}" && printf 'INFO :: killed cardano-node with pid %s\n' "${pid}"
}
function genpaymentkeypair {
local p1
p1="${1:-addr$(date '+%s')}"
shift
cardano-cli address key-gen \
--verification-key-file="${p1}.payment.vk.json" \
--signing-key-file="${p1}.payment.sk.json" \
"$@"
\ls -1 "${p1}".payment.*.json
}
function genstakingkeypair {
local p1
p1="${1:-addr$(date '+%s')}"
shift
cardano-cli stake-address key-gen \
--verification-key-file="${p1}.staking.vk.json" \
--signing-key-file="${p1}.staking.sk.json" \
"$@"
\ls -1 "${p1}".staking.*.json
}
function genpaymentaddr {
local p1 p2 p3
p1="${1:?'ERROR :: missing payment verification key file prefix'}"
shift
p2="${1:-${p1}}"
shift
if [ "${p2}" == "NONE" ]; then
p2='payment'
p3=()
else
p3=("--stake-verification-key-file" "${p2}".staking.vk.json)
fi
cardano-cli address build \
"${CARDANO_MAGIC}" \
--payment-verification-key-file="${p1}".payment.vk.json \
${p3[@]} \
--out-file="${p1}.${p2}".shelley \
"$@"
\ls -1 "${p1}.${p2}".shelley
}
function getpaymentaddrhash {
local p1
p1="${1:?'ERROR :: missing payment verification key file prefix'}"
shift
cardano-cli address key-hash \
--payment-verification-key-file="${p1}".payment.vk.json \
"$@"
}
function genstakingaddr {
local p1
p1="${1:?'ERROR :: missing staking verification key file prefix'}"
shift
cardano-cli stake-address build \
"${CARDANO_MAGIC}" \
--staking-verification-key-file="${p1}".staking.vk.json \
--out-file="${p1}".staking.shelley \
"$@"
\ls -1 "${p1}".staking.shelley
}
function getutxo {
local p1
p1="${1:?'ERROR :: missing Shelley payment address file name'}"
shift
cardano-cli query utxo \
"${CARDANO_MAGIC}" \
--address="$(cat "${p1}")" \
"$@"
}
function getallutxo {
local p1
for p1 in *.shelley; do
if [[ "${p1}" != *"staking"* ]]; then
printf '>>> %s\n' "${p1}"
getutxo "${p1}"
echo
fi
done
}
function buildtx {
local p1 id
p1="${1:?'ERROR :: missing change (Shelley) address file name'}"
shift
id="tx$(date '+%s')"
cardano-cli transaction build \
"${CARDANO_MAGIC}" \
"${CARDANO_ERA}" \
--out-file="${id}.raw.json" \
--change-address="$(cat "${p1}")" \
"$@"
printf '%s' "${id}"
}
function signtx {
local p1 p2
p1="${1:?'ERROR :: missing payment address file prefix'}"
shift
p2="${1:?'ERROR :: missing raw transaction file prefix'}"
shift
cardano-cli transaction sign \
"${CARDANO_MAGIC}" \
--signing-key-file="${p1}".payment.sk.json \
--tx-body-file="${p2}".raw.json \
--out-file="${p2}".signed.json \
"$@"
}
function submittx {
local p1
p1="${1:?'ERROR :: missing signed transaction file prefix'}"
shift
cardano-cli transaction submit \
"${CARDANO_MAGIC}" \
--tx-file="${p1}".signed.json \
"$@"
gettxid "${p1}"
}
function witnesstx {
local p1 p2
p1="${1:?'ERROR :: missing payment address file prefix'}"
shift
p2="${1:?'ERROR :: missing raw transaction file prefix'}"
shift
cardano-cli transaction witness \
"${CARDANO_MAGIC}" \
--signing-key-file="${p1}".payment.sk.json \
--tx-body-file="${p2}".raw.json \
--out-file="${p2}".witness."${p1}".json \
"$@"
}
function assembletx {
local p1 args w
p1="${1:?'ERROR :: missing raw transaction file prefix'}"
shift
args=""
for w in "${p1}".witness.*.json; do
args+='--witness-file='${w}' '
done
cardano-cli transaction assemble \
--tx-body-file="${p1}".raw.json \
--out-file="${p1}".signed.json \
${args} \
"$@"
cardanogettxid "${p1}"
}
function calctxfee {
local p1 p2 p3 p4
p1="${1:?'ERROR :: missing raw transaction file prefix'}"
shift
p2="${1:?'ERROR :: missing # of transaction input UTXOs'}"
shift
p3="${1:?'ERROR :: missing # of transaction output UTXOs'}"
shift
p4="${1:?'ERROR :: missing # of transaction witnesses'}"
shift
gettestnetparams
cardano-cli transaction calculate-min-fee \
"${CARDANO_MAGIC}" \
--protocol-params-file=network.params.json \
--tx-body-file="${p1}".raw.json \
--tx-in-count="${p2}" \
--tx-out-count="${p3}" \
--witness-count="${p4}" \
"$@"
}
function gettxid {
local p1
p1="${1:?'ERROR :: missing signed transaction file prefix'}"
shift
cardano-cli transaction txid --tx-file="${p1}".signed.json "$@"
}
function viewrawtx {
local p1
p1="${1:?'ERROR :: missing raw transaction file prefix'}"
shift
cardano-cli transaction view --tx-body-file="${p1}".raw.json "$@"
}
function viewsignedtx {
local p1
p1="${1:?'ERROR :: missing signed transaction file prefix'}"
shift
cardano-cli transaction view --tx-file="${p1}".signed.json "$@"
}
function gettxmeta {
local p1
p1="${1:?'ERROR :: missing submitted transaction file prefix'}"
shift
curl -s -H "project_id: ${BLOCKFROST}" "${BLOCKFROST_ENDPOINT}/txs/$(gettxid "${p1}")/metadata" | jq
}
### SCRIPT FUNCTION HANDLING
if [ $# -eq 0 ]; then
printf 'ERROR :: missing command\n\n'
help
exit 1
fi
action=$1
shift 1
if [[ "${action}" == "--" ]]; then
action=execute
fi
if [[ ! " ${functions[*]} " =~ " ${action} " ]]; then
printf 'ERROR :: invalid command %s\n\n' "$1"
help
exit 1
fi
${action} "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment