Last active
August 9, 2023 20:23
-
-
Save feamcor/7ad31b57ee386d233f39cfff6ba8f8e4 to your computer and use it in GitHub Desktop.
Script for common actions on Cardano node and CLI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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