diff --git a/docs/Scripts/cntools-changelog.md b/docs/Scripts/cntools-changelog.md index c09365cb8..014949197 100644 --- a/docs/Scripts/cntools-changelog.md +++ b/docs/Scripts/cntools-changelog.md @@ -6,6 +6,15 @@ All notable changes to this tool will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [13.0.0] - 2024-01-19 +#### Added +- New light mode using Koios API instead of local node for blockchain queries. Mode can be selected at runtime using -l (light), -n (node, default) or -o (offline). +#### Changed +- Improved submission handling +- Removed complicated file descriptor re-direction for logging. This simplified the code and improves screen printing speed as special hacks around FDs could be removed. +#### Fixed +- Update handling and reload of script. + ## [12.1.0] - 2024-01-19 #### Changed - ADA price discovery through CoinGecko in set currency incl 24hr change. Shown in main UI, wallet list & show as well as pool show. Disabled by default. diff --git a/scripts/cnode-helper-scripts/cntools.library b/scripts/cnode-helper-scripts/cntools.library index f369c12e9..a21a82285 100644 --- a/scripts/cnode-helper-scripts/cntools.library +++ b/scripts/cnode-helper-scripts/cntools.library @@ -11,9 +11,9 @@ # The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) # and this adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) # Major: Any considerable change in the code base, big feature, workflow or breaking change from previous version -CNTOOLS_MAJOR_VERSION=12 +CNTOOLS_MAJOR_VERSION=13 # Minor: Changes and features of minor character that can be applied without breaking existing functionality or workflow -CNTOOLS_MINOR_VERSION=1 +CNTOOLS_MINOR_VERSION=0 # Patch: Backwards compatible bug fixes. No additional functionality or major changes CNTOOLS_PATCH_VERSION=0 @@ -28,6 +28,18 @@ if ! mkdir -p "${TMP_DIR}" 2>/dev/null; then myExit 1 "${FG_RED}ERROR${NC}: Fail if ! mkdir -p "${WALLET_FOLDER}" 2>/dev/null; then myExit 1 "${FG_RED}ERROR${NC}: Failed to create wallet directory: ${WALLET_FOLDER}"; fi if ! mkdir -p "${POOL_FOLDER}" 2>/dev/null; then myExit 1 "${FG_RED}ERROR${NC}: Failed to create pool directory: ${POOL_FOLDER}"; fi if ! mkdir -p "${ASSET_FOLDER}" 2>/dev/null; then myExit 1 "${FG_RED}ERROR${NC}: Failed to create asset directory: ${POOL_ASSET}"; fi +[[ -z ${CNTOOLS_MODE} ]] && CNTOOLS_MODE=LOCAL || CNTOOLS_MODE=${CNTOOLS_MODE^^} +if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + CNTOOLS_MODE_COLOR="${FG_BLUE}" +elif [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + CNTOOLS_MODE_COLOR="${FG_GREEN}" +else + CNTOOLS_MODE_COLOR="${FG_GRAY}" +fi +printf -v launch_modes_info "${FG_BLUE}INFO${NC}: Available launch modes, re-run CNTools in + ${CNTOOLS_MODE_COLOR}LOCAL${NC} mode (-n): Default mode with a local node connection. + ${CNTOOLS_MODE_COLOR}LIGHT${NC} mode (-l): Utilizing Koios query layer with full functionallity for supported networks. + ${CNTOOLS_MODE_COLOR}OFFLINE${NC} mode (-o): A limited set of functionallity without external communication useful for air-gapped mode." [[ -z ${TIMEOUT_NO_OF_SLOTS} ]] && TIMEOUT_NO_OF_SLOTS=600 [[ -z ${TX_TTL} ]] && TX_TTL=3600 [[ -z ${WALLET_SELECTION_FILTER_LIMIT} ]] && WALLET_SELECTION_FILTER_LIMIT=10 @@ -73,22 +85,18 @@ logln() { # : newline > Add a newline at the end for tty output (default true) # : message > The message to print/log println() { - sleep 0.05 # hack, sleep 50ms before each print to preserve order of execution local log_level=$1 shift local newline="\n" - if [[ $1 =~ true|false ]]; then - [[ $1 = false ]] && newline="" - shift - fi + if [[ $1 = false && $# -gt 2 ]]; then unset newline; shift; elif [[ $1 = true && $# -gt 2 ]]; then shift; fi case $log_level in - OFF) printf "%b${newline}" "$@" >&6 ;; + OFF) printf "%b${newline}" "$@" ;; LOG) logln "DEBUG" "$@" ;; - DEBUG) printf "%b${newline}" "$@" >&6; logln "DEBUG" "$@" ;; - INFO) printf "%b${newline}" "$@" ;; + DEBUG) printf "%b${newline}" "$@"; logln "DEBUG" "$@" ;; + INFO) printf "%b${newline}" "$@"; logln "INFO" "$@" ;; ACTION) logln "ACTION" "$@" ;; - ERROR) printf "%b${newline}" "$@" >&2 ;; - *) printf "%b${newline}" "${log_level}"; [[ $# -ge 1 ]] && printf "%b${newline}" "$@" ;; + ERROR) printf "%b${newline}" "$@"; logln "ERROR" "$@" ;; + *) println INFO "${log_level}" "$@" ;; esac } @@ -99,7 +107,6 @@ println() { # : log > [true|false] log question (default: true) # : question > what to ask user to input getAnswerAnyCust() { - sleep 0.1 # hack, sleep 100ms before asking question to preserve order var_name=$1 shift local log_question=true @@ -107,7 +114,7 @@ getAnswerAnyCust() { [[ $1 = false ]] && log_question=false shift fi - getAnswerAny "${var_name}" "$*" >&6 + getAnswerAny "${var_name}" "$*" [[ ${log_question} = true ]] && println LOG "$*: ${!var_name}" } @@ -122,15 +129,6 @@ archiveLog() { find "${log_archive}" -maxdepth 1 -type f -name "${log_file}*" -printf '%Ts\t%p\n' | sort -n | head -n -10 | cut -f 2- | xargs rm -rf } -# Command : waitForInput [optional: message] -# Description : wait for user keypress to continue -# Parameters : message > [optional]: override default 'press any key to proceed ..' message -waitForInput() { - exec >&6 # normal stdout - waitToProceed "$*" - exec >&8 # custom stdout -} - # Command : protectionPreRequisites # Description : Check if needed protection prerequisites is available, else print error protectionPreRequisites() { @@ -170,6 +168,19 @@ safeDel() { return 0 } +updateProtocolParams() { + _epoch_=$(getEpoch) + [[ -n ${current_epoch} && ${current_epoch} -eq ${_epoch_} ]] && return + current_epoch=${_epoch_} + getProtocolParams + case $? in + 1) myExit 1 "${FG_YELLOW}WARN${NC}: node socket path wrongly configured or node not running, please verify that socket set in env file match what is used to run the node\n\n${launch_modes_info}" ;; + 2) myExit 1 "${FG_YELLOW}WARN${NC}: failed to query protocol parameters, ensure your node is running with correct genesis (the node needs to be in sync to 1 epoch after the hardfork)\n\nError message: ${PROT_PARAMS}\n\n${launch_modes_info}" ;; + 3) myExit 1 "${FG_YELLOW}WARN${NC}: Unable to query Koios for current epoch parameters\n\n${launch_modes_info}" ;; + esac + echo "${PROT_PARAMS}" > "${TMP_DIR}"/protparams.json +} + # Command : dialogSetup # Description : set config parameters for dialog formatting dialogSetup() { @@ -240,16 +251,13 @@ fileDialog() { getAnswerAnyCust file "$1" && return else println DEBUG false "${1}: " - waitForInput "Press any key to open the file explorer [cancel to skip!]" + waitToProceed "Press any key to open the file explorer [cancel to skip!]" fi - exec >&6 2>&7 # normal stdout/stderr - sleep 0.1 dialogSetup [[ -n $2 ]] && start_path="$2" || start_path="${TMP_DIR}/" dialog --clear --keep-tite --title "$1" --fselect "${start_path}" $(($(tput lines)-14)) $(($(tput cols)-10)) 2>"${TMP_DIR}/dialog.out" file=$([[ -f "${TMP_DIR}/dialog.out" ]] && cat "${TMP_DIR}/dialog.out" || echo "") tput cup $(( ${ROW#*[} -1 )) $(( COL -1 )) - exec >&8 2>&9 # custom stdout/stderr println DEBUG "${FG_LGRAY}${file}${NC}" } # Command : dirDialog [title] [optional: start dir] @@ -262,16 +270,13 @@ dirDialog() { getAnswerAnyCust dir "$1" && return else println DEBUG false "${1}: " - waitForInput "Press any key to open the file explorer [cancel to skip!]" + waitToProceed "Press any key to open the file explorer [cancel to skip!]" fi - exec >&6 2>&7 # normal stdout/stderr - sleep 0.1 dialogSetup [[ -n $2 ]] && start_path="$2" || start_path="${TMP_DIR}/" dialog --clear --keep-tite --title "$1" --dselect "${start_path}" $(($(tput lines)-14)) $(($(tput cols)-10)) 2>"${TMP_DIR}/dialog.out" dir=$([[ -f "${TMP_DIR}/dialog.out" ]] && cat "${TMP_DIR}/dialog.out" || echo "") tput cup $(( ${ROW#*[} -1 )) $(( COL -1 )) - exec >&8 2>&9 # custom stdout/stderr println DEBUG "${FG_LGRAY}${dir}${NC}" } @@ -298,14 +303,20 @@ key_input() { key2="" else echo ${key1}; fi; } opt_shortcut() { [[ "$1" =~ ^\[([[:alnum:]]+)\].* ]] && echo ${BASH_REMATCH[1]}; } opt_firstchar() { printf "${1:0:1}" | tr '[:upper:]' '[:lower:]'; } +clrbuf() { read -r -t 0.1 -s -e; stty echo echok; } selectOption() { - sleep 0.1 # sleep for a short time to get tty in sync # initially print empty new lines (scroll down if at bottom of screen) printf "\n" && for opt; do printf "\n"; done - # determine current screen position for overwriting the options - local startrow=$(( $(get_cursor_row) - $# - 1 )) + # determine current screen position for overwriting the options or return -1 on failure + clrbuf + local startrow=-1 + for i in {1..10}; do + local cursor_row=$(get_cursor_row) + isNumber ${cursor_row} && startrow=$(( cursor_row - $# - 1 )) && break + done + [[ ${startrow} -eq -1 ]] && return 255 cursor_blink_off @@ -369,14 +380,16 @@ selectOption() { # Description : Helper function to selectOption # Parameters : optX > a list of available options to choose from select_opt() { - exec >&6 # normal stdout local opts=() for item in "$@"; do [[ -n ${item} ]] && opts+=("${item}") done selectOption "${opts[@]}" local answer=$? - exec >&8 # custom stdout + if [[ ${answer} -eq 255 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Failed to print menu, default selection used!\n" + return 0 + fi selected_value="${opts[${answer}]}" println DEBUG "Selected value: ${selected_value}" return $answer @@ -389,7 +402,7 @@ select_opt() { getDirs() { if [[ ! -d "$1" ]]; then println ERROR "${FG_RED}ERROR${NC}: Missing folder: $1" - waitForInput && return 1 + waitToProceed && return 1 fi dirs=() while IFS= read -r -d '' dir; do @@ -404,14 +417,17 @@ getDirs() { # : dirX > array of dirs to include in selection, '[Esc] Cancel' option added to all selections # Return : populates ${dir_name} variable selectDir() { - exec >&6 # normal stdout local type=$1 && shift dirs=( "$@" ) dirs+=("[Esc] Cancel") selectOption "${dirs[@]}" - dir_name=${dirs[$?]} - [[ "${dir_name}" = "[Esc] Cancel" ]] && return 1 - exec >&8 # custom stdout + local answer=$? + if [[ ${answer} -eq 255 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Failed to print menu, please try again or report issue!" + return 1 + fi + dir_name=${dirs[${answer}]} + [[ "${dir_name}" = "[Esc] Cancel" ]] && return 2 println DEBUG "Selected ${type}: ${dir_name}" } @@ -421,14 +437,40 @@ selectDir() { # : arg array > array of files required to exist in wallet folder for it to be selectable **OR** the name of wallet to exclude from selection # Return : populates ${wallet_name} variable with wallet selection selectWallet() { - wallet_dirs=() + mode=$1 && shift + + if [[ ${mode} = "cache" && ${#wallet_dirs_filtered[@]} -gt 0 ]]; then + selectDir "wallet" "${wallet_dirs_filtered[@]}" || return $? # ${dir_name} populated by selectDir function + wallet_name="$(echo ${dir_name} | cut -d' ' -f1)" + return 0 + fi + + wallet_dirs=() + if ! getDirs "${WALLET_FOLDER}"; then return 1; fi # dirs() array populated with all wallet folders - if [[ ${CNTOOLS_MODE} = "CONNECTED" && ${mode} != "none" ]]; then + if [[ ${CNTOOLS_MODE} != "OFFLINE" && ${mode} != "none" ]]; then tput sc wallet_count=${#dirs[@]} - [[ ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]] && println OFF "Balance checking wallets..." + if [[ ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]]; then + if [[ -n ${KOIOS_API} ]]; then + println OFF "${FG_YELLOW}> Querying Koios API for wallet balance${NC}" + else + println OFF "${FG_YELLOW}> Querying node for wallet balance${NC}" + fi + else + println OFF "${FG_YELLOW}> Max wallet count exceeded for balance/filtering (${wallet_count}/${WALLET_SELECTION_FILTER_LIMIT}).\nUpdate 'WALLET_SELECTION_FILTER_LIMIT' setting to increase this limit${NC}" + fi fi + + unset reward_status + addr_list=() + reward_addr_list=() + declare -gA asset_cnt=() + declare -gA balances=() + declare -gA reward_pool=() + declare -gA rewards_available=() + for dir in "${dirs[@]}"; do for arg in "$@"; do # check if wallet is missing a required file or name matches execution, if so hide it [[ ${arg} == *"."* && ! -f "${WALLET_FOLDER}/${dir}/${arg}" ]] && continue 2 @@ -441,35 +483,101 @@ selectWallet() { else wallet_dirs+=("${dir} (${FG_YELLOW}unprotected${NC})") fi - elif [[ ${CNTOOLS_MODE} = "CONNECTED" && ${mode} != "none" && ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - if [[ ${mode} = "non-reg" ]]; then - if isWalletRegistered ${dir}; then continue; fi - elif [[ ${mode} = "reg" ]]; then - if ! isWalletRegistered ${dir}; then continue; fi + elif [[ ${CNTOOLS_MODE} != "OFFLINE" && ${mode} != "none" && ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]]; then + if [[ ${mode} = "reg" || ${mode} = "non-reg" ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + if [[ ${mode} = "reg" ]]; then + ! isWalletRegistered ${dir} && continue + else + isWalletRegistered ${dir} && continue + fi + else + getRewardAddress ${dir} + [[ -n ${reward_addr} ]] && reward_addr_list+=(${reward_addr}) + fi fi if [[ ${mode} = "balance" || ${mode} = "non-reg" || ${mode} = "reg" ]]; then getBaseAddress ${dir} getPayAddress ${dir} [[ -z ${base_addr} || -z ${pay_addr} ]] && wallet_dirs+=("${dir}") && continue # ignore and add wallet without extra details - getBalance ${base_addr} - base_lovelace=${assets[lovelace]} - [[ ${#assets[@]} -gt 1 ]] && base_assets_out=" + ${FG_LBLUE}$((${#assets[@]}-1))${NC} additional assets" || base_assets_out="" - getBalance ${pay_addr} - pay_lovelace=${assets[lovelace]} - [[ ${#assets[@]} -gt 1 ]] && pay_assets_out=" + ${FG_LBLUE}$((${#assets[@]}-1))${NC} additional assets" || pay_assets_out="" - if [[ ${base_lovelace} -gt 0 && ${pay_lovelace} -gt 0 ]]; then - wallet_dirs+=("${dir} (Funds: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA${base_assets_out} | Enterprise Funds: ${FG_LBLUE}$(formatLovelace ${pay_lovelace})${NC} ADA${pay_assets_out})") - elif [[ ${pay_lovelace} -gt 0 ]]; then - wallet_dirs+=("${dir} (Enterprise Funds: ${FG_LBLUE}$(formatLovelace ${pay_lovelace})${NC} ADA${pay_assets_out})") - else - wallet_dirs+=("${dir} (Funds: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA${base_assets_out})") + addr_list+=(${base_addr} ${pay_addr}) + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + getBalance ${base_addr} + balances["${base_addr}"]=${assets[lovelace]} + [[ ${#assets[@]} -gt 1 ]] && asset_cnt["${base_addr}"]="$((${#assets[@]}-1))" + getBalance ${pay_addr} + balances["${pay_addr}"]=${assets[lovelace]} + [[ ${#assets[@]} -gt 1 ]] && asset_cnt["${pay_addr}"]="$((${#assets[@]}-1))" fi + wallet_dirs+=("${dir}_balance_") elif [[ ${mode} = "delegate" ]]; then getBaseAddress ${dir} [[ -z ${base_addr} ]] && wallet_dirs+=("${dir}") && continue # ignore and add wallet without extra details - getBalance ${base_addr} - if getRewardAddress ${dir}; then - delegation_pool_id=$(${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address "${reward_addr}" | jq -r '.[0].delegation // empty') + addr_list+=(${base_addr}) + getRewardAddress ${dir} + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + getBalance ${base_addr} + balances["${base_addr}"]=${assets[lovelace]} + if [[ -n ${reward_addr} ]]; then + delegation_pool_id=$(${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address "${reward_addr}" | jq -r '.[0].delegation // empty') + [[ -n ${delegation_pool_id} ]] && reward_pool[${reward_addr}]=${delegation_pool_id} + fi + else + [[ -n ${reward_addr} ]] && reward_addr_list+=(${reward_addr}) + fi + wallet_dirs+=("${dir}_balance_") + elif [[ ${mode} = "reward" ]]; then + if [[ -n ${KOIOS_API} ]]; then + getRewardAddress ${dir} + [[ -n ${reward_addr} ]] && reward_addr_list+=(${reward_addr}) + else + getWalletRewards ${dir} + [[ ${reward_lovelace} -eq 0 ]] && continue + rewards_available[${reward_addr}]=${reward_lovelace} + fi + wallet_dirs+=("${dir}_balance_") + fi + else + wallet_dirs+=("${dir}") + fi + done + + if [[ -n ${KOIOS_API} ]]; then + if [[ ${#addr_list[@]} -gt 0 ]]; then + getBalanceKoios false + for key in ${!assets[@]}; do + if [[ ${key} = *lovelace ]]; then + _address=${key%,*} + balances[${_address}]=${assets[${key}]} + fi + done + fi + [[ ${#reward_addr_list[@]} -gt 0 ]] && getRewardInfoKoios + fi + + wallet_dirs_filtered=() + + for dir in "${wallet_dirs[@]}"; do + if [[ ${dir} = *_balance_ ]]; then + wallet_dir=${dir%%_balance_} + unset base_addr + getBaseAddress ${wallet_dir} + base_lovelace=${balances[${base_addr}]:-0} + if [[ ${mode} = "reg" || ${mode} = "non-reg" ]]; then + getRewardAddress ${wallet_dir} + if [[ -n ${reward_addr} ]]; then + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + if [[ ${mode} = "reg" ]]; then + if [[ ! -v reward_status[${reward_addr}] || ${reward_status[${reward_addr}]} != "registered" ]]; then continue; fi + else + [[ -v reward_status[${reward_addr}] && ${reward_status[${reward_addr}]} = "registered" ]] && continue + fi + fi + fi + elif [[ ${mode} = "delegate" ]]; then + getRewardAddress ${wallet_dir} + if [[ -n ${reward_addr} ]]; then + delegation_pool_id=${reward_pool[${reward_addr}]} unset poolName if [[ -n ${delegation_pool_id} ]]; then while IFS= read -r -d '' pool; do @@ -480,38 +588,58 @@ selectWallet() { done < <(find "${POOL_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) fi if [[ -n ${poolName} ]]; then - wallet_dirs+=("${dir} (${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA - ${FG_RED}delegated${NC} to ${FG_GREEN}${poolName}${NC})") + wallet_dirs_filtered+=("${wallet_dir} (${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA - ${FG_RED}delegated${NC} to ${FG_GREEN}${poolName}${NC})") elif [[ -n ${delegation_pool_id} ]]; then - wallet_dirs+=("${dir} (${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA - ${FG_RED}delegated${NC} to external address)") + wallet_dirs_filtered+=("${wallet_dir} (${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA - ${FG_RED}delegated${NC} to ${FG_LGRAY}${delegation_pool_id:0:6}...${delegation_pool_id: -6}${NC})") else - wallet_dirs+=("${dir} (${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA)") + wallet_dirs_filtered+=("${wallet_dir} (${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA)") fi else - wallet_dirs+=("${dir} (${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA)") + wallet_dirs_filtered+=("${wallet_dir} (${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA)") fi + continue elif [[ ${mode} = "reward" ]]; then - getRewards ${dir} - [[ ${reward_lovelace} -le 0 ]] && continue - wallet_dirs+=("${dir} (Rewards: ${FG_LBLUE}$(formatLovelace ${reward_lovelace})${NC} ADA)") + getRewardAddress ${wallet_dir} + if [[ -n ${reward_addr} && -v rewards_available[${reward_addr}] && ${rewards_available[${reward_addr}]} -gt 0 ]]; then + wallet_dirs_filtered+=("${wallet_dir} (Rewards: ${FG_LBLUE}$(formatLovelace ${rewards_available[${reward_addr}]})${NC} ADA)") + fi + continue + fi + getPayAddress ${wallet_dir} + pay_lovelace=${balances[${pay_addr}]:-0} + [[ -v asset_cnt[${base_addr}] ]] && base_asset_str=" + ${FG_LBLUE}${asset_cnt[${base_addr}]}${NC} additional assets" || unset base_asset_str + [[ -v asset_cnt[${pay_addr}] ]] && pay_asset_str=" + ${FG_LBLUE}${asset_cnt[${pay_addr}]}${NC} additional assets" || unset pay_asset_str + if [[ ${base_lovelace} -gt 0 && ${pay_lovelace} -gt 0 ]]; then + wallet_dirs_filtered+=("${wallet_dir} (Funds: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA${base_asset_str} | Enterprise Funds: ${FG_LBLUE}$(formatLovelace ${pay_lovelace})${NC} ADA${pay_asset_str})") + elif [[ ${pay_lovelace} -gt 0 ]]; then + wallet_dirs_filtered+=("${wallet_dir} (Enterprise Funds: ${FG_LBLUE}$(formatLovelace ${pay_lovelace})${NC} ADA${pay_asset_str})") + else + wallet_dirs_filtered+=("${wallet_dir} (Funds: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA${base_asset_str})") fi else - wallet_dirs+=("${dir}") + wallet_dirs_filtered+=("${dir}") fi done - if [[ ${CNTOOLS_MODE} = "CONNECTED" && ${mode} != "none" ]]; then tput rc && tput ed; fi - if [[ ${#wallet_dirs[@]} -eq 0 ]]; then + + if [[ ${CNTOOLS_MODE} != "OFFLINE" && ${mode} != "none" && ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]]; then tput rc && tput ed; fi + if [[ ${#wallet_dirs_filtered[@]} -eq 0 ]]; then if [[ ${mode} = "balance" ]]; then - println ERROR "${FG_YELLOW}WARN${NC}: No wallets with funds available for selection! Required files:\n$(printf '%b\n' "$@")" + println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets with funds available for selection! Required files:\n$(printf '%b\n' "$@")" elif [[ ${mode} = "delegate" ]]; then - println ERROR "${FG_YELLOW}WARN${NC}: No wallets available that can be delegated or used as pool pledge/owner/reward wallet! Required files:\n$(printf '%b\n' "$@")" + println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available that can be delegated or used as pool pledge/owner/reward wallet! Required files:\n$(printf '%b\n' "$@")" elif [[ ${mode} = "reward" ]]; then - println ERROR "${FG_YELLOW}WARN${NC}: No wallets available that have rewards to withdraw!" + println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available that have rewards to withdraw or signing keys to do so!" + elif [[ ${mode} = "reg" ]]; then + println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available that are registered on chain!" + elif [[ ${mode} = "non-reg" ]]; then + println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available that are unregistered!" else - println ERROR "${FG_YELLOW}WARN${NC}: No wallets available for selection! Required files:\n$(printf '%b\n' "$@")" + println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available for selection! Required files:\n$(printf '%b\n' "$@")" fi return 1 fi - if ! selectDir "wallet" "${wallet_dirs[@]}"; then return 2; fi # ${dir_name} populated by selectDir function + + selectDir "wallet" "${wallet_dirs_filtered[@]}" || return $? # ${dir_name} populated by selectDir function wallet_name="$(echo ${dir_name} | cut -d' ' -f1)" } @@ -556,7 +684,7 @@ selectPool() { return 1 fi [[ ${enc_req_files} -gt 0 ]] && println DEBUG "${FG_YELLOW}encrypted pools found but NOT listed, please decrypt to show${NC}" - if ! selectDir "pool" "${pool_dirs[@]}"; then return 2; fi # ${dir_name} populated by selectDir function + selectDir "pool" "${pool_dirs[@]}" || return $? # ${dir_name} populated by selectDir function pool_name="$(echo ${dir_name} | cut -d' ' -f1)" } @@ -573,7 +701,7 @@ isPoolRegistered() { unset error_msg pool_info pool_info_tsv pool_info_arr unset p_active_epoch_no p_vrf_key_hash p_margin p_fixed_cost p_pledge p_reward_addr p_owners p_relays p_meta_url p_meta_hash p_meta_json p_pool_status unset p_retiring_epoch p_op_cert p_op_cert_counter p_active_stake p_epoch_block_cnt p_live_stake p_live_delegators p_live_saturation - if [[ ${CNTOOLS_MODE} = "OFFLINE" || -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} != "LIGHT" ]]; then [[ -f "${POOL_FOLDER}/${1}/${POOL_REGCERT_FILENAME}" ]] && return 2 || return 1 else getPoolID "$1" @@ -642,7 +770,7 @@ isPoolRegistered() { # 2: offline/disabled/no result getAssetInfo() { unset - if [[ ${CNTOOLS_MODE} = "OFFLINE" || -z ${KOIOS_API} || $# -ge 1 ]]; then + if [[ ${CNTOOLS_MODE} != "LIGHT" || $# -lt 1 ]]; then return 2 else println ACTION "curl -sSL -f -d _asset_policy=$1 -d _asset_name=$2 ${KOIOS_API}/asset_info" @@ -707,7 +835,7 @@ selectPolicy() { return 1 fi [[ ${enc_req_files} -gt 0 ]] && println DEBUG "${FG_YELLOW}encrypted policies found but NOT listed, please decrypt to show${NC}" - if ! selectDir "policy" "${policy_dirs[@]}"; then return 2; fi # ${dir_name} populated by selectDir function + selectDir "policy" "${policy_dirs[@]}" || return $? # ${dir_name} populated by selectDir function policy_name="$(echo ${dir_name} | cut -d' ' -f1)" } @@ -729,7 +857,7 @@ selectAsset() { println ERROR "${FG_YELLOW}WARN${NC}: No assets found on disk!" return 1 fi - if ! selectDir "asset" "${asset_list[@]}"; then return 2; fi # ${dir_name} populated by selected value + selectDir "asset" "${asset_list[@]}" || return $? # ${dir_name} populated by selected value policy_dir="${dir_name%%/*}" asset_name="${dir_name##*/}" asset_file="${ASSET_FOLDER}/${policy_dir}/${asset_name}.asset" @@ -740,11 +868,8 @@ selectAsset() { # Parameters : confirm > [optional] force user to provide password twice for confirmation # Return : populates ${password} variable, make sure to unset variable when done getPasswordCust() { - exec >&6 2>&7 # normal stdout/stderr - sleep 0.1 getPassword 8 $1 return_code=$? - exec >&8 2>&9 # custom stdout/stderr return ${return_code} } @@ -753,15 +878,11 @@ getPasswordCust() { # Parameters : file > Path for file to encrypt, will get a new .gpg file extention added to filename # : password > Password to encrypt file with encryptFile() { - exec >&6 2>&7 # normal stdout/stderr - sleep 0.1 echo "${2}" | gpg --symmetric --yes --batch --cipher-algo AES256 --passphrase-fd 0 --output "${1}.gpg" "${1}" &>/dev/null && \ safeDel "${1}" >/dev/null || { - exec >&8 2>&9 # custom stdout/stderr println ERROR "${FG_RED}ERROR${NC}: failed to encrypt ${1}" return 1 } - exec >&8 2>&9 # custom stdout/stderr println DEBUG "${1} successfully encrypted" } @@ -771,15 +892,11 @@ encryptFile() { # Parameters : file > Path for file to decrypt, file extension .gpg required # : password > Password to decrypt file with decryptFile() { - exec >&6 2>&7 # normal stdout/stderr - sleep 0.1 echo "${2}" | gpg --decrypt --batch --yes --passphrase-fd 0 --output "${1%.*}" "${1}" &>/dev/null && \ rm -f "${1}" || { - exec >&8 2>&9 # custom stdout/stderr println ERROR "${FG_RED}ERROR${NC}: failed to decrypt ${1}" return 1 } - exec >&8 2>&9 # custom stdout/stderr println DEBUG "${1} successfully decrypted" } @@ -835,15 +952,32 @@ waitNewBlockCreated() { # Description : Verify that the transaction was successfully registered by checking address balance against $newBalance # Parameters : address > the address to compare with verifyTx() { - if ! waitNewBlockCreated; then return 1; fi - getBalance ${1} - while [[ ${assets[lovelace]} -ne ${newBalance} ]]; do - println DEBUG "${FG_YELLOW}WARN${NC}: Balance mismatch, transaction not included in latest block... waiting for next block!" - println LOG "$(formatLovelace ${assets[lovelace]}) != $(formatLovelace ${newBalance})" - if ! waitNewBlockCreated "silent"; then return 1; fi - getBalance ${1} - done - return 0 + if [[ -n ${KOIOS_API} && -n ${tx_id} ]]; then + println DEBUG "Waiting for transaction to be seen on chain" + println DEBUG "Id: ${tx_id}" + println DEBUG "${FG_BLUE}INFO${NC}: press any key to cancel and return (won't stop transaction)" + while :; do + read -r -n 1 -s -t 5 abort + if [[ $? -eq 0 ]]; then + println "\n${FG_YELLOW}WARN${NC}: aborted!! transaction still in queue!" + return 1 + fi + println ACTION "curl -sSL -f -X POST -H \"Content-Type: application/json\" -H \"accept: text/csv\" -d '{\"_tx_hashes\":[\"${tx_id}\"]' ${KOIOS_API}/tx_status?select=num_confirmations" + ! num_confirmations=$(curl -sSL -f -X POST -H "Content-Type: application/json" -H "accept: text/csv" -d '{"_tx_hashes":["'${tx_id}'"]}' "${KOIOS_API}/tx_status?select=num_confirmations" 2>&1) && println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${num_confirmations}\n" && return 1 # print error and return + num_confirmations=$(tail -n +2 <<< ${num_confirmations}) + [[ -n "${num_confirmations}" ]] && { println DEBUG "\nTx put on chain !!"; break; } || printf . + done + else + if ! waitNewBlockCreated; then return 1; fi + getAddressBalance ${1} true + while [[ ${lovelace} -ne ${newBalance} ]]; do + println DEBUG "${FG_YELLOW}WARN${NC}: Balance mismatch, transaction not included in latest block... waiting for next block!" + println LOG "$(formatLovelace ${lovelace}) != $(formatLovelace ${newBalance})" + if ! waitNewBlockCreated "silent"; then return 1; fi + getAddressBalance ${1} true + done + return 0 + fi } # Command : getPayAddress [wallet name] @@ -857,9 +991,11 @@ getPayAddress() { pay_addr="" if [[ -f "${payment_vk_file}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file ${payment_vk_file} --out-file ${payment_addr_file} ${NETWORK_IDENTIFIER}" - if ${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${payment_vk_file}" --out-file "${payment_addr_file}" ${NETWORK_IDENTIFIER} 2>/dev/null; then + if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${payment_vk_file}" --out-file "${payment_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then pay_addr=$(cat "${payment_addr_file}") return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during payment address creation!\n${stdout}" fi fi return 1 @@ -871,19 +1007,34 @@ getPayAddress() { # Return : populates ${base_addr} getBaseAddress() { payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" + payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" base_addr_file="${WALLET_FOLDER}/${1}/${WALLET_BASE_ADDR_FILENAME}" [[ -f ${base_addr_file} ]] && base_addr=$(cat "${base_addr_file}") && return 0 base_addr="" if [[ -f "${payment_vk_file}" && -f "${stake_vk_file}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file ${payment_vk_file} --stake-verification-key-file ${stake_vk_file} --out-file ${base_addr_file} ${NETWORK_IDENTIFIER}" - if ${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${payment_vk_file}" --stake-verification-key-file "${stake_vk_file}" --out-file "${base_addr_file}" ${NETWORK_IDENTIFIER} 2>/dev/null; then + if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${payment_vk_file}" --stake-verification-key-file "${stake_vk_file}" --out-file "${base_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then base_addr=$(cat "${base_addr_file}") return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during base address creation!\n${stdout}" + fi + elif [[ -f "${payment_script_file}" && -f "${stake_vk_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-script-file ${payment_script_file} --stake-verification-key-file ${stake_vk_file} --out-file ${base_addr_file} ${NETWORK_IDENTIFIER}" + if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-script-file "${payment_script_file}" --stake-verification-key-file "${stake_vk_file}" --out-file "${base_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then + base_addr=$(cat "${base_addr_file}") + return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during base address creation!\n${stdout}" fi elif [[ $# -eq 2 && -f "${1}" && -f "${2}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file ${1} --stake-verification-key-file ${2} ${NETWORK_IDENTIFIER}" - base_addr=$(${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${1}" --stake-verification-key-file "${2}" ${NETWORK_IDENTIFIER} 2>/dev/null) + if base_addr=$(${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${1}" --stake-verification-key-file "${2}" ${NETWORK_IDENTIFIER} 2>&1); then + return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during base address creation!\n${base_addr}" + fi fi return 1 } @@ -899,13 +1050,15 @@ getRewardAddress() { reward_addr="" if [[ -f "${stake_vk_file}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file ${stake_vk_file} --out-file ${stake_addr_file} ${NETWORK_IDENTIFIER}" - if ${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_addr_file}" ${NETWORK_IDENTIFIER} 2>/dev/null; then + if stdout=$(${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then reward_addr=$(cat "${stake_addr_file}") return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during reward address creation!\n${stdout}" fi elif [[ -f "${1}" ]]; then - println ACTION "${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file ${1} ${NETWORK_IDENTIFIER}" - reward_addr=$(${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file "${1}" ${NETWORK_IDENTIFIER} 2>/dev/null) + getRewardAddressFromKey ${1} + return $? fi return 1 } @@ -916,7 +1069,62 @@ getRewardAddress() { # Return : populates ${reward_addr} getRewardAddressFromKey() { println ACTION "${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file ${1} ${NETWORK_IDENTIFIER}" - reward_addr=$(${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file "${1}" ${NETWORK_IDENTIFIER} 2>/dev/null) + if ! reward_addr=$(${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file "${1}" ${NETWORK_IDENTIFIER} 2>&1); then + println LOG "\n${FG_RED}ERROR${NC}: failure during reward address creation!\n${base_addr}" + return 1 + fi +} + +# Command : getPayScriptAddress [wallet name] +# Description : create, store and save multi-sig payment script address +# Parameters : wallet name > the name of the wallet +# Return : populates ${pay_script_addr} +getPayScriptAddress() { + payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" + payment_script_addr_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_ADDR_FILENAME}" + [[ -f ${payment_script_addr_file} ]] && pay_script_addr=$(cat "${payment_script_addr_file}") && return 0 + unset pay_script_addr + if [[ -f "${payment_script_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-script-file ${payment_script_file} --out-file ${payment_script_addr_file} ${NETWORK_IDENTIFIER}" + if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-script-file "${payment_script_file}" --out-file "${payment_script_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then + pay_script_addr=$(cat "${payment_script_addr_file}") + return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during payment script address creation!\n${stdout}" + fi + fi + return 1 +} + +# Command : getCredentials [wallet name] +# Description : create and save wallet credentials (key hash) for payment and stake keys +# Parameters : wallet name > the name of the wallet +# Return : populates ${pay_cred} & ${stake_cred} +getCredentials() { + payment_cred_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_CRED_FILENAME}" + stake_cred_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_CRED_FILENAME}" + [[ -f ${payment_cred_file} && -f ${stake_cred_file} ]] && pay_cred=$(cat "${payment_cred_file}") && stake_cred=$(cat "${stake_cred_file}") && return 0 + unset pay_cred stake_cred + payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" + stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" + if [[ -f "${payment_vk_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file ${payment_vk_file} --out-file ${payment_cred_file}" + if stdout=$(${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file "${payment_vk_file}" --out-file "${payment_cred_file}" 2>&1); then + pay_cred=$(cat "${payment_cred_file}") + else + println LOG "\n${FG_RED}ERROR${NC}: failure during payment key hash creation!\n${stdout}" + return 1 + fi + fi + if [[ -f "${stake_vk_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-hash --stake-verification-key-file ${stake_vk_file} --out-file ${stake_cred_file}" + if stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-hash --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_cred_file}" 2>&1); then + stake_cred=$(cat "${stake_cred_file}") + else + println LOG "\n${FG_RED}ERROR${NC}: failure during stake key hash creation!\n${stdout}" + return 1 + fi + fi } # Command : getAddressInfo [address] @@ -925,15 +1133,17 @@ getRewardAddressFromKey() { # Return : populates ${address_info} getAddressInfo() { println ACTION "${CCLI} ${NETWORK_ERA} address info --address $1" - address_info=$(${CCLI} ${NETWORK_ERA} address info --address $1) + if ! address_info=$(${CCLI} ${NETWORK_ERA} address info --address $1 2>&1); then + println LOG "\n${FG_RED}ERROR${NC}: failure during reward address creation!\n${base_addr}" + return 1 + fi } # Command : getBalance [address] # Description : check balance for provided address # Parameters : address > the wallet address to query -# Return : populates associative arrays ${assets[]} & ${policyIDs[]} getBalance() { - declare -gA utxos=(); declare -gA assets=(); declare -gA policyIDs=() + declare -gA utxos=(); declare -gA assets=() assets["lovelace"]=0; utxo_cnt=0 asset_name_maxlen=5; asset_amount_maxlen=12 tx_in="" @@ -961,8 +1171,9 @@ getBalance() { if ! isNumber "${asset_amount}"; then break; fi asset_hash_name="${utxo_entry[$((idx+1))]}" IFS='.' read -ra asset <<< "${asset_hash_name}" - policyIDs["${asset[0]}"]=1 - [[ ${#asset[@]} -eq 2 && ${#asset[1]} -gt ${asset_name_maxlen} ]] && asset_name_maxlen=${#asset[1]} + tname="$(hexToAscii ${asset[1]})" + tname="${tname//[![:print:]]/}" + [[ ${#asset[@]} -eq 2 && ${#tname} -gt ${asset_name_maxlen} ]] && asset_name_maxlen=${#tname} asset_amount_fmt="$(formatAsset ${asset_amount})" [[ ${#asset_amount_fmt} -gt ${asset_amount_maxlen} ]] && asset_amount_maxlen=${#asset_amount_fmt} assets["${asset_hash_name}"]=$(( ${assets["${asset_hash_name}"]:-0} + asset_amount )) @@ -977,9 +1188,129 @@ getBalance() { [[ ${#lovelace_fmt} -gt ${asset_amount_maxlen} ]] && asset_amount_maxlen=${#lovelace_fmt} } +# Command : getBalanceKoios parse_assets +# Description : check balance for provided addresses using Koios API +# Parameters : parse_assets > [true|false] should additional assets on utxo be parsed or not (default=true) +getBalanceKoios() { + # generate different arrays using key constructed in format:
, + # Ex: ( [addr123,lovelace]=1000 [addr456,policy.name]=500 ) + # Its assumed that an array called addr_list has been populated with all addresses to fetch balance for + + declare -gA utxos=(); declare -gA utxos_cnt=(); declare -gA assets=(); declare -gA tx_in_arr=(); declare -gA asset_name_maxlen_arr=(); declare -gA asset_amount_maxlen_arr=() + + if [[ -n ${KOIOS_API} && -n ${addr_list+x} ]]; then + printf -v addr_list_joined '\"%s\",' "${addr_list[@]}" + [[ $1 != false ]] && extended=true || extended=false + println ACTION "curl -sSL -f -X POST -H \"Content-Type: application/json\" -H \"accept: text/csv\" -d '{\"_addresses\":[${addr_list_joined%,}],\"_extended\":${extended}}' ${KOIOS_API}/address_utxos?select=address,tx_hash,tx_index,value,asset_list" + ! address_utxo_list=$(curl -sSL -f -X POST -H "Content-Type: application/json" -H "accept: text/csv" -d '{"_addresses":['${addr_list_joined%,}'],"_extended":'${extended}'}' "${KOIOS_API}/address_utxos?select=address,tx_hash,tx_index,value,asset_list" 2>&1) && println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${address_utxo_list}\n" && return 1 # print error and return + [[ -z ${address_utxo_list} ]] && return + while IFS=',' read -r _address _tx_hash _tx_index _value _asset_list; do + [[ ${_address} = address ]] && continue # header line + index_prefix="${_address}," + assets["${index_prefix}lovelace"]=$(( ${assets["${index_prefix}lovelace"]:-0} + _value )) + utxos["${index_prefix}${_tx_hash}#${_tx_index}. ADA"]=${_value} + utxos_cnt["${_address}"]=$(( ${utxos_cnt["${_address}"]:-0} + 1 )) + tx_in_arr["${_address}"]="${tx_in_arr["${_address}"]} --tx-in ${_tx_hash}#${_tx_index}" + if [[ $1 != false ]]; then + asset_list_unescaped=${_asset_list:1: -1} # remove first and last char (quotation) + asset_list_unescaped=$(sed 's/""/"/g' <<< "${asset_list_unescaped}") # remove all double quotes, sed seems to perform better than bash string manipulation + while IFS=',' read -r _policy_id _asset_name _quantity; do + tname="$(hexToAscii ${_asset_name})" + tname="${tname//[![:print:]]/}" + [[ ${#tname} -gt ${asset_name_maxlen_arr["${_address}"]:-5} ]] && asset_name_maxlen_arr["${_address}"]=${#tname} + asset_amount_fmt="$(formatAsset ${_quantity})" + [[ ${#asset_amount_fmt} -gt ${asset_amount_maxlen_arr["${_address}"]:-12} ]] && asset_amount_maxlen_arr["${_address}"]=${#asset_amount_fmt} + assets["${index_prefix}${_policy_id}.${_asset_name}"]=$(( ${assets["${index_prefix}${_policy_id}.${_asset_name}"]:-0} + _quantity )) + utxos["${index_prefix}${_tx_hash}#${_tx_index}.${_policy_id}.${_asset_name}"]=${_quantity} + done < <( jq -cr '.[] | "\(.policy_id),\(.asset_name),\(.quantity)"' <<< "${asset_list_unescaped}" ) + fi + done <<< "${address_utxo_list}" + fi +} + +# Command : getWalletBalance [wallet name] [force] [base] [pay] [asset] +# Description : get balance for wallet +# Parameters : force > optional: [true|false] force update of balance (default = false) +# : base > optional: [true|false] get base address balance (default = true) +# : pay > optional: [true|false] get payment address balance (default = true) +# : asset > optional: [true|false] fetch additional koios asset data (default = false) +getWalletBalance() { + addr_list=() + declare -gA base_assets=(); declare -gA pay_assets=() + [[ $2 = true ]] && declare -gA balances=() + [[ $5 = true ]] && asset_info=true || asset_info=false + if [[ $3 != false ]] && getBaseAddress $1 && [[ -n ${base_addr} ]]; then + if [[ -v balances[${base_addr}] ]]; then + base_lovelace=${balances[${base_addr}]} + else + if [[ -n ${KOIOS_API} ]]; then + addr_list+=(${base_addr}) + else + getBalance ${base_addr} + base_lovelace=${assets[lovelace]:-0} + for idx in "${!assets[@]}"; do base_assets[${idx}]=${assets[${idx}]}; done + fi + fi + else + base_lovelace=0 + fi + if [[ $4 != false ]] && getPayAddress $1 && [[ -n ${pay_addr} ]]; then + if [[ -v balances[${pay_addr}] ]]; then + pay_lovelace=${balances[${pay_addr}]} + else + if [[ -n ${KOIOS_API} ]]; then + addr_list+=(${pay_addr}) + else + getBalance ${pay_addr} + pay_lovelace=${assets[lovelace]:-0} + for idx in "${!assets[@]}"; do pay_assets[${idx}]=${assets[${idx}]}; done + fi + fi + else + pay_lovelace=0 + fi + if [[ ${#addr_list[@]} -gt 0 ]]; then + getBalanceKoios ${asset_info} + if [[ -n ${base_addr} ]]; then + base_lovelace=${assets["${base_addr},lovelace"]:-0} + for idx in "${!assets[@]}"; do [[ ${idx} != "${base_addr},"* ]] && continue; base_assets[${idx#*,}]=${assets[${idx}]}; done + fi + if [[ -n ${pay_addr} ]]; then + pay_lovelace=${assets["${pay_addr},lovelace"]:-0} + for idx in "${!assets[@]}"; do [[ ${idx} != "${pay_addr},"* ]] && continue; pay_assets[${idx#*,}]=${assets[${idx}]}; done + fi + fi +} + +# Command : getAddressBalance [address] [force] [asset] +# Description : get balance for address +# Parameters : force > optional: [true|false] force update of balance (default = false) +# : asset > optional: [true|false] fetch additional koios asset data (default = false) +getAddressBalance() { + [[ $2 = true ]] && declare -gA balances=() + [[ $3 = true ]] && asset_info=true || asset_info=false + if [[ -n ${1} ]]; then + if [[ -v balances[${1}] ]]; then + lovelace=${balances[${1}]} + else + if [[ -n ${KOIOS_API} ]]; then + addr_list=(${1}) + getBalanceKoios ${asset_info} + lovelace=${assets["${1},lovelace"]:-0} + else + getBalance ${1} + lovelace=${assets[lovelace]:-0} + fi + fi + else + lovelace=0 + fi +} + # Command : getAssetsTxOut [PolicyID.AssetName] [Amount] # Description : generate tx out string for multi-assets in wallet # getBalance assumed to be run before calling this function +# address variable assumed to be set to selected wallet bech32 address # Parameters : PolicyID.AssetName > optional: Adjust balance for this asset before generating output # Amount > optional: The amount to adjust balance # Return : populates ${assets_tx_out} @@ -990,8 +1321,8 @@ getAssetsTxOut() { assets[$1]=$(( old_value + $2 )) fi for idx in "${!assets[@]}"; do - [[ ${idx} = "lovelace" ]] && continue - [[ ${assets[${idx}]} -gt 0 ]] && assets_tx_out+="+${assets[${idx}]} ${idx}" + [[ ${idx} = *lovelace ]] && continue + [[ ${assets[${idx}]} -gt 0 ]] && assets_tx_out+="+${assets[${idx}]} ${idx#*,}" done } @@ -1000,6 +1331,7 @@ getAssetsTxOut() { # : string as passed to --tx-out parameter # Return : populates ${min_utxo_out} getMinUTxO() { + unset min_utxo_out min_utxo_args=( ${NETWORK_ERA} transaction calculate-min-required-utxo @@ -1007,31 +1339,72 @@ getMinUTxO() { --tx-out "$1" ) println ACTION "${CCLI} ${min_utxo_args[*]}" - min_utxo_out=$([[ "$(${CCLI} "${min_utxo_args[@]}")" =~ ([0-9]+) ]] && echo ${BASH_REMATCH[1]}) + if ! stdout=$(${CCLI} "${min_utxo_args[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during min utxo calculation!\n${stdout}" + return 1 + fi + min_utxo_out=$([[ ${stdout} =~ ([0-9]+) ]] && echo ${BASH_REMATCH[1]}) } -# Command : getRewards [wallet name] +# Command : getWalletRewards [wallet name] [force] # Description : check balance of reward address # Parameters : wallet name > the name of the wallet # Return : populates ${reward_lovelace} -getRewards() { +getWalletRewards() { reward_lovelace=-1 + if [[ $2 = true ]]; then + declare -gA rewards_available=(); declare -gA reward_status=(); declare -gA reward_pool=(); + fi if isWalletRegistered $1; then reward_lovelace=0 - for reward_entry in $(jq -r '.[] | @base64' <<< "${stake_address_info}"); do - _jq() { base64 -d <<< ${reward_entry} | jq -r "${1}"; } - reward_lovelace=$(( reward_lovelace + $(_jq '.rewardAccountBalance //0') )) - done + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + for reward_entry in $(jq -r '.[] | @base64' <<< "${stake_address_info}"); do + _jq() { base64 -d <<< ${reward_entry} | jq -r "${1}"; } + reward_lovelace=$(( reward_lovelace + $(_jq '.rewardAccountBalance //0') )) + done + else + reward_lovelace=${rewards_available[${reward_addr}]:-0} + fi fi } + +# Command : getRewardInfoKoios +# Description : check status and rewards for provided reward addresses using Koios API +getRewardInfoKoios() { + # generate different arrays using reward address as key, rewards available, status and delegated pool if any + # Its assumed that an array called reward_addr_list has been populated with all reward addresses to fetch data for + + declare -gA rewards_available=(); declare -gA reward_status=(); declare -gA reward_pool=(); + + # set defaults + for _reward_addr in "${reward_addr_list[@]}"; do + reward_status["${_reward_addr}"]="not registered" + rewards_available["${_reward_addr}"]=0 + unset 'reward_pool[${_reward_addr}]' + done + + if [[ -n ${KOIOS_API} && -n ${reward_addr_list+x} ]]; then + printf -v addr_list_joined '\"%s\",' "${reward_addr_list[@]}" + println ACTION "curl -sSL -f -X POST -H \"Content-Type: application/json\" -H \"accept: text/csv\" -d '{\"_stake_addresses\":[${addr_list_joined%,}]}' ${KOIOS_API}/account_info?select=stake_address,status,delegated_pool,rewards_available" + ! account_info_list=$(curl -sSL -f -X POST -H "Content-Type: application/json" -H "accept: text/csv" -d '{"_stake_addresses":['${addr_list_joined%,}']}' "${KOIOS_API}/account_info?select=stake_address,status,delegated_pool,rewards_available" 2>&1) && println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${account_info_list}\n" && return 1 # print error and return + [[ -z ${account_info_list} ]] && return + while IFS=',' read -r stake_address status delegated_pool rewards_available; do + [[ ${stake_address} = stake_address ]] && continue # header line + reward_status["${stake_address}"]="${status}" + rewards_available["${stake_address}"]="${rewards_available}" + [[ -n ${delegated_pool} ]] && reward_pool["${stake_address}"]="${delegated_pool}" + done <<< "${account_info_list}" + fi +} + # Command : getRewardsFromAddr [stake address] # Description : check balance of reward address # Parameters : stake address > the address from stake.vkey # Return : populates ${reward_lovelace} getRewardsFromAddr() { - println ACTION "${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${1}" - stake_address_info=$(${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${1}) reward_lovelace=0 + println ACTION "${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${1}" + ! stake_address_info=$(${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${1}) && println "ERROR" "\n${FG_RED}NODE CLI ERROR${NC}: ${stake_address_info}\n" && return 1 # print error and return for reward_entry in $(jq -r '.[] | @base64' <<< "${stake_address_info}"); do _jq() { base64 -d <<< ${reward_entry} | jq -r "${1}"; } reward_lovelace=$(( reward_lovelace + $(_jq '.rewardAccountBalance //0') )) @@ -1043,19 +1416,25 @@ getRewardsFromAddr() { # Parameters : wallet name > the name of the wallet isWalletRegistered() { if getRewardAddress $1; then - println ACTION "${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${reward_addr}" - stake_address_info=$(${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${reward_addr}) - [[ -n "${stake_address_info}" && $(jq -r 'length' <<< ${stake_address_info}) -gt 0 ]] && return 0 + if [[ -n ${KOIOS_API} ]]; then + [[ ! -v "reward_status[${reward_addr}]" ]] && reward_addr_list=( ${reward_addr} ) && getRewardInfoKoios + [[ ${reward_status[${reward_addr}]} = registered ]] && return 0 + else + println ACTION "${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${reward_addr}" + ! stake_address_info=$(${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${reward_addr}) && println "ERROR" "\n${FG_RED}NODE CLI ERROR${NC}: ${stake_address_info}\n" && return 1 # print error and return + [[ -n "${stake_address_info}" && $(jq -r 'length' <<< ${stake_address_info}) -gt 0 ]] && return 0 + fi fi return 1 } # Command : getWalletType [wallet name] -# Description : check if wallet is a hardware wallet, 0=yes, 1=cli, 2=cli & encrypted, 3=signing keys missing, 4=verification keys missing +# Description : check if wallet is a hardware wallet, 0=yes, 1=cli, 2=cli & encrypted, 3=signing keys missing, 4=verification keys missing, 5=multi-sig # Parameters : wallet name > the name of the wallet getWalletType() { payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" payment_sk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SK_FILENAME}" + payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" stake_sk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_SK_FILENAME}" if [[ -f "${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" && -f "${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" ]]; then # CNTools wallet @@ -1068,6 +1447,8 @@ getWalletType() { else [[ ${op_mode} = "online" && ( ! -f ${payment_sk_file} || ! -f ${stake_sk_file} ) ]] && return 3 || return 1 fi + elif [[ -f "${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" && -f "${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" ]]; then # CNTools multi-sig wallet + return 5 else return 4 fi @@ -1133,23 +1514,27 @@ registerStakeWallet() { wallet_name=$1 wallet_source="base" + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi + if [[ -z $2 || $2 = "false" ]]; then println DEBUG "Wallet ${FG_GREEN}${wallet_name}${NC} not registered on chain" - waitForInput "press any key to continue with registration" + waitToProceed "press any key to continue with registration" fi stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" stake_cert_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_CERT_FILENAME}" - getBaseAddress "${wallet_name}" - getBalance ${base_addr} - println ACTION "${CCLI} ${NETWORK_ERA} stake-address registration-certificate --stake-verification-key-file ${stake_vk_file} --out-file ${stake_cert_file}" - if ! ${CCLI} ${NETWORK_ERA} stake-address registration-certificate --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_cert_file}"; then return 1; fi + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address registration-certificate --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_cert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake registration certificate creation!\n${stdout}"; return 1 + fi if ! getTTL; then return 1; fi - stakeAddressDeposit=$(jq -r '.stakeAddressDeposit' <<< "${PROT_PARAMS}") - println LOG "Key Deposit is ${stakeAddressDeposit}" + + println LOG "Key Deposit is ${KEY_DEPOSIT}" getAssetsTxOut @@ -1164,20 +1549,20 @@ registerStakeWallet() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee - stakeAddressDeposit )) - println LOG "New balance after tx fee and key deposit is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) - $(formatLovelace ${stakeAddressDeposit}))" + newBalance=$(( base_lovelace - min_fee - KEY_DEPOSIT )) + println LOG "New balance after tx fee and key deposit is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) - $(formatLovelace ${KEY_DEPOSIT}))" - if [[ ${assets[lovelace]} -lt $(( min_fee + stakeAddressDeposit )) ]]; then + if [[ ${base_lovelace} -lt $(( min_fee + KEY_DEPOSIT )) ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address for tx fee and key deposit!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ - "Minimum required: ${FG_LBLUE}$(formatLovelace $(( min_fee + stakeAddressDeposit )))${NC} ADA" + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ + "Minimum required: ${FG_LBLUE}$(formatLovelace $(( min_fee + KEY_DEPOSIT )))${NC} ADA" return 1 fi tx_out="${base_addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee and key deposit, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -1197,7 +1582,7 @@ registerStakeWallet() { if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet Registration"; then return 1; fi if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { txFee: \"$(( min_fee + stakeAddressDeposit ))\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txFee: \"$(( min_fee + KEY_DEPOSIT ))\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi @@ -1229,14 +1614,20 @@ deregisterStakeWallet() { wallet_source="base" + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi + stake_dereg_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_DEREG_FILENAME}" println ACTION "${CCLI} ${NETWORK_ERA} stake-address deregistration-certificate --stake-verification-key-file ${stake_vk_file} --out-file ${stake_dereg_file}" - if ! ${CCLI} ${NETWORK_ERA} stake-address deregistration-certificate --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_dereg_file}"; then return 1; fi + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address deregistration-certificate --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_dereg_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake deregistration certificate creation!\n${stdout}"; return 1 + fi if ! getTTL; then return 1; fi - stakeAddressDeposit=$(jq -r '.stakeAddressDeposit' <<< "${PROT_PARAMS}") - println LOG "Key Deposit is ${stakeAddressDeposit}" + println LOG "Key Deposit is ${KEY_DEPOSIT}" getAssetsTxOut @@ -1251,20 +1642,20 @@ deregisterStakeWallet() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - newBalance=$(( ${assets[lovelace]} + stakeAddressDeposit - min_fee )) - println LOG "New balance after returned key deposit and subtracted tx fee is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${assets[lovelace]}) + $(formatLovelace ${stakeAddressDeposit}) - $(formatLovelace ${min_fee}))" + newBalance=$(( base_lovelace + KEY_DEPOSIT - min_fee )) + println LOG "New balance after returned key deposit and subtracted tx fee is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${base_lovelace}) + $(formatLovelace ${KEY_DEPOSIT}) - $(formatLovelace ${min_fee}))" - if [[ $(( ${assets[lovelace]} + stakeAddressDeposit )) -lt ${min_fee} ]]; then + if [[ $(( ${base_lovelace} + KEY_DEPOSIT )) -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ - "Minimum required: ${FG_LBLUE}$(formatLovelace $(( min_fee - stakeAddressDeposit )))${NC} ADA" + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ + "Minimum required: ${FG_LBLUE}$(formatLovelace $(( min_fee - KEY_DEPOSIT )))${NC} ADA" return 1 fi tx_out="${base_addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee and returned key deposit, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -1284,7 +1675,7 @@ deregisterStakeWallet() { if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet De-Registration"; then return 1; fi if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"amount-returned\": \"${stakeAddressDeposit}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"amount-returned\": \"${KEY_DEPOSIT}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi @@ -1314,6 +1705,11 @@ sendAssets() { [[ $(cat "${WALLET_FOLDER}/${s_wallet}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${s_addr}" ]] && wallet_source="enterprise" || wallet_source="base" + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${s_addr}]} + tx_in=${tx_in_arr[${s_addr}]} + fi + if ! getTTL; then return 1; fi if [[ -n ${metafile} && -f ${metafile} ]]; then @@ -1321,8 +1717,6 @@ sendAssets() { else metafile_param="" fi - - getBalance ${s_addr} [[ ${#assets_left[@]} -eq 0 ]] && outCount=1 || outCount=2 @@ -1352,7 +1746,7 @@ sendAssets() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} ${outCount} 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} ${outCount} 1 || return 1 build_args=( ${tx_in} @@ -1365,7 +1759,7 @@ sendAssets() { if [[ ${outCount} -eq 1 ]]; then # all assets to destination, nothing to return newBalance=0 tx_out="${d_addr}+$(( ${assets_to_send[lovelace]} - min_fee ))${assets_tx_out_d}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${assets_to_send[lovelace]} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in address ( $(formatLovelace ${assets_to_send[lovelace]}) < $(formatLovelace ${min_utxo_out}) )" println ERROR "Top up wallet with enough ADA to cover minimum UTxO balance" @@ -1374,10 +1768,10 @@ sendAssets() { build_args+=( --tx-out "${tx_out}" ) else if [[ ${include_fee} = "no" ]]; then - newBalance=$(( ${assets[lovelace]} - ${assets_to_send[lovelace]} - min_fee )) + newBalance=$(( ${assets[${index_prefix}lovelace]} - ${assets_to_send[lovelace]} - min_fee )) tx_out_d="${d_addr}+${assets_to_send[lovelace]}${assets_tx_out_d}" else - newBalance=$(( ${assets[lovelace]} - ${assets_to_send[lovelace]} )) + newBalance=$(( ${assets[${index_prefix}lovelace]} - ${assets_to_send[lovelace]} )) tx_out_d="${d_addr}+$(( ${assets_to_send[lovelace]} - min_fee ))${assets_tx_out_d}" fi getMinUTxO "${tx_out_d}" @@ -1438,6 +1832,11 @@ delegate() { wallet_source="base" + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi + if ! getTTL; then return 1; fi getAssetsTxOut @@ -1453,20 +1852,20 @@ delegate() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) )" + newBalance=$(( base_lovelace - min_fee )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) )" - if [[ ${assets[lovelace]} -lt ${min_fee} ]]; then + if [[ ${base_lovelace} -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" return 1 fi tx_out="${base_addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -1517,6 +1916,11 @@ withdrawRewards() { wallet_source="base" + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi + if ! getTTL; then return 1; fi getAssetsTxOut @@ -1531,20 +1935,20 @@ withdrawRewards() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee + reward_lovelace )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) )" + newBalance=$(( base_lovelace - min_fee + reward_lovelace )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) )" - if [[ ${assets[lovelace]} -lt ${min_fee} ]]; then + if [[ ${base_lovelace} -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ "Minimum required: ${FG_LBLUE}$(formatLovelace $((min_fee - reward_lovelace)))${NC} ADA" return 1 fi tx_out="${base_addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee and withdrawal, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -1590,13 +1994,14 @@ withdrawRewards() { # Description : Register pool with pledge on chain registerPool() { - getBaseAddress ${owner_wallets[0]} - getBalance ${base_addr} + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi if ! getTTL; then return 1; fi - stakePoolDeposit=$(jq -r '.stakePoolDeposit' <<< "${PROT_PARAMS}") - println LOG "Pool Deposit is ${stakePoolDeposit}" + println LOG "Pool Deposit is ${POOL_DEPOSIT}" owner_delegation_cert="" [[ ${delegate_owner_wallet} = 'Y' ]] && owner_delegation_cert="--certificate-file ${owner_delegation_cert_file}" @@ -1622,20 +2027,20 @@ registerPool() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_count} + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_count} || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee - stakePoolDeposit )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) - $(formatLovelace ${stakePoolDeposit}) )" + newBalance=$(( base_lovelace - min_fee - POOL_DEPOSIT )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) - $(formatLovelace ${POOL_DEPOSIT}) )" - if [[ ${assets[lovelace]} -lt $(( min_fee + stakePoolDeposit )) ]]; then + if [[ ${base_lovelace} -lt $(( min_fee + POOL_DEPOSIT )) ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address for tx fee and pool registration deposit!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ - "Minimum required: ${FG_LBLUE}$(formatLovelace $(( min_fee + stakePoolDeposit )))${NC} ADA" + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ + "Minimum required: ${FG_LBLUE}$(formatLovelace $(( min_fee + POOL_DEPOSIT )))${NC} ADA" return 1 fi tx_out="${base_addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee and pool registration deposit, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -1671,7 +2076,7 @@ registerPool() { if ! offlineJSON=$(jq ". += { \"pool-margin\": \"${margin}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"pool-cost\": \"${cost_ada}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"pool-reg-cert\": $(jq -c . "${pool_regcert_file}") }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { txFee: \"$(( min_fee + stakePoolDeposit ))\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txFee: \"$(( min_fee + POOL_DEPOSIT ))\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi for index in "${!owner_wallets[@]}"; do @@ -1715,8 +2120,10 @@ registerPool() { # Description : Register pool with pledge on chain modifyPool() { - getBaseAddress ${owner_wallets[0]} - getBalance ${base_addr} + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi if ! getTTL; then return 1; fi @@ -1735,20 +2142,20 @@ modifyPool() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_count} + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_count} || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) )" + newBalance=$(( base_lovelace - min_fee )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) )" - if [[ ${assets[lovelace]} -lt ${min_fee} ]]; then + if [[ ${base_lovelace} -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" return 1 fi tx_out="${base_addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -1828,9 +2235,18 @@ deRegisterPool() { [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="enterprise" || wallet_source="base" - getBalance ${addr} if ! getTTL; then return 1; fi + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + for key in ${!assets[@]}; do + [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' + done + utxo_cnt=${utxos_cnt[${addr}]} + tx_in=${tx_in_arr[${addr}]} + else + getBalance ${addr} + fi + getAssetsTxOut build_args=( @@ -1844,20 +2260,20 @@ deRegisterPool() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) )" + newBalance=$(( lovelace - min_fee )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" - if [[ ${assets[lovelace]} -lt ${min_fee} ]]; then + if [[ ${lovelace} -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in ${wallet_source} address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${lovelace})${NC} ADA"\ "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" return 1 fi tx_out="${addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -1934,20 +2350,31 @@ rotatePoolKeys() { if [[ ! -f ${pool_coldkey_vk_file} ]]; then # lets re-generate it from cold signing key println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${pool_coldkey_sk_file} --verification-key-file ${pool_coldkey_vk_file}" - ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${pool_coldkey_sk_file}" --verification-key-file "${pool_coldkey_vk_file}" && return 1 - output=$(jq '.description = "Stake Pool Operator Verification Key"' "${pool_coldkey_vk_file}") && jq <<< ${output} > "${pool_coldkey_vk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${pool_coldkey_sk_file}" --verification-key-file "${pool_coldkey_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cold verification key creation!\n${stdout}"; return 1 + fi + println ACTION "jq '.description = \"Stake Pool Operator Verification Key\"' ${pool_coldkey_vk_file}" + if ! stdout=$(jq '.description = "Stake Pool Operator Verification Key"' "${pool_coldkey_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cold verification key description update!\n${stdout}"; return 1 + else + jq <<< ${stdout} > "${pool_coldkey_vk_file}" + fi fi current_kes_period=$(getCurrentKESperiod) echo "${current_kes_period}" > ${pool_saved_kes_start} println ACTION "${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file ${pool_hotkey_vk_file} --signing-key-file ${pool_hotkey_sk_file}" - ! ${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file "${pool_hotkey_vk_file}" --signing-key-file "${pool_hotkey_sk_file}" && return 1 + if ! stdout=$(${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file "${pool_hotkey_vk_file}" --signing-key-file "${pool_hotkey_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during KES key creation!\n${stdout}"; return 1 + fi p_opcert="" if [[ $# -eq 1 ]]; then println ACTION "${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file ${pool_coldkey_vk_file} --counter-value $1 --operational-certificate-issue-counter-file ${pool_opcert_counter_file}" - ! ${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file "${pool_coldkey_vk_file}" --counter-value $1 --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" && return 1 + if ! stdout=$(${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file "${pool_coldkey_vk_file}" --counter-value $1 --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during operational certificate counter creation!\n${stdout}"; return 1 + fi elif [[ -n ${KOIOS_API} ]]; then ! getPoolID "${pool_name}" && println "ERROR" "\n${FG_RED}ERROR${NC}: failed to get pool ID!\n" && return 1 println ACTION "curl -sSL -f -X POST -H \"Content-Type: application/json\" -d '{\"_pool_bech32_ids\":[\"${pool_id_bech32}\"]}' ${KOIOS_API}/pool_info" @@ -1958,7 +2385,9 @@ rotatePoolKeys() { new_counter_nbr=0 # null returned = no block on chain for this pool fi println ACTION "${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file ${pool_coldkey_vk_file} --counter-value ${new_counter_nbr} --operational-certificate-issue-counter-file ${pool_opcert_counter_file}" - ! ${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file "${pool_coldkey_vk_file}" --counter-value ${new_counter_nbr} --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" && return 1 + if ! stdout=$(${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file "${pool_coldkey_vk_file}" --counter-value ${new_counter_nbr} --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during operational certificate counter creation!\n${stdout}"; return 1 + fi elif [[ -f ${pool_opcert_file} ]]; then println ACTION "${CCLI} ${NETWORK_ERA} query kes-period-info --op-cert-file ${pool_opcert_file} ${NETWORK_IDENTIFIER}" if ! kes_period_info=$(${CCLI} ${NETWORK_ERA} query kes-period-info --op-cert-file "${pool_opcert_file}" ${NETWORK_IDENTIFIER}); then @@ -1970,7 +2399,9 @@ rotatePoolKeys() { new_counter_nbr=0 # null returned = no block on chain for this pool fi println ACTION "${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file ${pool_coldkey_vk_file} --counter-value ${new_counter_nbr} --operational-certificate-issue-counter-file ${pool_opcert_counter_file}" - ! ${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file "${pool_coldkey_vk_file}" --counter-value ${new_counter_nbr} --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" && return 1 + if ! stdout=$(${CCLI} ${NETWORK_ERA} node new-counter --cold-verification-key-file "${pool_coldkey_vk_file}" --counter-value ${new_counter_nbr} --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during operational certificate counter creation!\n${stdout}"; return 1 + fi else println "ERROR" "\n${FG_RED}ERROR${NC}: op cert file missing and Koios disabled/unavailable. Unable to get current on-chain counter value!\n" && return 1 fi @@ -1978,17 +2409,19 @@ rotatePoolKeys() { if [[ ${needHWCLI} = true ]]; then if ! unlockHWDevice "issue the opcert"; then return 1; fi println ACTION "cardano-hw-cli node issue-op-cert --kes-verification-key-file ${pool_hotkey_vk_file} --hw-signing-file ${pool_coldkey_sk_file} --operational-certificate-issue-counter-file ${pool_opcert_counter_file} --kes-period ${current_kes_period} --out-file ${pool_opcert_file}" - ! cardano-hw-cli node issue-op-cert \ + if ! stdout=$(cardano-hw-cli node issue-op-cert \ --kes-verification-key-file "${pool_hotkey_vk_file}" \ --hw-signing-file "${pool_coldkey_sk_file}" \ --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" \ --kes-period "${current_kes_period}" \ - --out-file "${pool_opcert_file}" \ - && return 1 - + --out-file "${pool_opcert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during hardware operational certificate creation!\n${stdout}"; return 1 + fi else println ACTION "${CCLI} ${NETWORK_ERA} node issue-op-cert --kes-verification-key-file ${pool_hotkey_vk_file} --cold-signing-key-file ${pool_coldkey_sk_file} --operational-certificate-issue-counter-file ${pool_opcert_counter_file} --kes-period ${current_kes_period} --out-file ${pool_opcert_file}" - ! ${CCLI} ${NETWORK_ERA} node issue-op-cert --kes-verification-key-file "${pool_hotkey_vk_file}" --cold-signing-key-file "${pool_coldkey_sk_file}" --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" --kes-period "${current_kes_period}" --out-file "${pool_opcert_file}" && return 1 + if ! stdout=$(${CCLI} ${NETWORK_ERA} node issue-op-cert --kes-verification-key-file "${pool_hotkey_vk_file}" --cold-signing-key-file "${pool_coldkey_sk_file}" --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" --kes-period "${current_kes_period}" --out-file "${pool_opcert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during operational certificate creation!\n${stdout}"; return 1 + fi fi chmod 700 ${POOL_FOLDER}/${pool_name}/* @@ -2014,9 +2447,18 @@ sendMetadata() { return 1 fi - getBalance ${addr} if ! getTTL; then return 1; fi + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + for key in ${!assets[@]}; do + [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' + done + utxo_cnt=${utxos_cnt[${addr}]} + tx_in=${tx_in_arr[${addr}]} + else + getBalance ${addr} + fi + getAssetsTxOut build_args=( @@ -2030,20 +2472,20 @@ sendMetadata() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 1 || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) )" + newBalance=$(( lovelace - min_fee )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" - if [[ ${assets[lovelace]} -lt ${min_fee} ]]; then + if [[ ${lovelace} -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in ${wallet_source} address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${lovelace})${NC} ADA"\ "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" return 1 fi tx_out="${addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -2090,7 +2532,18 @@ mintAsset() { [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="enterprise" || wallet_source="base" - getBalance ${addr} + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + for key in ${!assets[@]}; do + [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' + done + index_prefix="${addr}," + utxo_cnt=${utxos_cnt[${addr}]} + tx_in=${tx_in_arr[${addr}]} + else + getBalance ${addr} + unset index_prefix + fi + if [[ ${policy_ttl} -eq 0 ]]; then if ! getTTL; then return 1; fi else @@ -2100,7 +2553,7 @@ mintAsset() { fi [[ -z ${asset_name} ]] && asset_name_out="" || asset_name_out=".$(asciiToHex "${asset_name}")" - getAssetsTxOut "${policy_id}${asset_name_out}" "${assets_to_mint}" + getAssetsTxOut "${index_prefix}${policy_id}${asset_name_out}" "${assets_to_mint}" build_args=( ${tx_in} @@ -2115,14 +2568,14 @@ mintAsset() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) )" + newBalance=$(( lovelace - min_fee )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" - if [[ ${assets[lovelace]} -lt ${min_fee} ]]; then + if [[ ${lovelace} -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in ${wallet_source} address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${lovelace})${NC} ADA"\ "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" return 1 fi @@ -2184,7 +2637,18 @@ mintAsset() { # Description : burn custom assets on specified wallet burnAsset() { - getBalance ${addr} + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + for key in ${!assets[@]}; do + [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' + done + index_prefix="${addr}," + utxo_cnt=${utxos_cnt[${addr}]} + tx_in=${tx_in_arr[${addr}]} + else + getBalance ${addr} + unset index_prefix + fi + if [[ ${policy_ttl} -eq 0 ]]; then if ! getTTL; then return 1; fi else @@ -2194,7 +2658,7 @@ burnAsset() { fi [[ -z ${asset_name} ]] && asset_name_out="" || asset_name_out=".${asset_name}" - getAssetsTxOut "${policy_id}${asset_name_out}" "-${assets_to_burn}" + getAssetsTxOut "${index_prefix}${policy_id}${asset_name_out}" "-${assets_to_burn}" build_args=( ${tx_in} @@ -2209,20 +2673,20 @@ burnAsset() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) )" + newBalance=$(( lovelace - min_fee )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" - if [[ ${assets[lovelace]} -lt ${min_fee} ]]; then + if [[ ${lovelace} -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in ${wallet_source} address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${lovelace})${NC} ADA"\ "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" return 1 fi tx_out="${addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -2280,7 +2744,9 @@ burnAsset() { # Parameters : build_args > an array with all the arguments to assemble the transaction buildTx() { println ACTION "${CCLI} ${NETWORK_ERA} transaction build-raw ${build_args[*]}" - ${CCLI} ${NETWORK_ERA} transaction build-raw "${build_args[@]}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} transaction build-raw "${build_args[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during transaction building!\n${stdout}"; return 1 + fi } # Command : calcMinFee [rax tx file] @@ -2302,7 +2768,10 @@ calcMinFee() { --protocol-params-file "${TMP_DIR}"/protparams.json ) println ACTION "${CCLI} ${min_fee_args[*]}" - min_fee=$([[ "$(${CCLI} ${min_fee_args[*]})" =~ ([0-9]+) ]] && echo ${BASH_REMATCH[1]}) + if ! stdout=$(${CCLI} "${min_fee_args[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during minimum fee calculation!\n${stdout}"; return 1 + fi + min_fee=$([[ ${stdout} =~ ([0-9]+) ]] && echo ${BASH_REMATCH[1]}) println LOG "fee is $(formatLovelace ${min_fee}) ADA" } @@ -2351,7 +2820,7 @@ witnessTx() { --out-file "${tx_witness}" ) println ACTION "${witness_command[@]}" - if ! "${witness_command[@]}"; then println ERROR "\n${FG_RED}ERROR${NC}: during transaction signing !!" && return 1; fi + if ! stdout=$("${witness_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: during transaction signing !!\n${stdout}" && return 1; fi fi tx_witness_files+=( "${tx_witness}" ) done @@ -2360,7 +2829,7 @@ witnessTx() { if [[ ${isHW} = 'Y' ]]; then if ! unlockHWDevice "witness the transaction"; then return 1; fi println ACTION "${hw_witness_command[@]}" - if ! "${hw_witness_command[@]}"; then println ERROR "\n${FG_RED}ERROR${NC}: during hardware wallet signing !!" && return 1; fi + if ! stdout=$("${hw_witness_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: during hardware wallet signing !!\n${stdout}" && return 1; fi fi } @@ -2387,7 +2856,7 @@ assembleTx() { --out-file "${tx_signed}" ) println ACTION "${sign_command[@]}" - "${sign_command[@]}" + if ! stdout=$("${sign_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: during hardware wallet signing !!\n${stdout}" && return 1; fi else println ERROR "\n${FG_RED}ERROR${NC}: no witness files provided, unable to assemble tx!" return 1 @@ -2399,21 +2868,88 @@ assembleTx() { # Parameters : signed tx file > the signed transaction file to submit submitTx() { tx_signed="$1" + answer=0 + while true; do + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + submitTxNode ${tx_signed} && break + else + submitTxKoiosOgmios ${tx_signed} && break + fi + tput sc + println DEBUG "\nRetry transaction submit?" + select_opt "[y] Yes" "[n] No" + answer=$? + tput rc && tput ed + case ${answer} in + 0) : ;; + 1) break ;; + esac + done + return ${answer} +} - #if [[ -n ${KOIOS_API} ]]; then - # txdata="$(mktemp "${TMP_DIR}/tx.signed_XXXXXXXXXX")" - # xxd -p -r <<< $(jq .cborHex ${tx.signed}) > ${txdata} - # println ACTION "curl -X POST -H \"Content-Type: application/cbor\" --data-binary @${txdata} \"${KOIOS_API}/submittx\"" - # curl -X POST -H "Content-Type: application/cbor" --data-binary @${txdata} "${KOIOS_API}/submittx" - #else - submit_command=( - ${CCLI} ${NETWORK_ERA} transaction submit - --tx-file "${tx_signed}" - ${NETWORK_IDENTIFIER} - ) - println ACTION "${submit_command[@]}" - "${submit_command[@]}" - #fi +# Command : submitTxNode [signed tx file] +# Description : Helper function to submit signed transaction file using local node +# Parameters : signed tx file > the signed transaction file to submit +submitTxNode() { + submit_command=( + ${CCLI} ${NETWORK_ERA} transaction submit + --tx-file "$1" + ${NETWORK_IDENTIFIER} + ) + println ACTION "${submit_command[@]}" + if ! stdout=$("${submit_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: Transaction submit failed !!\n${stdout}"; return 1; fi +} + +# Command : getTxId [tx file] +# Description : Helper function to calculate transaction id +# Parameters : signed tx file > the signed transaction file to submit +# Info : tx_id set to hash of transaction body +getTxId() { + txid_command=( + ${CCLI} transaction txid + --tx-file "$1" + ) + println ACTION "${txid_command[@]}" + if ! tx_id=$("${txid_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: during transaction hashing !!\n${tx_id}" && return 1; fi +} + +# Command : submitTxKoiosSubmitAPI [signed tx file] +# Description : Helper function to submit signed transaction file using koios submitapi endpoint +# Parameters : signed tx file > the signed transaction file to submit +submitTxKoiosSubmitAPI() { + getTxId $1 || return $? + cborHex=$(jq -er '.cborHex' "$1" 2>/dev/null) || { println ERROR "\n${FG_RED}ERROR${NC}: Invalid tx file format, 'cborHex' missing in: $1"; return 1; } + txdata="$(mktemp "${TMP_DIR}/tx.signed_XXXXXXXXXX")" + xxd -p -r <<< ${cborHex} > ${txdata} + println ACTION "curl -sfSL -X POST -H \"Content-Type: application/cbor\" --data-binary @${txdata} \"${KOIOS_API}/submittx\"" + if ! stdout=$(curl -sfSL -X POST -H "Content-Type: application/cbor" --data-binary @${txdata} "${KOIOS_API}/submittx" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: Transaction submit failed !!\n${stdout}"; return 1 + fi + println LOG "Submit result: ${stdout}" +} + +# Command : submitTxKoiosOgmios [signed tx file] +# Description : Helper function to submit signed transaction file using koios submitapi endpoint +# Parameters : signed tx file > the signed transaction file to submit +submitTxKoiosOgmios() { + getTxId $1 || return $? + cborHex=$(jq -er '.cborHex' "$1" 2>/dev/null) || { println ERROR "\n${FG_RED}ERROR${NC}: Invalid tx file format, 'cborHex' missing in: $1"; return 1; } + jsonrpc=$(jq -n -c --arg cbor "${cborHex}" '{jsonrpc: "2.0", method: "submitTransaction", params: {transaction: {cbor: $cbor}}}') + unset ogmios_error + println ACTION "curl -sSL -X POST -H \"accept: application/json\" -H \"Content-Type: application/json\" -d \"${jsonrpc}\" \"${KOIOS_API}/ogmios/\"" + stdout=$(curl -sSL -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "${jsonrpc}" "${KOIOS_API}/ogmios/" 2>&1) + if [[ -z ${stdout} ]] || ogmios_error=$(jq -er '.error //empty' <<< "${stdout}") || ! jq -er '.result //empty' <<< "${stdout}" &>/dev/null; then + println ERROR "\n${FG_RED}ERROR${NC}: Transaction submit failed !!" + if [[ -n ${ogmios_error} ]]; then + jq -r . <<< "${ogmios_error}" + println LOG $(jq -rc . <<< "${ogmios_error}") + else + println ERROR "Submit API error: ${stdout}" + fi + return 1 + fi + ogmios_res=$(jq -erc '.result' <<< "${stdout}") && println LOG "Submit result: ${ogmios_res}" } # Command : transformRawTx [raw tx file] @@ -2423,26 +2959,32 @@ transformRawTx() { tx_raw="$1" tx_raw_tmp="$(mktemp "${TMP_DIR}/tx.raw_XXXXXXXXXX")" println ACTION "cardano-hw-cli transaction transform --tx-file ${tx_raw} --out-file ${tx_raw_tmp}" - if ! cardano-hw-cli transaction transform --tx-file "${tx_raw}" --out-file "${tx_raw_tmp}" >/dev/null; then return 1; fi + if ! stdout=$(cardano-hw-cli transaction transform --tx-file "${tx_raw}" --out-file "${tx_raw_tmp}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: Transaction transform failed !!\n${stdout}"; return 1 + fi println ACTION "mv ${tx_raw_tmp} ${tx_raw}" - if ! mv "${tx_raw_tmp}" "${tx_raw}" >/dev/null; then return 1; fi + if ! stdout=$(mv "${tx_raw_tmp}" "${tx_raw}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: Transaction transform failure moving temporary file to ${tx_raw} !!\n${stdout}"; return 1 + fi } # Command : unlockHWDevice [action] # Description : Directions to unlock and open HW device # Parameters : action > message for action to be taken unlockHWDevice() { - if ! HWCLIversionCheck; then waitForInput && return 1; fi - waitForInput "${FG_BLUE}INFO${NC}: please connect and unlock hardware device" "\n ${FG_YELLOW}Ledger${NC} - Unlock with pin and open Cardano app" "\n ${FG_YELLOW}Trezor${NC} - Make sure trezor bridge is installed (https://wallet.trezor.io/#/bridge) " "\n\nwhen done, press any key to continue" + if ! HWCLIversionCheck; then waitToProceed && return 1; fi + waitToProceed "${FG_BLUE}INFO${NC}: please connect and unlock hardware device" "\n ${FG_YELLOW}Ledger${NC} - Unlock with pin and open Cardano app" "\n ${FG_YELLOW}Trezor${NC} - Make sure trezor bridge is installed (https://wallet.trezor.io/#/bridge) " "\n\nwhen done, press any key to continue" println ACTION "cardano-hw-cli device version" - device_app="$(cardano-hw-cli device version)" + if ! device_app=$(cardano-hw-cli device version 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: accessing hardware device failed !!\n${device_app}"; return 1 + fi device_app_vendor="$(cut -d' ' -f1 <<< "${device_app}")" device_app_version="$(cut -d' ' -f4 <<< "${device_app}")" println LOG "hardware device: vendor=${device_app_vendor} version=${device_app_version}" if [[ ! ${device_app_version} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then println ERROR "${FG_RED}ERROR${NC}: unable to identify connected hardware device, is the device plugged in and unlocked?" println ERROR "Make sure device is seen by OS using tools like lsusb etc and is working correctly" - waitForInput && return 1 + waitToProceed && return 1 elif [[ ${device_app_vendor} = Ledger ]]; then if ! versionCheck "5.0.1" "${device_app_version}"; then println ERROR "${FG_RED}ERROR${NC}: Cardano app version installed on Ledger is ${FG_LGRAY}v${device_app_version}${NC}, minimum required app version is ${FG_GREEN}v5.0.1${NC} !!" @@ -2587,9 +3129,20 @@ submitPoll() { [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="enterprise" || wallet_source="base" POOL_ID_HASH=$(cat ${POOL_FOLDER}/${pool_name}/${POOL_ID_FILENAME}) - getBalance ${addr} + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + for key in ${!assets[@]}; do + [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' + done + utxo_cnt=${utxos_cnt[${addr}]} + tx_in=${tx_in_arr[${addr}]} + else + getBalance ${addr} + fi + if ! getTTL; then return 1; fi + getAssetsTxOut + metafile_param="--metadata-cbor-file ${cborFile}" if [[ -n ${metafile} && -f ${metafile} ]]; then metafile_param+=" --json-metadata-no-schema --metadata-json-file ${metafile}" @@ -2607,20 +3160,20 @@ submitPoll() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - newBalance=$(( ${assets[lovelace]} - min_fee )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${assets[lovelace]}) - $(formatLovelace ${min_fee}) )" + newBalance=$(( lovelace - min_fee )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" - if [[ ${assets[lovelace]} -lt ${min_fee} ]]; then + if [[ ${lovelace} -lt ${min_fee} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in ${wallet_source} address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${lovelace})${NC} ADA"\ "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" return 1 fi tx_out="${addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" + getMinUTxO "${tx_out}" || return 1 if [[ ${newBalance} -lt ${min_utxo_out} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" return 1 @@ -2679,7 +3232,7 @@ submitPoll() { if ! assembleTx "${TMP_DIR}/tx.raw"; then return 1; fi if ! submitTx "${tx_signed}"; then return 1; fi println "Poll ${FG_GREEN}${poll_txId}${NC} ballot casted to the network" - waitForInput && continue + waitToProceed && continue esac } diff --git a/scripts/cnode-helper-scripts/cntools.sh b/scripts/cnode-helper-scripts/cntools.sh index 3bcb8cdce..77b121aaa 100755 --- a/scripts/cnode-helper-scripts/cntools.sh +++ b/scripts/cnode-helper-scripts/cntools.sh @@ -1,17 +1,6 @@ #!/usr/bin/env bash # shellcheck disable=SC1090,SC2086,SC2154,SC2034,SC2012,SC2140,SC2028,SC1091 -. "$(dirname $0)"/env offline - -# legacy config (deprecated and removed in future major version update) -if [[ -f "$(dirname $0)"/cntools.config ]]; then - ! . "$(dirname $0)"/cntools.config && exit 1 - clear && waitToProceed "${FG_RED}cntools.config deprecated and will be removed in future major version!${NC}\n"\ - "Uncomment and set any customization in User Variables section of cntools.sh instead."\ - "Once done, delete cntools.config file to get rid of this message.\n"\ - "press any key to proceed .." -fi - ###################################### # User Variables - Change as desired # # Common variables set in env file # @@ -50,6 +39,9 @@ fi # Price fetching currency. Disable by setting value 'off' [off|usd|eur|...] (default: off) (https://api.coingecko.com/api/v3/simple/supported_vs_currencies) #CURRENCY=usd +# Runtime mode, offline | local | light (default local) +# CNTOOLS_MODE=local + ###################################### # Do NOT modify code below # ###################################### @@ -59,9 +51,6 @@ fi # General exit handler cleanup() { sleep 0.1 - if { true >&6; } 2<> /dev/null; then - exec 1>&6 2>&7 3>&- 6>&- 7>&- 8>&- 9>&- # Restore stdout/stderr and close tmp file descriptors - fi [[ -n $1 ]] && err=$1 || err=$? [[ $err -eq 0 ]] && clear [[ -n ${exit_msg} ]] && echo -e "\n${exit_msg}\n" || echo -e "\nCNTools terminated, cleaning up...\n" @@ -86,7 +75,9 @@ usage() { Usage: $(basename "$0") [-o] [-a] [-b ] [-v] Koios CNTools - The Cardano SPOs best friend - -o Activate offline mode - run CNTools in offline mode without node access, a limited set of functions available + -n Local mode - run CNTools in local node mode (default) + -l Light mode - run CNTools using Koios query layer for full functionallity without a local node + -o Offline mode - run CNTools with a limited set of functionallity without external communication useful for air-gapped mode -a Enable advanced/developer features like metadata transactions, multi-asset management etc (not needed for SPO usage) -u Skip script update check overriding UPDATE_CHECK value in env -b Run CNTools and look for updates on alternate branch instead of master (only for testing/development purposes) @@ -95,16 +86,19 @@ usage() { EOF } -CNTOOLS_MODE="CONNECTED" ADVANCED_MODE="false" SKIP_UPDATE=N PRINT_VERSION="false" PARENT="$(dirname $0)" [[ -f "${PARENT}"/.env_branch ]] && BRANCH="$(cat "${PARENT}"/.env_branch)" || BRANCH="master" -while getopts :oaub:v opt; do +# save launch params +arg_copy=("$@") + +while getopts :olaub:v opt; do case ${opt} in o ) CNTOOLS_MODE="OFFLINE" ;; + l ) CNTOOLS_MODE="LIGHT" ;; a ) ADVANCED_MODE="true" ;; u ) SKIP_UPDATE=Y ;; b ) echo "${OPTARG}" > "${PARENT}"/.env_branch ;; @@ -125,20 +119,28 @@ if [[ ! -f "${PARENT}"/env ]]; then myExit 1 fi -# Source env file, re-sourced later -if [[ "${CNTOOLS_MODE}" == "OFFLINE" ]]; then - . "${PARENT}"/env offline &>/dev/null +# Source env file in normal mode with node connection, else offline mode +if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + . "${PARENT}"/env || myExit 1 else - . "${PARENT}"/env &>/dev/null + . "${PARENT}"/env offline || myExit 1 fi # Source cntools.library to populate defaults for CNTools -! . "${PARENT}"/cntools.library && myExit 1 +. "${PARENT}"/cntools.library || myExit 1 + +# If light mode, test if koios is reachable, otherwise - unset KOIOS_API +if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + test_koios + [[ -z ${KOIOS_API} ]] && myExit 1 "ERROR: Koios query test failed, unable to launch CNTools in light mode utilizing Koios query layer\n\n${launch_modes_info}" +fi + +[[ ${CNTOOLS_MODE} != "LIGHT" ]] && unset KOIOS_API [[ ${PRINT_VERSION} = "true" ]] && myExit 0 "CNTools v${CNTOOLS_VERSION} (branch: $([[ -f "${PARENT}"/.env_branch ]] && cat "${PARENT}"/.env_branch || echo "master"))" -# Do some checks when run in connected mode -if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then +# Do some checks when run in connected(local|light) mode +if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then # check to see if there are any updates available clear if [[ ${UPDATE_CHECK} = Y && ${SKIP_UPDATE} != Y ]]; then @@ -159,11 +161,14 @@ if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then esac # source common env variables in case it was updated - . "${PARENT}"/env - case $? in - 1) myExit 1 "ERROR: CNTools failed to load common env file\nPlease verify set values in 'User Variables' section in env file or log an issue on GitHub" ;; - 2) clear ;; - esac + if [[ ${ENV_UPDATED} = Y ]]; then + [[ ${CNTOOLS_MODE} = "LOCAL" ]] && . "${PARENT}"/env || . "${PARENT}"/env offline + case $? in + 1) myExit 1 "ERROR: CNTools failed to load common env file\nPlease verify set values in 'User Variables' section in env file or log an issue on GitHub" ;; + 2) clear ;; + esac + [[ ${CNTOOLS_MODE} != "LIGHT" ]] && unset KOIOS_API + fi # check for cntools update checkUpdate "${PARENT}"/cntools.library "${ENV_UPDATED}" Y N @@ -173,7 +178,7 @@ if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then echo -e "\n${FG_RED}ERROR${NC}: Update check of cntools.sh against GitHub failed!" waitToProceed fi - $0 "$@" "-u"; myExit 0 ;; # re-launch script with same args skipping update check + $0 "${arg_copy[@]}" "-u"; myExit 1 ;; # re-launch script with same args skipping update check 2) echo -e "\n${FG_RED}ERROR${NC}: Update check of cntools.library against GitHub failed!" waitToProceed ;; esac @@ -182,8 +187,7 @@ if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then if curl -s -f -m ${CURL_TIMEOUT} -o "${TMP_DIR}"/cntools-changelog.md "${URL_DOCS}/cntools-changelog.md"; then if ! cmp -s "${TMP_DIR}"/cntools-changelog.md "${PARENT}/cntools-changelog.md"; then # Latest changes not shown, show whats new and copy changelog - clear - sleep 0.1 + clear if [[ ! -f "${PARENT}/cntools-changelog.md" ]]; then # special case for first installation or 5.0.0 upgrade, print release notes until previous major version echo -e "~ CNTools - What's New ~\n\n" "$(sed -n "/\[${CNTOOLS_MAJOR_VERSION}\.${CNTOOLS_MINOR_VERSION}\.${CNTOOLS_PATCH_VERSION}\]/,/\[$((CNTOOLS_MAJOR_VERSION-1))\.[0-9]\.[0-9]\]/p" "${TMP_DIR}"/cntools-changelog.md | head -n -2)" | less -X @@ -199,19 +203,8 @@ if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then waitToProceed fi fi - - # Validate protocol parameters - if grep -q "Network.Socket.connect" <<< "${PROT_PARAMS}"; then - myExit 1 "${FG_YELLOW}WARN${NC}: node socket path wrongly configured or node not running, please verify that socket set in env file match what is used to run the node\n\n${FG_BLUE}INFO${NC}: re-run CNTools in offline mode with -o parameter if you want to access CNTools with limited functionality" - elif [[ -z "${PROT_PARAMS}" ]] || ! jq -er . <<< "${PROT_PARAMS}" &>/dev/null; then - myExit 1 "${FG_YELLOW}WARN${NC}: failed to query protocol parameters, ensure your node is running with correct genesis (the node needs to be in sync to 1 epoch after the hardfork)\n\nError message: ${PROT_PARAMS}\n\n${FG_BLUE}INFO${NC}: re-run CNTools in offline mode with -o parameter if you want to access CNTools with limited functionality" - fi - echo "${PROT_PARAMS}" > "${TMP_DIR}"/protparams.json fi -# Test if koios is reachable , otherwise - unset KOIOS_API -test_koios - archiveLog # archive current log and cleanup log archive folder # check for required command line tools @@ -230,14 +223,6 @@ if ! versionCheck "4.4.0" "${BASH_REMATCH[1]}"; then myExit 1 "BASH does not meet the minimum required version of ${FG_LBLUE}4.4.0${NC}, found ${FG_LBLUE}${BASH_REMATCH[1]}${NC}\n\nPlease upgrade to a newer Linux distribution or compile latest BASH following official docs.\n\nINSTALL: https://www.gnu.org/software/bash/manual/html_node/Installing-Bash.html\nDOWNLOAD: http://git.savannah.gnu.org/cgit/bash.git/ (latest stable TAG)" fi -exec 6>&1 # Link file descriptor #6 with normal stdout. -exec 7>&2 # Link file descriptor #7 with normal stderr. -[[ -n ${CNTOOLS_LOG} ]] && exec > >( tee >( while read -r line; do logln "INFO" "${line}"; done ) ) -[[ -n ${CNTOOLS_LOG} ]] && exec 2> >( tee >( while read -r line; do logln "ERROR" "${line}"; done ) >&2 ) -[[ -n ${CNTOOLS_LOG} ]] && exec 3> >( tee >( while read -r line; do logln "DEBUG" "${line}"; done ) >&6 ) -exec 8>&1 # Link file descriptor #8 with custom stdout. -exec 9>&2 # Link file descriptor #9 with custom stderr. - # check if there are pools in need of KES key rotation clear kes_rotation_needed="no" @@ -245,10 +230,10 @@ if [[ ${CHECK_KES} = true ]]; then while IFS= read -r -d '' pool; do unset pool_kes_start - [[ ${CNTOOLS_MODE} = "CONNECTED" ]] && getNodeMetrics + [[ ${CNTOOLS_MODE} = "LOCAL" ]] && getNodeMetrics [[ (-z ${remaining_kes_periods} || ${remaining_kes_periods} -eq 0) && -f "${pool}/${POOL_CURRENT_KES_START}" ]] && unset remaining_kes_periods && pool_kes_start="$(cat "${pool}/${POOL_CURRENT_KES_START}")" - if ! kesExpiration ${pool_kes_start}; then println ERROR "${FG_RED}ERROR${NC}: failure during KES calculation for ${FG_GREEN}$(basename ${pool})${NC}" && waitForInput && continue; fi + if ! kesExpiration ${pool_kes_start}; then println ERROR "${FG_RED}ERROR${NC}: failure during KES calculation for ${FG_GREEN}$(basename ${pool})${NC}" && waitToProceed && continue; fi if [[ ${expiration_time_sec_diff} -lt ${KES_ALERT_PERIOD} ]]; then kes_rotation_needed="yes" @@ -266,7 +251,7 @@ if [[ ${CHECK_KES} = true ]]; then println DEBUG "Time left : ${FG_YELLOW}$(timeLeft ${expiration_time_sec_diff})${NC}" fi done < <(find "${POOL_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) - [[ ${kes_rotation_needed} = "yes" ]] && waitForInput + [[ ${kes_rotation_needed} = "yes" ]] && waitToProceed fi @@ -284,14 +269,14 @@ function main { find "${TMP_DIR:?}" -type f -not \( -name 'protparams.json' -o -name '.dialogrc' -o -name "offline_tx*" -o -name "*_cntools_backup*" -o -name "metadata_*" -o -name "asset*" \) -delete unset IFS clear - [[ ${CNTOOLS_MODE} != "OFFLINE" ]] && getNodeMetrics && getPriceInfo - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then - println "$(printf " >> Koios CNTools v%s - %s - ${FG_GREEN}%s${NC} <<" "${CNTOOLS_VERSION}" "${NETWORK_NAME}" "${CNTOOLS_MODE}")" - else - println "$(printf " >> Koios CNTools v%s - %s - ${FG_LBLUE}%s${NC} <<" "${CNTOOLS_VERSION}" "${NETWORK_NAME}" "${CNTOOLS_MODE}")" + if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then + [[ ${CNTOOLS_MODE} = "LOCAL" ]] && getNodeMetrics + getPriceInfo + updateProtocolParams fi println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println "$(printf " >> Koios CNTools v%s - %s - ${CNTOOLS_MODE_COLOR}%s${NC} <<" "${CNTOOLS_VERSION}" "${NETWORK_NAME}" "${CNTOOLS_MODE}")" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println OFF " Main Menu Telegram Announcement / Support channel: ${FG_YELLOW}t.me/CardanoKoios/9759${NC}\n"\ " ) Wallet - create, show, remove and protect wallets"\ " ) Funds - send, withdraw and delegate"\ @@ -303,7 +288,7 @@ function main { " ) Refresh - reload home screen content"\ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println DEBUG "$(printf "%84s" "Epoch $(getEpoch) - $(timeLeft "$(timeUntilNextEpoch)") until next")" - if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then + if [[ ${CNTOOLS_MODE} != "LOCAL" ]]; then println DEBUG " What would you like to do?" else tip_diff=$(( $(getSlotTipRef) - slotnum )) @@ -378,17 +363,17 @@ function main { println " >> WALLET >> NEW" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - getAnswerAnyCust wallet_name "Name of new wallet" wallet_name + getAnswerAnyCust wallet_name "Name of new wallet" # Remove unwanted characters from wallet name wallet_name=${wallet_name//[^[:alnum:]]/_} if [[ -z "${wallet_name}" ]]; then println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" - waitForInput && continue + waitToProceed && continue fi echo if ! mkdir -p "${WALLET_FOLDER}/${wallet_name}"; then println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${wallet_name}" - waitForInput && continue + waitToProceed && continue fi # Wallet key filenames payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_SK_FILENAME}" @@ -398,27 +383,28 @@ function main { if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}$wallet_name${NC} already exists" println " Choose another name or delete the existing one" - waitForInput && continue + waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file ${payment_vk_file} --signing-key-file ${payment_sk_file}" - if ! ${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file "${payment_vk_file}" --signing-key-file "${payment_sk_file}"; then - println ERROR "\n${FG_RED}ERROR${NC}: failure during payment key creation!"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitForInput && continue + if ! stdout=$(${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file "${payment_vk_file}" --signing-key-file "${payment_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during payment key creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file ${stake_vk_file} --signing-key-file ${stake_sk_file}" - if ! ${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file "${stake_vk_file}" --signing-key-file "${stake_sk_file}"; then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key creation!"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitForInput && continue + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file "${stake_vk_file}" --signing-key-file "${stake_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi chmod 600 "${WALLET_FOLDER}/${wallet_name}/"* getBaseAddress ${wallet_name} getPayAddress ${wallet_name} getRewardAddress ${wallet_name} + getCredentials ${wallet_name} println "New Wallet : ${FG_GREEN}${wallet_name}${NC}" println "Address : ${FG_LGRAY}${base_addr}${NC}" println "Enterprise Address : ${FG_LGRAY}${pay_addr}${NC}" println DEBUG "\nYou can now send and receive ADA using the above addresses." println DEBUG "Note that Enterprise Address will not take part in staking." println DEBUG "Wallet will be automatically registered on chain if you\nchoose to delegate or pledge wallet when registering a stake pool." - waitForInput && continue + waitToProceed && continue ;; ################################################################### import) while true; do # Wallet >> Import loop @@ -449,24 +435,24 @@ function main { ! cmdAvailable "cardano-address" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: bech32 and/or cardano-address not found in '\$PATH'" println ERROR "Please run updated guild-deploy.sh and re-build/re-download cardano-node" - waitForInput && continue + waitToProceed && continue fi getAnswerAnyCust wallet_name "Name of imported wallet" # Remove unwanted characters from wallet name wallet_name=${wallet_name//[^[:alnum:]]/_} if [[ -z "${wallet_name}" ]]; then println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" - waitForInput && continue + waitToProceed && continue fi echo if ! mkdir -p "${WALLET_FOLDER}/${wallet_name}"; then println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${wallet_name}" - waitForInput && continue + waitToProceed && continue fi if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}$wallet_name${NC} already exists" println " Choose another name or delete the existing one" - waitForInput && continue + waitToProceed && continue fi getAnswerAnyCust mnemonic false "24 or 15 word mnemonic(space separated)" echo @@ -475,7 +461,7 @@ function main { println ERROR "${FG_RED}ERROR${NC}: 24 or 15 words expected, found ${FG_RED}${#words[@]}${NC}" echo && safeDel "${WALLET_FOLDER}/${wallet_name}" unset mnemonic; unset words - waitForInput && continue + waitToProceed && continue fi payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_SK_FILENAME}" payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" @@ -486,7 +472,7 @@ function main { if ! root_prv=$(cardano-address key from-recovery-phrase Shelley <<< ${mnemonic}); then echo && safeDel "${WALLET_FOLDER}/${wallet_name}" unset mnemonic; unset words - waitForInput && continue + waitToProceed && continue fi unset mnemonic; unset words payment_xprv=$(cardano-address key child 1852H/1815H/0H/0/0 <<< ${root_prv}) @@ -518,31 +504,32 @@ function main { } EOF println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${payment_sk_file} --verification-key-file ${TMP_DIR}/payment.evkey" - if ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${payment_sk_file}" --verification-key-file "${TMP_DIR}/payment.evkey"; then - println ERROR "\n${FG_RED}ERROR${NC}: failure during payment signing key extraction!"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitForInput && continue + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${payment_sk_file}" --verification-key-file "${TMP_DIR}/payment.evkey"2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during payment signing key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${stake_sk_file} --verification-key-file ${TMP_DIR}/stake.evkey" - if ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${stake_sk_file}" --verification-key-file "${TMP_DIR}/stake.evkey"; then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake signing key extraction!"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitForInput && continue + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${stake_sk_file}" --verification-key-file "${TMP_DIR}/stake.evkey"2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake signing key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/payment.evkey --verification-key-file ${payment_vk_file}" - if ! ${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/payment.evkey" --verification-key-file "${payment_vk_file}"; then - println ERROR "\n${FG_RED}ERROR${NC}: failure during payment verification key extraction!"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitForInput && continue + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/payment.evkey" --verification-key-file "${payment_vk_file}"2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during payment verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/stake.evkey --verification-key-file ${stake_vk_file}" - if ! ${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/stake.evkey" --verification-key-file "${stake_vk_file}"; then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake verification key extraction!"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitForInput && continue + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/stake.evkey" --verification-key-file "${stake_vk_file}"2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi chmod 600 "${WALLET_FOLDER}/${wallet_name}/"* getBaseAddress ${wallet_name} getPayAddress ${wallet_name} getRewardAddress ${wallet_name} + getCredentials ${wallet_name} if [[ ${base_addr} != "${base_addr_candidate}" ]]; then println ERROR "${FG_RED}ERROR${NC}: base address generated doesn't match base address candidate." println ERROR "base_addr[${FG_LGRAY}${base_addr}${NC}]\n!=\nbase_addr_candidate[${FG_LGRAY}${base_addr_candidate}${NC}]" println ERROR "Create a GitHub issue and include log file from failed CNTools session." echo && safeDel "${WALLET_FOLDER}/${wallet_name}" - waitForInput && continue + waitToProceed && continue fi echo println "Wallet Imported : ${FG_GREEN}${wallet_name}${NC}" @@ -566,7 +553,7 @@ function main { echo println DEBUG "Please read more about HD wallets at:" println DEBUG "https://cardano-community.github.io/support-faq/wallets?id=heirarchical-deterministic-hd-wallets" - waitForInput && continue + waitToProceed && continue ;; ################################################################### hardware) @@ -580,34 +567,34 @@ function main { select_opt "[y] Yes" "[n] No" case $? in 0) : ;; # do nothing - 1) waitForInput "Unsupported hardware wallet, press any key to return home" && continue ;; + 1) waitToProceed "Unsupported hardware wallet, press any key to return home" && continue ;; esac echo if ! cmdAvailable "cardano-hw-cli" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: cardano-hw-cli executable not found in path!" println ERROR "Please run '${FG_YELLOW}guild-deploy.sh -s w${NC}' to add hardware wallet support and install Vaccumlabs cardano-hw-cli, '${FG_YELLOW}guild-deploy.sh -h${NC}' shows all available options" - waitForInput && continue + waitToProceed && continue fi if [[ ! -x $(command -v cardano-hw-cli) ]]; then println ERROR "${FG_RED}ERROR${NC}: cardano-hw-cli binary doesn't have execution persmission, please fix!" - waitForInput && continue + waitToProceed && continue fi - if ! HWCLIversionCheck; then waitForInput && continue; fi + if ! HWCLIversionCheck; then waitToProceed && continue; fi getAnswerAnyCust wallet_name "Name of imported wallet" # Remove unwanted characters from wallet name wallet_name=${wallet_name//[^[:alnum:]]/_} if [[ -z "${wallet_name}" ]]; then println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" - waitForInput && continue + waitToProceed && continue fi if ! mkdir -p "${WALLET_FOLDER}/${wallet_name}"; then println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${wallet_name}" - waitForInput && continue + waitToProceed && continue fi if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}$wallet_name${NC} already exists" println " Choose another name or delete the existing one" - waitForInput && continue + waitToProceed && continue fi payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_HW_PAY_SK_FILENAME}" payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" @@ -615,14 +602,14 @@ function main { stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" if ! unlockHWDevice "extract ${FG_LGRAY}payment keys${NC}"; then safeDel "${WALLET_FOLDER}/${wallet_name}"; continue; fi println ACTION "cardano-hw-cli address key-gen --path 1852H/1815H/0H/0/0 --verification-key-file ${payment_vk_file} --hw-signing-file ${payment_sk_file}" - if ! cardano-hw-cli address key-gen --path 1852H/1815H/0H/0/0 --verification-key-file "${payment_vk_file}" --hw-signing-file "${payment_sk_file}"; then - println ERROR "\n${FG_RED}ERROR${NC}: failure during payment key extraction!"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitForInput && continue + if ! stdout=$(cardano-hw-cli address key-gen --path 1852H/1815H/0H/0/0 --verification-key-file "${payment_vk_file}" --hw-signing-file "${payment_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during payment key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi jq '.description = "Payment Hardware Verification Key"' "${payment_vk_file}" > "${TMP_DIR}/$(basename "${payment_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${payment_vk_file}").tmp" "${payment_vk_file}" println DEBUG "${FG_BLUE}INFO${NC}: repeat and follow instructions on hardware device to extract the ${FG_LGRAY}stake keys${NC}" println ACTION "cardano-hw-cli address key-gen --path 1852H/1815H/0H/2/0 --verification-key-file ${stake_vk_file} --hw-signing-file ${stake_sk_file}" - if ! cardano-hw-cli address key-gen --path 1852H/1815H/0H/2/0 --verification-key-file "${stake_vk_file}" --hw-signing-file "${stake_sk_file}"; then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key extraction!"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitForInput && continue + if ! stdout=$(cardano-hw-cli address key-gen --path 1852H/1815H/0H/2/0 --verification-key-file "${stake_vk_file}" --hw-signing-file "${stake_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi jq '.description = "Stake Hardware Verification Key"' "${stake_vk_file}" > "${TMP_DIR}/$(basename "${stake_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${stake_vk_file}").tmp" "${stake_vk_file}" getBaseAddress ${wallet_name} @@ -648,7 +635,7 @@ function main { println DEBUG " ${FG_LGRAY}>${NC} If restored wallet contain funds since before, send all ADA through Daedalus/Yoroi to address shown in CNTools" println DEBUG " ${FG_LGRAY}>${NC} Only use the address shown in CNTools to receive funds" println DEBUG " ${FG_LGRAY}>${NC} Only spend ADA from CNTools, if spent through Daedalus/Yoroi balance seen in CNTools wont match" - waitForInput && continue + waitToProceed && continue ;; ################################################################### esac # wallet >> import sub OPERATION done # Wallet >> Import loop @@ -658,10 +645,10 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> WALLET >> REGISTER" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -670,49 +657,47 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "non-reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "non-reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} fi - getBaseAddress ${wallet_name} - getBalance ${base_addr} - if [[ ${assets[lovelace]} -gt 0 ]]; then + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -gt 0 ]]; then if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds in wallet:" "$(formatLovelace ${assets[lovelace]})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds in wallet:" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "\n${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}" - stakeAddressDeposit=$(jq -r '.stakeAddressDeposit' <<< "${PROT_PARAMS}") - println DEBUG "Funds for key deposit($(formatLovelace ${stakeAddressDeposit}) ADA) + transaction fee needed to register the wallet" - waitForInput && continue + println DEBUG "Funds for key deposit($(formatLovelace ${KEY_DEPOSIT}) ADA) + transaction fee needed to register the wallet" + waitToProceed && continue fi if ! registerStakeWallet ${wallet_name} "true"; then - waitForInput && continue + waitToProceed && continue fi println "\n${FG_GREEN}${wallet_name}${NC} successfully registered on chain!" - waitForInput && continue + waitToProceed && continue ;; ################################################################### deregister) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> WALLET >> DE-REGISTER" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -721,44 +706,43 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} fi - getRewards ${wallet_name} + getWalletRewards ${wallet_name} if [[ "${reward_lovelace}" -gt 0 ]]; then println "\n${FG_YELLOW}WARN${NC}: wallet has unclaimed rewards, please use 'Funds >> Withdraw Rewards' before de-registration to claim your rewards" - waitForInput && continue + waitToProceed && continue fi - getBaseAddress ${wallet_name} - getBalance ${base_addr} - if [[ ${assets[lovelace]} -le 0 ]]; then + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -le 0 ]]; then println ERROR "\n${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}" println ERROR "Funds for transaction fee needed to deregister the wallet" - waitForInput && continue + waitToProceed && continue fi if ! deregisterStakeWallet; then [[ -f ${stake_dereg_file} ]] && rm -f ${stake_dereg_file} - waitForInput && continue + waitToProceed && continue fi echo - if ! verifyTx ${base_addr}; then waitForInput && continue; fi + if ! verifyTx ${base_addr}; then waitToProceed && continue; fi echo println "${FG_GREEN}${wallet_name}${NC} successfully de-registered from chain!" - println "Key deposit fee that will be refunded : ${FG_LBLUE}$(formatLovelace ${stakeAddressDeposit})${NC} ADA" - waitForInput && continue + println "Key deposit fee that will be refunded : ${FG_LBLUE}$(formatLovelace ${KEY_DEPOSIT})${NC} ADA" + waitToProceed && continue ;; ################################################################### list) clear @@ -766,15 +750,42 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> WALLET >> LIST" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, wallet balance not shown!" fi + if [[ -n ${KOIOS_API} ]]; then + tput sc + println OFF "\n${FG_YELLOW}> Querying Koios API for wallet information${NC}" + addr_list=() + reward_addr_list=() + while IFS= read -r -d '' wallet; do + wallet_name=$(basename ${wallet}) + getBaseAddress ${wallet_name} + [[ -n ${base_addr} ]] && addr_list+=(${base_addr}) + getPayAddress ${wallet_name} + [[ -n ${pay_addr} ]] && addr_list+=(${pay_addr}) + getRewardAddress ${wallet_name} + [[ -n ${reward_addr} ]] && reward_addr_list+=(${reward_addr}) + done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0) + [[ ${#addr_list[@]} -gt 0 ]] && getBalanceKoios + [[ ${#reward_addr_list[@]} -gt 0 ]] && getRewardInfoKoios + tput rc && tput ed + fi + while IFS= read -r -d '' wallet; do wallet_name=$(basename ${wallet}) enc_files=$(find "${wallet}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c) - if [[ ${CNTOOLS_MODE} = "CONNECTED" ]] && isWalletRegistered ${wallet_name}; then registered="yes"; else registered="no"; fi + if [[ ${CNTOOLS_MODE} != "OFFLINE" ]] && isWalletRegistered ${wallet_name}; then registered="yes"; else registered="no"; fi echo + if [[ ${registered} = "yes" ]]; then + postfix="- ${FG_LBLUE}REGISTERED${NC}" + else + postfix="- ${FG_LGRAY}UNREGISTERED${NC}" + fi + getWalletType ${wallet_name} + [[ $? -eq 5 ]] && postfix="${postfix} (${FG_LGRAY}multi-sig${NC})" + [[ ${enc_files} -gt 0 ]] && postfix="${postfix} (${FG_YELLOW}encrypted${NC})" if [[ ${enc_files} -gt 0 && ${registered} = "yes" ]]; then println "${FG_GREEN}${wallet_name}${NC} - ${FG_LGRAY}REGISTERED${NC} (${FG_YELLOW}encrypted${NC})" elif [[ ${registered} = "yes" ]]; then @@ -786,46 +797,93 @@ function main { fi getBaseAddress ${wallet_name} getPayAddress ${wallet_name} - if [[ -z ${base_addr} && -z ${pay_addr} ]]; then - println ERROR "${FG_RED}ERROR${NC}: wallet missing pay/base addr files or vkey files to generate them!" + getPayScriptAddress ${wallet_name} + if [[ -z ${base_addr} && -z ${pay_addr} && -z ${pay_script_addr} ]]; then + println ERROR "${FG_RED}ERROR${NC}: wallet missing pay/base/script addr files or vkey/script files to generate them!" continue fi if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then - [[ -n ${base_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" - [[ -n ${pay_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Enterprise Addr" "${pay_addr}")" + [[ -n ${base_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" + [[ -n ${pay_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Enterprise Addr" "${pay_addr}")" + [[ -n ${pay_script_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Script Addr" "${pay_script_addr}")" else if [[ -n ${base_addr} ]]; then - getBalance ${base_addr} - getPriceString ${assets[lovelace]} + lovelace=0 + asset_cnt=0 + if [[ -n ${KOIOS_API} ]]; then + for key in "${!assets[@]}"; do + [[ ${key} = "${base_addr},lovelace" ]] && lovelace=${assets["${base_addr},lovelace"]} && continue + [[ ${key} = "${base_addr},"* ]] && ((asset_cnt++)) + done + else + getBalance ${base_addr} + lovelace=${assets[lovelace]} + asset_cnt=$(( ${#assets[@]} - 1 )) + fi + getPriceString ${lovelace} println "$(printf "%-19s : ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" - if [[ ${#assets[@]} -eq 1 ]]; then - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Funds" "$(formatLovelace ${assets[lovelace]})")" + if [[ ${asset_cnt} -eq 0 ]]; then + println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Funds" "$(formatLovelace ${lovelace})")" else - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Funds" "$(formatLovelace ${assets[lovelace]})" "$(( ${#assets[@]} - 1 ))")" + println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Funds" "$(formatLovelace ${lovelace})" "${asset_cnt}")" fi fi if [[ -n ${pay_addr} ]]; then - getBalance ${pay_addr} - getPriceString ${assets[lovelace]} - if [[ ${assets[lovelace]} -gt 0 ]]; then + lovelace=0 + asset_cnt=0 + if [[ -n ${KOIOS_API} ]]; then + for key in "${!assets[@]}"; do + [[ ${key} = "${pay_addr},lovelace" ]] && lovelace=${assets["${pay_addr},lovelace"]} && continue + [[ ${key} = "${pay_addr},"* ]] && ((asset_cnt++)) + done + else + getBalance ${pay_addr} + lovelace=${assets[lovelace]} + asset_cnt=$(( ${#assets[@]} - 1 )) + fi + getPriceString ${lovelace} + if [[ ${lovelace} -gt 0 ]]; then println "$(printf "%-19s : ${FG_LGRAY}%s${NC}" "Enterprise Address" "${pay_addr}")" - if [[ ${#assets[@]} -eq 1 ]]; then - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Enterprise Funds" "$(formatLovelace ${assets[lovelace]})")" + if [[ ${asset_cnt} -eq 0 ]]; then + println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Enterprise Funds" "$(formatLovelace ${lovelace})")" else - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Enterprise Funds" "$(formatLovelace ${assets[lovelace]})" "$(( ${#assets[@]} - 1 ))")" + println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Enterprise Funds" "$(formatLovelace ${lovelace})" "${asset_cnt}")" fi fi fi - if [[ -z ${base_addr} && -z ${pay_addr} ]]; then - println "${FG_RED}Not a supported wallet${NC} - genesis address?" - println "Use an external script to send funds to a CNTools compatible wallet" - continue + if [[ -n ${pay_script_addr} ]]; then + lovelace=0 + asset_cnt=0 + if [[ -n ${KOIOS_API} ]]; then + for key in "${!assets[@]}"; do + [[ ${key} = "${pay_script_addr},lovelace" ]] && lovelace=${assets["${pay_script_addr},lovelace"]} && continue + [[ ${key} = "${pay_script_addr},"* ]] && ((asset_cnt++)) + done + else + getBalance ${pay_script_addr} + lovelace=${assets[lovelace]} + asset_cnt=$(( ${#assets[@]} - 1 )) + fi + getPriceString ${lovelace} + if [[ ${lovelace} -gt 0 ]]; then + println "$(printf "%-19s : ${FG_LGRAY}%s${NC}" "Script Address" "${pay_script_addr}")" + if [[ ${asset_cnt} -eq 0 ]]; then + println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Script Funds" "$(formatLovelace ${lovelace})")" + else + println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Script Funds" "$(formatLovelace ${lovelace})" "${asset_cnt}")" + fi + fi + fi + if [[ -n ${KOIOS_API} ]]; then + [[ -v rewards_available[${reward_addr}] ]] && reward_lovelace=${rewards_available[${reward_addr}]} || reward_lovelace=0 + delegation_pool_id=${reward_pool[${reward_addr}]} + else + getWalletRewards ${wallet_name} + delegation_pool_id=$(jq -r '.[0].delegation // empty' <<< "${stake_address_info}") fi - getRewards ${wallet_name} - if [[ "${reward_lovelace}" -ge 0 ]]; then + if [[ ${reward_lovelace} -gt 0 ]]; then getPriceString ${reward_lovelace} println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Rewards" "$(formatLovelace ${reward_lovelace})")" - delegation_pool_id=$(jq -r '.[0].delegation // empty' <<< "${stake_address_info}") if [[ -n ${delegation_pool_id} ]]; then unset poolName while IFS= read -r -d '' pool; do @@ -839,7 +897,7 @@ function main { fi fi done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) - waitForInput && continue + waitToProceed && continue ;; ################################################################### show) clear @@ -847,14 +905,14 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> WALLET >> SHOW" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, limited wallet info shown!" fi tput sc selectWallet "none" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac tput rc && tput ed @@ -866,127 +924,123 @@ function main { fi getBaseAddress ${wallet_name} getPayAddress ${wallet_name} - if [[ -z ${base_addr} && -z ${pay_addr} ]]; then - println ERROR "\n${FG_RED}ERROR${NC}: wallet missing pay/base addr files or vkey files to generate them!" - waitForInput && continue + getPayScriptAddress ${wallet_name} + if [[ -z ${base_addr} && -z ${pay_addr} && -z ${pay_script_addr} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: wallet missing pay/base/script addr files or vkey/script files to generate them!" + waitToProceed && continue fi + getCredentials ${wallet_name} getRewardAddress ${wallet_name} - base_lovelace=0 - pay_lovelace=0 - declare -A token_data=() - declare -A token_name=() - declare -A assets_total=() - if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then - # Token Metadata API URLs - case ${NWMAGIC} in - 764824073) token_meta_server="https://tokens.cardano.org/metadata/" ;; # mainnet - *) token_meta_server="https://metadata.cardano-testnet.iohkdev.io/metadata/" ;; # other test networks - esac - for i in {1..2}; do - if [[ $i -eq 1 ]]; then + if [[ -n ${KOIOS_API} ]]; then + tput sc + println OFF "\n${FG_YELLOW}> Querying Koios API for wallet information${NC}" + addr_list=() + [[ -n ${base_addr} ]] && addr_list+=("${base_addr}") + [[ -n ${pay_addr} ]] && addr_list+=("${pay_addr}") + [[ -n ${pay_script_addr} ]] && addr_list+=("${pay_script_addr}") + reward_addr_list=("${reward_addr}") + [[ ${#addr_list[@]} -gt 0 ]] && getBalanceKoios + [[ ${#reward_addr_list[@]} -gt 0 ]] && getRewardInfoKoios + tput rc && tput ed + fi + total_lovelace=0 + if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then + for i in {1..3}; do + if [[ $i -eq 1 ]]; then + [[ -z ${base_addr} ]] && continue address_type="Base" - getBalance ${base_addr} - base_lovelace=${assets[lovelace]} + address=${base_addr} + if [[ -n ${KOIOS_API} ]]; then + base_lovelace=${assets["${base_addr},lovelace"]} + else + getBalance ${base_addr} + base_lovelace=${assets[lovelace]} + fi total_lovelace=$((total_lovelace + base_lovelace)) - else + elif [[ $i -eq 2 ]]; then + [[ -z ${pay_addr} ]] && continue address_type="Enterprise" - getBalance ${pay_addr} - pay_lovelace=${assets[lovelace]} + address=${pay_addr} + if [[ -n ${KOIOS_API} ]]; then + pay_lovelace=${assets["${pay_addr},lovelace"]} + [[ ${utxos_cnt["${pay_addr}"]:-0} -eq 0 ]] && continue # Dont print Enterprise if empty + else + getBalance ${pay_addr} + pay_lovelace=${assets[lovelace]} + [[ ${utxo_cnt} -eq 0 ]] && continue # Dont print Enterprise if empty + fi total_lovelace=$((total_lovelace + pay_lovelace)) - fi - [[ $i -eq 2 && ${utxo_cnt} -eq 0 ]] && continue # Dont print Enterprise if empty - - # loop all assets to query metadata register for token data - for asset in "${!assets[@]}"; do - assets_total[${asset}]=$(( assets_total[asset] + assets[asset] )) - [[ ${asset} = "lovelace" ]] && continue - IFS='.' read -ra asset_arr <<< "${asset}" - [[ ${#asset_arr[@]} -eq 1 ]] && asset_name="" || asset_name="${asset_arr[1]}" - tsubject="${asset_arr[0]}${asset_name}" - if tdata=$(curl -sL -f -m ${CURL_TIMEOUT} ${token_meta_server}${tsubject}); then - token_data[${asset}]="${tdata}" - if tticker=$(jq -er .ticker.value <<< "${tdata}" 2>/dev/null); then token_name[${asset}]="${tticker}" - elif tname=$(jq -er .name.value <<< "${tdata}" 2>/dev/null); then token_name[${asset}]="${tname}" - fi + else + [[ -z ${pay_script_addr} ]] && continue + address_type="Script" + address=${pay_script_addr} + if [[ -n ${KOIOS_API} ]]; then + pay_script_lovelace=${assets["${pay_script_addr},lovelace"]} + [[ ${utxos_cnt["${pay_script_addr}"]:-0} -eq 0 ]] && continue # Dont print Script if empty + else + getBalance ${pay_script_addr} + pay_script_lovelace=${assets[lovelace]} + [[ ${utxo_cnt} -eq 0 ]] && continue # Dont print Script if empty fi - done - + total_lovelace=$((total_lovelace + pay_script_lovelace)) + fi + echo + if [[ -n ${KOIOS_API} ]]; then + utxo_cnt=${utxos_cnt["${address}"]:-0} + asset_name_maxlen=${asset_name_maxlen_arr["${address}"]:-5} + asset_amount_maxlen=${asset_amount_maxlen_arr["${address}"]:-12} + fi println "${FG_LBLUE}${utxo_cnt} UTxO(s)${NC} found for ${FG_GREEN}${address_type}${NC} Address!" if [[ ${utxo_cnt} -gt 0 ]]; then echo - println DEBUG "$(printf "%-67s ${FG_DGRAY}|${NC} %${asset_name_maxlen}s ${FG_DGRAY}|${NC} %-${asset_amount_maxlen}s\n" "UTxO Hash#Index" "Asset" "Amount")" - println DEBUG "${FG_DGRAY}$(printf "%68s+%$((asset_name_maxlen+2))s+%$((asset_amount_maxlen+1))s\n" "" "" "" | tr " " "-")${NC}" + println DEBUG "$(printf "%-68s ${FG_DGRAY}|${NC} %${asset_name_maxlen}s ${FG_DGRAY}|${NC} %-${asset_amount_maxlen}s\n" "UTxO Hash#Index" "Asset" "Amount")" + println DEBUG "${FG_DGRAY}$(printf "%69s+%$((asset_name_maxlen+2))s+%$((asset_amount_maxlen+1))s\n" "" "" "" | tr " " "-")${NC}" mapfile -d '' utxos_sorted < <(printf '%s\0' "${!utxos[@]}" | sort -z) for utxo in "${utxos_sorted[@]}"; do - IFS='.' read -ra utxo_arr <<< "${utxo}" + [[ -n ${KOIOS_API} && ${utxo} != "${address},"* ]] && continue + IFS='.' read -ra utxo_arr <<< "${utxo#*,}" if [[ ${#utxo_arr[@]} -eq 2 && ${utxo_arr[1]} = " ADA" ]]; then - println DEBUG "$(printf "%-67s ${FG_DGRAY}|${NC} ${FG_GREEN}%${asset_name_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_LBLUE}%-${asset_amount_maxlen}s${NC}\n" "${utxo_arr[0]}" "ADA" "$(formatLovelace ${utxos["${utxo}"]})")" + println DEBUG "$(printf "%-68s ${FG_DGRAY}|${NC} ${FG_GREEN}%${asset_name_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_LBLUE}%-${asset_amount_maxlen}s${NC}\n" "${utxo_arr[0]}" "ADA" "$(formatLovelace ${utxos["${utxo}"]})")" else [[ ${#utxo_arr[@]} -eq 3 ]] && asset_name="${utxo_arr[2]}" || asset_name="" - if [[ -n ${token_name[${utxo_arr[1]}.${asset_name}]} ]]; then - tname="${token_name[${utxo_arr[1]}.${asset_name}]}" - else - tname="$(hexToAscii ${asset_name})" - fi + tname="$(hexToAscii ${asset_name})" + tname="${tname//[![:print:]]/}" ! assets_id_bech32=$(getAssetIDBech32 ${utxo_arr[1]} ${asset_name}) && continue 3 - println DEBUG "$(printf "${FG_DGRAY}%20s${NC}${FG_LGRAY}%-47s${NC} ${FG_DGRAY}|${NC} ${FG_MAGENTA}%${asset_name_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_LBLUE}%-${asset_amount_maxlen}s${NC}\n" "Asset Fingerprint: " "${assets_id_bech32}" "${tname}" "$(formatAsset ${utxos["${utxo}"]})")" + println DEBUG "$(printf "${FG_DGRAY}%20s${NC}${FG_LGRAY}%-48s${NC} ${FG_DGRAY}|${NC} ${FG_MAGENTA}%${asset_name_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_LBLUE}%-${asset_amount_maxlen}s${NC}\n" "Asset Fingerprint: " "${assets_id_bech32}" "${tname}" "$(formatAsset ${utxos["${utxo}"]})")" fi done fi - if [[ ${#assets[@]} -gt 0 ]]; then - println "\nASSET SUMMARY: ${FG_LBLUE}${#assets[@]} Asset-Type(s)${NC} $([[ ${#assets[@]} -gt 1 ]] && echo -e "/ ${FG_LBLUE}${#policyIDs[@]} Unique Policy ID(s)${NC}")\n" - println DEBUG "$(printf "%${asset_amount_maxlen}s ${FG_DGRAY}|${NC} %-${asset_name_maxlen}s%s\n" "Total Amount" "Asset" "$([[ ${#assets[@]} -gt 1 ]] && echo -e " ${FG_DGRAY}|${NC} Asset Fingerprint")")" - println DEBUG "${FG_DGRAY}$(printf "%$((asset_amount_maxlen+1))s+%$((asset_name_maxlen+2))s%s\n" "" "" "$([[ ${#assets[@]} -gt 1 ]] && printf "+%57s" "")" | tr " " "-")${NC}" - println DEBUG "$(printf "${FG_LBLUE}%${asset_amount_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_GREEN}%-${asset_name_maxlen}s${NC}%s\n" "$(formatLovelace ${assets[lovelace]})" "ADA" "$([[ ${#assets[@]} -gt 1 ]] && echo -n " ${FG_DGRAY}|${NC}")")" + lovelace=0 + asset_cnt=0 + if [[ -n ${KOIOS_API} ]]; then + for key in "${!assets[@]}"; do + [[ ${key} = "${address},lovelace" ]] && lovelace=${assets["${address},lovelace"]} + [[ ${key} = "${address},"* ]] && ((asset_cnt++)) + done + else + lovelace=${assets[lovelace]} + asset_cnt=${#assets[@]} + fi + if [[ ${asset_cnt} -gt 0 ]]; then + println "\nASSET SUMMARY: ${FG_LBLUE}${asset_cnt} Asset-Type(s)${NC}\n" + println DEBUG "$(printf "%${asset_amount_maxlen}s ${FG_DGRAY}|${NC} %-${asset_name_maxlen}s%s\n" "Total Amount" "Asset" "$([[ ${asset_cnt} -gt 1 ]] && echo -e " ${FG_DGRAY}|${NC} Asset Fingerprint")")" + println DEBUG "${FG_DGRAY}$(printf "%$((asset_amount_maxlen+1))s+%$((asset_name_maxlen+2))s%s\n" "" "" "$([[ ${asset_cnt} -gt 1 ]] && printf "+%57s" "")" | tr " " "-")${NC}" + println DEBUG "$(printf "${FG_LBLUE}%${asset_amount_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_GREEN}%-${asset_name_maxlen}s${NC}%s\n" "$(formatLovelace ${lovelace})" "ADA" "$([[ ${asset_cnt} -gt 1 ]] && echo -n " ${FG_DGRAY}|${NC}")")" mapfile -d '' assets_sorted < <(printf '%s\0' "${!assets[@]}" | sort -z) for asset in "${assets_sorted[@]}"; do - [[ ${asset} = "lovelace" ]] && continue - IFS='.' read -ra asset_arr <<< "${asset}" + [[ ${asset} = *"lovelace" ]] && continue + IFS='.' read -ra asset_arr <<< "${asset#*,}" [[ ${#asset_arr[@]} -eq 1 ]] && asset_name="" || asset_name="${asset_arr[1]}" - if [[ -n ${token_name[${asset_arr[0]}.${asset_name}]} ]]; then - tname="${token_name[${asset_arr[0]}.${asset_name}]}" - else - tname="$(hexToAscii ${asset_name})" - fi - ! assets_id_bech32=$(getAssetIDBech32 ${asset_arr[0]} ${asset_name}) && continue 2 + ! assets_id_bech32=$(getAssetIDBech32 ${asset_arr[0]} ${asset_name}) && assets_id_bech32="?" + tname="$(hexToAscii ${asset_name})" + tname="${tname//[![:print:]]/}" println DEBUG "$(printf "${FG_LBLUE}%${asset_amount_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_MAGENTA}%-${asset_name_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_LGRAY}%s${NC}\n" "$(formatAsset ${assets["${asset}"]})" "${tname}" "${assets_id_bech32}")" done fi done - if [[ ${#assets_total[@]} -gt 1 ]]; then - echo -e "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >&6 - println DEBUG "ASSET DETAILS & METADATA\n" - i=1 - for asset in "${!assets_total[@]}"; do - [[ ${asset} = "lovelace" ]] && continue - IFS='.' read -ra asset_arr <<< "${asset}" - [[ ${#asset_arr[@]} -eq 1 ]] && asset_name="" || asset_name="${asset_arr[1]}" - ! assets_id_bech32=$(getAssetIDBech32 ${asset_arr[0]} ${asset_name}) && continue 2 - println DEBUG "$(printf "%20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Asset Fingerprint" "${assets_id_bech32}")" - if [[ -n ${token_data[${asset}]} ]]; then - if jq -er .ticker.value <<< "${token_data[${asset}]}" &>/dev/null; then MetaNameColor=${FG_LGRAY}; MetaTickerColor=${FG_MAGENTA}; else MetaNameColor=${FG_MAGENTA}; MetaTickerColor=${FG_LGRAY}; fi - println DEBUG "$(printf "%20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "PolicyID.AssetName" "${asset}")" - println DEBUG "$(printf "%-21s${FG_DGRAY}%s${NC}" "" ": # METADATA #")" - println DEBUG " $(printf "%18s ${FG_DGRAY}:${NC} ${MetaNameColor}%s${NC}" "Name" "$(jq -r .name.value <<< "${token_data[${asset}]}")")" - println DEBUG " $(printf "%18s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Description" "$(jq -r .description.value <<< "${token_data[${asset}]}")")" - tticker=$(jq -er .ticker.value <<< "${token_data[${asset}]}") && println DEBUG " $(printf "%18s ${FG_DGRAY}:${NC} ${MetaTickerColor}%s${NC}" "Ticker" "${tticker}")" - turl=$(jq -er .url.value <<< "${token_data[${asset}]}") && println DEBUG " $(printf "%18s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "URL" "${turl}")" - if tlogo=$(jq -er .logo.value <<< "${token_data[${asset}]}"); then - base64 --decode <<< "${tlogo}" 2>/dev/null > "${TMP_DIR}/${assets_id_bech32}.png" - println DEBUG " $(printf "%18s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Logo" "Extracted to: ${TMP_DIR}/${assets_id_bech32}.png")" - fi - else - println DEBUG "$(printf "%20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}$([[ -n ${asset_name} ]] && echo ".")${FG_LGRAY}%s${NC}$([[ -n ${asset_name} ]] && echo " (")${FG_MAGENTA}%s${NC}$([[ -n ${asset_name} ]] && echo ")")" "PolicyID.AssetName" "${asset_arr[0]}" "${asset_name}" "$(hexToAscii ${asset_name})")" - println DEBUG "$(printf "${FG_DGRAY}%22s${NC} ${FG_YELLOW}%s${NC}" ":" "No metadata registered in Cardano token register for this asset")" - fi - ((i++)) - [[ ${#assets_total[@]} -gt $i ]] && println OFF "${FG_DGRAY} -------------------+---------------------------------------------${NC}" - done - fi - - echo -e "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >&6 + println DEBUG "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" if isWalletRegistered ${wallet_name}; then println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_GREEN}%s${NC}" "Registered" "YES")" else @@ -996,23 +1050,59 @@ function main { println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Registered" "Unknown")" fi - [[ -n ${base_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" - [[ -n ${pay_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Enterprise Address" "${pay_addr}")" - [[ -n ${reward_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Reward/Stake Address" "${reward_addr}")" - if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then + getWalletType ${wallet_name} + case $? in + 0) println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "Hardware")" ;; + 1) println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "CLI")" ;; + 5) println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "Multi-Sig")" ;; + esac + + if [[ -f ${payment_script_file} ]]; then + if timelock_after=$(jq -er '.scripts[0].type' "${payment_script_file}") && [[ ${timelock_after} = "after" ]]; then + timelock_slot=$(jq -r '.scripts[0].slot' "${payment_script_file}") + timelock_date=$(getDateFromSlot ${timelock_slot} '%(%F %T %Z)T') + [[ $(getSlotTipRef) -gt ${timelock_slot} ]] && timelock_color="${FG_GREEN}" || timelock_color="${FG_YELLOW}" + println "$(printf "%-20s ${FG_DGRAY}:${NC} ${timelock_color}%s${NC}" "Time Locked Until" "${timelock_date}")" + fi + if atleast=$(jq -er '.scripts[1].type' "${payment_script_file}") && [[ ${atleast} = "atLeast" ]]; then + cred_header="Multi-Sig Creds ($(jq -r '.scripts[1].scripts|length' "${payment_script_file}"))" + while read -r _sig; do + unset wallet_str + while IFS= read -r -d '' wallet; do + getCredentials "$(basename ${wallet})" + if [[ ${pay_cred} = ${_sig} ]]; then + wallet_str=" (${FG_GREEN}$(basename ${wallet})${NC})" && break + fi + done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) + println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}%s" "${cred_header}" "${_sig}" "${wallet_str}")" + unset cred_header + done < <( jq -r '.scripts[1].scripts[].keyHash' "${payment_script_file}") + println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Required signers" "$(jq -r '.scripts[1].required' "${payment_script_file}")")" + fi + fi + + [[ -n ${base_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" + [[ -n ${pay_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Enterprise Address" "${pay_addr}")" + [[ -n ${pay_script_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Script Address" "${pay_script_addr}")" + [[ -n ${reward_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Reward/Stake Address" "${reward_addr}")" + [[ -n ${pay_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Payment Credential" "${pay_cred}")" + [[ -n ${stake_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Stake Credential" "${stake_cred}")" + + if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then if [[ -n ${reward_addr} ]]; then - getRewardsFromAddr ${reward_addr} - if [[ "${reward_lovelace}" -ge 0 ]]; then - total_lovelace=$((total_lovelace + reward_lovelace)) - getPriceString ${reward_lovelace} - println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LBLUE}%s${NC} ADA${price_str}" "Rewards Available" "$(formatLovelace ${reward_lovelace})")" + if [[ -n ${KOIOS_API} ]]; then + [[ -v rewards_available[${reward_addr}] ]] && reward_lovelace=${rewards_available[${reward_addr}]} || reward_lovelace=0 + delegation_pool_id=${reward_pool[${reward_addr}]} + else + getRewardsFromAddr ${reward_addr} + delegation_pool_id=$(jq -r '.[0].delegation // empty' <<< "${stake_address_info}") fi + total_lovelace=$((total_lovelace + reward_lovelace)) + getPriceString ${reward_lovelace} + println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LBLUE}%s${NC} ADA${price_str}" "Rewards Available" "$(formatLovelace ${reward_lovelace})")" fi getPriceString ${total_lovelace} println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LBLUE}%s${NC} ADA${price_str}" "Funds + Rewards" "$(formatLovelace ${total_lovelace})")" - if [[ -n ${base_addr} ]]; then getAddressInfo "${base_addr}"; else getAddressInfo "${pay_addr}"; fi - println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Encoding" "$(jq -r '.encoding' <<< ${address_info})")" - if [[ -n ${reward_addr} ]]; then delegation_pool_id=$(jq -r '.[0].delegation // empty' <<< "${stake_address_info}" 2>/dev/null); else unset delegation_pool_id; fi if [[ -n ${delegation_pool_id} ]]; then unset poolName while IFS= read -r -d '' pool; do @@ -1025,28 +1115,30 @@ function main { println "${FG_RED}Delegated${NC} to ${FG_GREEN}${poolName}${NC} ${FG_LGRAY}(${delegation_pool_id})${NC}" fi fi - if [[ -z ${pay_addr} || -z ${base_addr} || -z ${reward_addr} ]]; then + if [[ -z ${pay_addr} || -z ${pay_script_addr} || -z ${base_addr} || -z ${reward_addr} ]]; then echo - [[ -z ${pay_addr} ]] && println "${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_PAY_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_PAY_VK_FILENAME}${NC}' to generate it!" + if [[ -z ${pay_addr} && -z ${pay_script_addr} ]]; then + println "${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_PAY_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_PAY_VK_FILENAME}${NC}' to generate it!" + fi [[ -z ${base_addr} ]] && println "${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_BASE_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_PAY_VK_FILENAME}${NC}/${FG_LGRAY}${WALLET_STAKE_VK_FILENAME}${NC}' to generate it!" [[ -z ${reward_addr} ]] && println "${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_STAKE_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_STAKE_VK_FILENAME}${NC}' to generate it!" fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### remove) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> WALLET >> REMOVE" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, unable to verify wallet balance" fi echo println DEBUG "# Select wallet to remove" - selectWallet "none" + selectWallet "balance" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac echo @@ -1059,7 +1151,7 @@ function main { 1) echo && println "skipped removal process for ${FG_GREEN}$wallet_name${NC}" ;; esac - waitForInput && continue + waitToProceed && continue fi if ! getBaseAddress ${wallet_name} && ! getPayAddress ${wallet_name}; then println DEBUG "${FG_RED}WARN${NC}: unable to get address for wallet and do a balance check" @@ -1071,21 +1163,10 @@ function main { 1) echo && println "skipped removal process for ${FG_GREEN}$wallet_name${NC}" ;; esac - waitForInput && continue - fi - if [[ -n ${base_addr} ]]; then - getBalance ${base_addr} - base_lovelace=${assets[lovelace]} - else - base_lovelace=0 - fi - if [[ -n ${pay_addr} ]]; then - getBalance ${pay_addr} - pay_lovelace=${assets[lovelace]} - else - pay_lovelace=0 + waitToProceed && continue fi - getRewards ${wallet_name} + getWalletBalance ${wallet_name} + getWalletRewards ${wallet_name} if [[ ${base_lovelace} -eq 0 && ${pay_lovelace} -eq 0 && ${reward_lovelace} -le 0 ]]; then println DEBUG "INFO: This wallet appears to be empty" println DEBUG "${FG_RED}WARN${NC}: Deleting this wallet is final and you can not recover it unless you have a backup\n" @@ -1113,7 +1194,7 @@ function main { ;; esac fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### decrypt) clear @@ -1121,11 +1202,11 @@ function main { println " >> WALLET >> DECRYPT" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue println DEBUG "# Select wallet to decrypt" selectWallet "encrypted" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac filesUnlocked=0 @@ -1143,7 +1224,7 @@ function main { echo if ! getPasswordCust; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" - waitForInput && continue + waitToProceed && continue fi while IFS= read -r -d '' file; do decryptFile "${file}" "${password}" && \ @@ -1161,7 +1242,7 @@ function main { println DEBUG "${FG_YELLOW}Wallet files are now unprotected${NC}" println DEBUG "Use 'WALLET >> ENCRYPT' to re-lock" fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### encrypt) clear @@ -1169,11 +1250,11 @@ function main { println " >> WALLET >> ENCRYPT" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue println DEBUG "# Select wallet to encrypt" selectWallet "encrypted" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac filesLocked=0 @@ -1184,7 +1265,7 @@ function main { echo if ! getPasswordCust confirm; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" - waitForInput && continue + waitToProceed && continue fi keyFiles=( "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_SK_FILENAME}" @@ -1201,7 +1282,7 @@ function main { else echo println DEBUG "${FG_YELLOW}NOTE${NC}: found GPG encrypted files in folder, please decrypt/unlock wallet files before encrypting" - waitForInput && continue + waitToProceed && continue fi echo println DEBUG "# Write protecting all wallet keys with 400 permission and if enabled 'chattr +i'" @@ -1220,7 +1301,7 @@ function main { println DEBUG "${FG_BLUE}INFO${NC}: wallet files are now protected" println DEBUG "Use 'WALLET >> DECRYPT' to unlock" fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### esac # wallet sub OPERATION done # Wallet loop @@ -1250,10 +1331,10 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> FUNDS >> SEND" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -1264,18 +1345,18 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} @@ -1283,12 +1364,7 @@ function main { s_wallet="${wallet_name}" s_payment_vk_file="${payment_vk_file}" s_payment_sk_file="${payment_sk_file}" - getBaseAddress ${s_wallet} - getPayAddress ${s_wallet} - getBalance ${base_addr} - base_lovelace=${assets[lovelace]} - getBalance ${pay_addr} - pay_lovelace=${assets[lovelace]} + getWalletBalance ${s_wallet} true true true true if [[ ${pay_lovelace} -gt 0 && ${base_lovelace} -gt 0 ]]; then # Both payment and base address available with funds, let user choose what to use println DEBUG "Select source wallet address" @@ -1315,7 +1391,7 @@ function main { fi else println ERROR "${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${s_wallet}${NC}" - waitForInput && continue + waitToProceed && continue fi # Destination @@ -1323,9 +1399,9 @@ function main { println DEBUG "# Select ${FG_YELLOW}destination${NC} type" select_opt "[w] Wallet" "[a] Address" "[Esc] Cancel" case $? in - 0) selectWallet "balance" + 0) selectWallet "cache" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac d_wallet="${wallet_name}" @@ -1345,10 +1421,10 @@ function main { d_addr="${pay_addr}" elif [[ "${base_addr}" = "${s_addr}" || "${pay_addr}" = "${s_addr}" ]]; then println ERROR "\n${FG_RED}ERROR${NC}: sending to same address as source not supported" - waitForInput && continue + waitToProceed && continue else println ERROR "\n${FG_RED}ERROR${NC}: no address found for wallet ${FG_GREEN}${d_wallet}${NC} :(" - waitForInput && continue + waitToProceed && continue fi ;; 1) getAnswerAnyCust d_addr "Address" ;; @@ -1357,14 +1433,20 @@ function main { # Destination could be empty, if so without getting a valid address if [[ -z ${d_addr} ]]; then println ERROR "${FG_RED}ERROR${NC}: destination address field empty" - waitForInput && continue + waitToProceed && continue fi - getBalance ${s_addr} + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + getBalance ${s_addr} # need to re-fetch balance if CLI due to possibly being overwritten by payment balance lookup + unset index_prefix + else + index_prefix="${s_addr}," + fi declare -gA assets_left=() declare -gA assets_to_send=() for asset in "${!assets[@]}"; do - assets_left[${asset}]=${assets[${asset}]} + [[ -n ${index_prefix} && ${asset} != ${index_prefix}* ]] && continue + assets_left[${asset#*,}]=${assets[${asset}]} done # Add additional assets to transaction? @@ -1410,7 +1492,6 @@ function main { ;; 2) continue ;; esac - echo fi # Amount @@ -1432,9 +1513,9 @@ function main { amountADA="${amountADA//,}" echo if [[ ${amountADA} != "all" ]]; then - if ! amount_lovelace=$(ADAToLovelace "${amountADA}"); then waitForInput && continue; fi - [[ ${amount_lovelace} -gt ${assets[lovelace]} ]] && println ERROR "${FG_RED}ERROR${NC}: not enough funds on address, ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA available but trying to send ${FG_LBLUE}$(formatLovelace ${amount_lovelace})${NC} ADA" && waitForInput && continue - if [[ ${amount_lovelace} -lt ${assets[lovelace]} ]]; then + if ! amount_lovelace=$(ADAToLovelace "${amountADA}"); then waitToProceed && continue; fi + [[ ${amount_lovelace} -gt ${assets[${index_prefix}lovelace]} ]] && println ERROR "${FG_RED}ERROR${NC}: not enough funds on address, ${FG_LBLUE}$(formatLovelace ${assets[${index_prefix}lovelace]})${NC} ADA available but trying to send ${FG_LBLUE}$(formatLovelace ${amount_lovelace})${NC} ADA" && waitToProceed && continue + if [[ ${amount_lovelace} -lt ${assets[${index_prefix}lovelace]} ]]; then println DEBUG "Fee payed by sender? [else amount sent is reduced]" select_opt "[y] Yes" "[n] No" "[Esc] Cancel" case $? in @@ -1446,27 +1527,29 @@ function main { include_fee="yes" fi else - amount_lovelace=${assets[lovelace]} + amount_lovelace=${assets[${index_prefix}lovelace]} println DEBUG "ADA to send set to total supply: ${FG_LBLUE}$(formatLovelace ${amount_lovelace})${NC}" include_fee="yes" fi - echo - - if [[ ${amount_lovelace} -eq ${assets[lovelace]} ]]; then + if [[ ${amount_lovelace} -eq ${assets[${index_prefix}lovelace]} ]]; then if [[ ${#assets_left[@]} -gt 1 ]]; then - println DEBUG "All ADA selected to be sent, automatically add all tokens?" + println DEBUG "\nAll ADA selected to be sent, automatically add all tokens?" select_opt "[y] Yes" "[n] No" "[Esc] Cancel" case $? in 0) declare -gA assets_left=() declare -gA assets_to_send=() for asset in "${!assets[@]}"; do - assets_to_send[${asset}]=${assets[${asset}]} # add all assets, e.g clone assets array to assets_to_send + [[ -n ${index_prefix} && ${asset} != ${index_prefix}* ]] && continue + assets_to_send[${asset#*,}]=${assets[${asset}]} # add all assets, e.g clone assets array to assets_to_send done ;; - 1) println ERROR "${FG_RED}ERROR${NC}: Unable to send all ADA as there are additional assets left on address not selected to be sent" && waitForInput && continue ;; + 1) println ERROR "${FG_RED}ERROR${NC}: Unable to send all ADA as there are additional assets left on address not selected to be sent" && waitToProceed && continue ;; 2) continue ;; esac + else + unset assets_left + assets_to_send[lovelace]=${amount_lovelace} fi else assets_left[lovelace]=$(( assets_left[lovelace] - amount_lovelace )) @@ -1482,14 +1565,12 @@ function main { DEFAULTEDITOR="$(command -v nano &>/dev/null && echo 'nano' || echo 'vi')" println OFF "\nA maximum of 64 characters(bytes) is allowed per line." println OFF "${FG_YELLOW}Please don't change default file path when saving.${NC}" - exec >&6 2>&7 # normal stdout/stderr - waitForInput "press any key to open '${FG_LGRAY}${DEFAULTEDITOR}${NC}' text editor" + waitToProceed "press any key to open '${FG_LGRAY}${DEFAULTEDITOR}${NC}' text editor" ${DEFAULTEDITOR} "${metafile}" - exec >&8 2>&9 # custom stdout/stderr if [[ ! -f "${metafile}" ]]; then println ERROR "${FG_RED}ERROR${NC}: file not found" println ERROR "File: ${FG_LGRAY}${metafile}${NC}" - waitForInput && continue + waitToProceed && continue fi tput cuu 4 && tput ed if [[ ! -s ${metafile} ]]; then @@ -1511,26 +1592,24 @@ function main { error="${FG_RED}ERROR${NC}: ${tx_msg}" && break fi done < "${metafile}" - [[ -n ${error} ]] && println ERROR "${error}" && waitForInput && continue + [[ -n ${error} ]] && println ERROR "${error}" && waitToProceed && continue jq -c . <<< "${tx_msg}" > "${metafile}" - jq -r . "${metafile}" >&3 && echo + jq -r . "${metafile}" && echo println LOG "Transaction message: ${tx_msg}" fi ;; esac if ! sendAssets; then - waitForInput && continue + waitToProceed && continue fi echo - if ! verifyTx ${s_addr}; then waitForInput && continue; fi - s_balance=${assets[lovelace]} - getBalance ${d_addr} - d_balance=${assets[lovelace]} + if ! verifyTx ${s_addr}; then waitToProceed && continue; fi + s_balance=${assets[${index_prefix}lovelace]} + getAddressBalance ${d_addr} true + d_balance=${lovelace} getPayAddress ${s_wallet} [[ "${pay_addr}" = "${s_addr}" ]] && s_wallet_type=" (Enterprise)" || s_wallet_type="" - getPayAddress ${d_wallet} - [[ "${pay_addr}" = "${d_addr}" ]] && d_wallet_type=" (Enterprise)" || d_wallet_type="" echo println "Transaction" println " From : ${FG_GREEN}${s_wallet}${NC}${s_wallet_type}" @@ -1540,6 +1619,8 @@ function main { println " ${FG_LBLUE}$(formatAsset ${assets_to_send[${idx}]})${NC} ${FG_LGRAY}${idx}${NC}" done if [[ -n "${d_wallet}" ]]; then + getPayAddress ${d_wallet} + [[ "${pay_addr}" = "${d_addr}" ]] && d_wallet_type=" (Enterprise)" || d_wallet_type="" println " To : ${FG_GREEN}${d_wallet}${NC}${d_wallet_type}" else println " To : ${FG_LGRAY}${d_addr}${NC}" @@ -1548,17 +1629,17 @@ function main { println " Balance" println " - Source : ${FG_LBLUE}$(formatLovelace ${s_balance})${NC} ADA" println " - Destination : ${FG_LBLUE}$(formatLovelace ${d_balance})${NC} ADA" - waitForInput && continue + waitToProceed && continue ;; ################################################################### delegate) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> FUNDS >> DELEGATE" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -1567,41 +1648,41 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "delegate" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "delegate" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} fi - getBaseAddress ${wallet_name} - getBalance ${base_addr} - if [[ ${assets[lovelace]} -gt 0 ]]; then + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -gt 0 ]]; then if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds in wallet:" "$(formatLovelace ${assets[lovelace]})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds in wallet:" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "\n${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" - waitForInput && continue + waitToProceed && continue fi - getRewards ${wallet_name} - + getWalletRewards ${wallet_name} if [[ ${reward_lovelace} -eq -1 ]]; then if [[ ${op_mode} = "online" ]]; then - if ! registerStakeWallet ${wallet_name}; then waitForInput && continue; fi + if ! registerStakeWallet ${wallet_name}; then waitToProceed && continue; fi + # re-fetch balance to get a fresh set of utxos + getWalletBalance ${wallet_name} true true false true else println ERROR "\n${FG_YELLOW}The wallet is not a registered wallet on chain and CNTools run in hybrid mode${NC}" println ERROR "Please first register the wallet using 'Wallet >> Register'" - waitForInput && continue + waitToProceed && continue fi fi echo @@ -1610,7 +1691,7 @@ function main { case $? in 0) selectPool "reg" "${POOL_COLDKEY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getPoolID "${pool_name}" @@ -1624,32 +1705,35 @@ function main { stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" pool_delegcert_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DELEGCERT_FILENAME}" println ACTION "${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate --stake-verification-key-file ${stake_vk_file} --stake-pool-id ${pool_id} --out-file ${pool_delegcert_file}" - ${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate --stake-verification-key-file "${stake_vk_file}" --stake-pool-id "${pool_id}" --out-file "${pool_delegcert_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate --stake-verification-key-file "${stake_vk_file}" --stake-pool-id "${pool_id}" --out-file "${pool_delegcert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake delegation certificate creation!\n${stdout}"; waitToProceed && continue + fi if ! delegate; then if [[ ${op_mode} = "online" ]]; then echo && println ERROR "${FG_RED}ERROR${NC}: failure during delegation, removing newly created delegation certificate file" rm -f "${pool_delegcert_file}" fi - waitForInput && continue + waitToProceed && continue fi echo - if ! verifyTx ${base_addr}; then waitForInput && continue; fi + if ! verifyTx ${base_addr}; then waitToProceed && continue; fi + getWalletBalance ${wallet_name} true true false echo println "Delegation successfully registered" println "Wallet : ${FG_GREEN}${wallet_name}${NC}" println "Pool : ${FG_GREEN}${pool_name}${NC}" - println "Amount : ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA" - waitForInput && continue + println "Amount : ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA" + waitToProceed && continue ;; ################################################################### withdrawrewards) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> FUNDS >> WITHDRAW REWARDS" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -1658,47 +1742,45 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "reward" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "reward" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} fi echo - getBaseAddress ${wallet_name} - getBalance ${base_addr} - getRewards ${wallet_name} + getWalletBalance ${wallet_name} true true false true + getWalletRewards ${wallet_name} if [[ ${reward_lovelace} -le 0 ]]; then println ERROR "Failed to locate any rewards associated with the chosen wallet, please try another one" - waitForInput && continue - elif [[ ${assets[lovelace]} -eq 0 ]]; then + waitToProceed && continue + elif [[ ${base_lovelace} -eq 0 ]]; then println ERROR "${FG_YELLOW}WARN${NC}: No funds in base address, please send funds to base address of wallet to cover withdraw transaction fee" - waitForInput && continue + waitToProceed && continue fi - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds" "$(formatLovelace ${assets[lovelace]})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds" "$(formatLovelace ${base_lovelace})")" println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Rewards" "$(formatLovelace ${reward_lovelace})")" if ! withdrawRewards; then - waitForInput && continue + waitToProceed && continue fi echo - if ! verifyTx ${base_addr}; then waitForInput && continue; fi - getRewards ${wallet_name} + if ! verifyTx ${base_addr}; then waitToProceed && continue; fi + getWalletBalance ${wallet_name} true true false echo println "Rewards successfully withdrawn" println "New Balance" - println " Funds : ${FG_LBLUE}$(formatLovelace ${assets[lovelace]})${NC} ADA" - println " Rewards : ${FG_LBLUE}$(formatLovelace ${reward_lovelace})${NC} ADA" - waitForInput && continue + println " Funds : ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA" + waitToProceed && continue ;; ################################################################### esac # funds sub OPERATION done # Funds loop @@ -1720,7 +1802,7 @@ function main { " ) Rotate - rotate pool KES keys"\ " ) Decrypt - remove write protection and decrypt pool"\ " ) Encrypt - encrypt pool cold keys and make all files immutable"\ - " ) Vote - Cast a CIP-0094 Poll ballot"\ + " ) Vote - cast a CIP-0094 Poll ballot"\ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println DEBUG " Select Pool Operation\n" select_opt "[n] New" "[i] Import" "[r] Register" "[m] Modify" "[x] Retire" "[l] List" "[s] Show" "[o] Rotate" "[d] Decrypt" "[e] Encrypt" "[v] Vote" "[h] Home" @@ -1750,7 +1832,7 @@ function main { pool_name=${pool_name//[^[:alnum:]]/_} if [[ -z "${pool_name}" ]]; then println ERROR "${FG_RED}ERROR${NC}: Empty pool name, please retry!" - waitForInput && continue + waitToProceed && continue fi mkdir -p "${POOL_FOLDER}/${pool_name}" pool_hotkey_vk_file="${POOL_FOLDER}/${pool_name}/${POOL_HOTKEY_VK_FILENAME}" @@ -1763,26 +1845,32 @@ function main { if [[ -f "${pool_hotkey_vk_file}" ]]; then println ERROR "${FG_RED}WARN${NC}: A pool ${FG_GREEN}$pool_name${NC} already exists" println ERROR " Choose another name or delete the existing one" - waitForInput && continue + waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file ${pool_hotkey_vk_file} --signing-key-file ${pool_hotkey_sk_file}" - ${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file "${pool_hotkey_vk_file}" --signing-key-file "${pool_hotkey_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file "${pool_hotkey_vk_file}" --signing-key-file "${pool_hotkey_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during KES key creation!\n${stdout}"; waitToProceed && continue + fi if [ -f "${POOL_FOLDER}-pregen/${pool_name}/${POOL_ID_FILENAME}" ]; then mv ${POOL_FOLDER}'-pregen/'${pool_name}/* ${POOL_FOLDER}/${pool_name}/ rm -r ${POOL_FOLDER}'-pregen/'${pool_name} else println ACTION "${CCLI} ${NETWORK_ERA} node key-gen --cold-verification-key-file ${pool_coldkey_vk_file} --cold-signing-key-file ${pool_coldkey_sk_file} --operational-certificate-issue-counter-file ${pool_opcert_counter_file}" - ${CCLI} ${NETWORK_ERA} node key-gen --cold-verification-key-file "${pool_coldkey_vk_file}" --cold-signing-key-file "${pool_coldkey_sk_file}" --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} node key-gen --cold-verification-key-file "${pool_coldkey_vk_file}" --cold-signing-key-file "${pool_coldkey_sk_file}" --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during operational certificate counter file creation!\n${stdout}"; waitToProceed && continue + fi fi println ACTION "${CCLI} ${NETWORK_ERA} node key-gen-VRF --verification-key-file ${pool_vrf_vk_file} --signing-key-file ${pool_vrf_sk_file}" - ${CCLI} ${NETWORK_ERA} node key-gen-VRF --verification-key-file "${pool_vrf_vk_file}" --signing-key-file "${pool_vrf_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} node key-gen-VRF --verification-key-file "${pool_vrf_vk_file}" --signing-key-file "${pool_vrf_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during VRF key creation!\n${stdout}"; waitToProceed && continue + fi chmod 600 "${POOL_FOLDER}/${pool_name}/"* getPoolID ${pool_name} echo println "Pool: ${FG_GREEN}${pool_name}${NC}" [[ -n ${pool_id} ]] && println "ID (hex) : ${pool_id}" [[ -n ${pool_id_bech32} ]] && println "ID (bech32) : ${pool_id_bech32}" - waitForInput && continue + waitToProceed && continue ;; ################################################################### import) clear @@ -1795,7 +1883,7 @@ function main { pool_name=${pool_name//[^[:alnum:]]/_} if [[ -z "${pool_name}" ]]; then println ERROR "${FG_RED}ERROR${NC}: Empty pool name, please retry!" - waitForInput && continue + waitToProceed && continue fi mkdir -p "${POOL_FOLDER}/${pool_name}" pool_hotkey_vk_file="${POOL_FOLDER}/${pool_name}/${POOL_HOTKEY_VK_FILENAME}" @@ -1808,18 +1896,25 @@ function main { if [[ -f "${pool_hotkey_vk_file}" ]]; then println ERROR "${FG_RED}WARN${NC}: A pool ${FG_GREEN}$pool_name${NC} already exists" println ERROR " Choose another name or delete the existing one" - waitForInput && continue + waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file ${pool_hotkey_vk_file} --signing-key-file ${pool_hotkey_sk_file}" - ${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file "${pool_hotkey_vk_file}" --signing-key-file "${pool_hotkey_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} node key-gen-KES --verification-key-file "${pool_hotkey_vk_file}" --signing-key-file "${pool_hotkey_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during KES key creation!\n${stdout}"; waitToProceed && continue + fi println ACTION "${CCLI} ${NETWORK_ERA} node key-gen-VRF --verification-key-file ${pool_vrf_vk_file} --signing-key-file ${pool_vrf_sk_file}" - ${CCLI} ${NETWORK_ERA} node key-gen-VRF --verification-key-file "${pool_vrf_vk_file}" --signing-key-file "${pool_vrf_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} node key-gen-VRF --verification-key-file "${pool_vrf_vk_file}" --signing-key-file "${pool_vrf_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during VRF key creation!\n${stdout}"; waitToProceed && continue + fi - println ACTION "cardano-hw-cli node key-gen --path 1853H/1815H/0H/0H --hw-signing-file ${pool_coldkey_sk_file} --cold-verification-key-file ${pool_coldkey_kk_file} --operational-certificate-issue-counter-file ${pool_opcert_counter_file}" if ! unlockHWDevice "export cold pub keys"; then safeDel "${POOL_FOLDER}/${pool_name}"; continue; fi - cardano-hw-cli node key-gen --path "1853H/1815H/0H/0H" --hw-signing-file "${pool_coldkey_sk_file}" --cold-verification-key-file "${pool_coldkey_vk_file}" --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" + println ACTION "cardano-hw-cli node key-gen --path 1853H/1815H/0H/0H --hw-signing-file ${pool_coldkey_sk_file} --cold-verification-key-file ${pool_coldkey_kk_file} --operational-certificate-issue-counter-file ${pool_opcert_counter_file}" + if ! stdout=$(cardano-hw-cli node key-gen --path "1853H/1815H/0H/0H" --hw-signing-file "${pool_coldkey_sk_file}" --cold-verification-key-file "${pool_coldkey_vk_file}" --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during HW key extraction!\n${stdout}"; waitToProceed && continue + fi + jq '.description = "Stake Pool Operator Hardware Verification Key"' "${pool_coldkey_vk_file}" > "${TMP_DIR}/$(basename "${pool_coldkey_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${pool_coldkey_vk_file}").tmp" "${pool_coldkey_vk_file}" chmod 600 "${POOL_FOLDER}/${pool_name}/"* @@ -1829,18 +1924,18 @@ function main { println "Pool: ${FG_GREEN}${pool_name}${NC}" [[ -n ${pool_id} ]] && println "ID (hex) : ${pool_id}" [[ -n ${pool_id_bech32} ]] && println "ID (bech32) : ${pool_id_bech32}" - waitForInput && continue + waitToProceed && continue ;; ################################################################## register|modify) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> POOL >> ${SUBCOMMAND^^}" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitForInput && continue - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -1852,19 +1947,19 @@ function main { if [[ ${op_mode} = "online" ]]; then selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" "${POOL_VRF_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getPoolType ${pool_name} case $? in 0) isHWpool=Y ;; - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: signing keys missing from pool!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: signing keys missing from pool!" && waitToProceed && continue ;; esac else selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" "${POOL_VRF_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getPoolType ${pool_name} @@ -1890,7 +1985,7 @@ function main { pledge_enter="${pledge_enter//,}" if [[ -n "${pledge_enter}" ]]; then if ! ADAToLovelace "${pledge_enter}" >/dev/null; then - waitForInput && continue + waitToProceed && continue fi pledge_lovelace=$(ADAToLovelace "${pledge_enter}") pledge_ada="${pledge_enter}" @@ -1902,21 +1997,21 @@ function main { getAnswerAnyCust margin_enter "Margin (in %, default: ${margin})" if [[ -n "${margin_enter}" ]]; then if ! pctToFraction "${margin_enter}" >/dev/null; then - waitForInput && continue + waitToProceed && continue fi margin_fraction=$(pctToFraction "${margin_enter}") margin="${margin_enter}" else margin_fraction=$(pctToFraction "${margin}") fi - minPoolCost=$(formatLovelace $(jq -r '.minPoolCost //0' <<< "${PROT_PARAMS}") normal) # convert to ADA + minPoolCost=$(formatLovelace ${MIN_POOL_COST} normal) # convert to ADA [[ -f ${pool_config} ]] && cost_ada=$(jq -r '.costADA //0' "${pool_config}") || cost_ada=${minPoolCost} # default cost [[ $(bc -l <<< "${cost_ada} < ${minPoolCost}") -eq 1 ]] && cost_ada=${minPoolCost} # raise old value to new minimum cost getAnswerAnyCust cost_enter "Cost (in ADA, minimum: ${minPoolCost}, default: ${cost_ada})" cost_enter="${cost_enter//,}" if [[ -n "${cost_enter}" ]]; then if ! ADAToLovelace "${cost_enter}" >/dev/null; then - waitForInput && continue + waitToProceed && continue fi cost_lovelace=$(ADAToLovelace "${cost_enter}") cost_ada="${cost_enter}" @@ -1925,7 +2020,7 @@ function main { fi if [[ $(bc -l <<< "${cost_ada} < ${minPoolCost}") -eq 1 ]]; then println ERROR "\n${FG_RED}ERROR${NC}: cost set lower than allowed" - waitForInput && continue + waitToProceed && continue fi println DEBUG "\n# Pool Metadata\n" pool_meta_file="${POOL_FOLDER}/${pool_name}/poolmeta.json" @@ -1934,17 +2029,17 @@ function main { [[ -n "${json_url_enter}" ]] && meta_json_url="${json_url_enter}" if [[ ! "${meta_json_url}" =~ https?://.* || ${#meta_json_url} -gt 64 ]]; then println ERROR "${FG_RED}ERROR${NC}: invalid URL format or more than 64 chars in length" - waitForInput && continue + waitToProceed && continue fi metadata_done=false meta_tmp="${TMP_DIR}/url_poolmeta.json" if curl -sL -f -m ${CURL_TIMEOUT} -o "${meta_tmp}" ${meta_json_url} && jq -er . "${meta_tmp}" &>/dev/null; then - [[ $(wc -c <"${meta_tmp}") -gt 512 ]] && println ERROR "${FG_RED}ERROR${NC}: file at specified URL contain more than allowed 512b of data!" && waitForInput && continue - echo && jq -r . "${meta_tmp}" >&3 && echo - if ! jq -er .name "${meta_tmp}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: unable to get 'name' field from downloaded metadata file!" && waitForInput && continue; fi - if ! jq -er .ticker "${meta_tmp}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: unable to get 'ticker' field from downloaded metadata file!" && waitForInput && continue; fi - if ! jq -er .homepage "${meta_tmp}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: unable to get 'homepage' field from downloaded metadata file!" && waitForInput && continue; fi - if ! jq -er .description "${meta_tmp}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: unable to get 'description' field from downloaded metadata file!" && waitForInput && continue; fi + [[ $(wc -c <"${meta_tmp}") -gt 512 ]] && println ERROR "${FG_RED}ERROR${NC}: file at specified URL contain more than allowed 512b of data!" && waitToProceed && continue + echo && jq -r . "${meta_tmp}" && echo + if ! jq -er .name "${meta_tmp}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: unable to get 'name' field from downloaded metadata file!" && waitToProceed && continue; fi + if ! jq -er .ticker "${meta_tmp}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: unable to get 'ticker' field from downloaded metadata file!" && waitToProceed && continue; fi + if ! jq -er .homepage "${meta_tmp}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: unable to get 'homepage' field from downloaded metadata file!" && waitToProceed && continue; fi + if ! jq -er .description "${meta_tmp}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: unable to get 'description' field from downloaded metadata file!" && waitToProceed && continue; fi println DEBUG "Metadata exists at URL. Use existing data?" select_opt "[y] Yes" "[n] No" case $? in @@ -1965,26 +2060,26 @@ function main { [[ -n "${name_enter}" ]] && meta_name="${name_enter}" if [[ ${#meta_name} -gt 50 ]]; then println ERROR "${FG_RED}ERROR${NC}: Name cannot exceed 50 characters" - waitForInput && continue + waitToProceed && continue fi getAnswerAnyCust ticker_enter "Enter Pool's Ticker , should be between 3-5 characters (default: ${meta_ticker})" ticker_enter=${ticker_enter//[^[:alnum:]]/} [[ -n "${ticker_enter}" ]] && meta_ticker="${ticker_enter^^}" if [[ ${#meta_ticker} -lt 3 || ${#meta_ticker} -gt 5 ]]; then println ERROR "${FG_RED}ERROR${NC}: ticker must be between 3-5 characters" - waitForInput && continue + waitToProceed && continue fi getAnswerAnyCust desc_enter "Enter Pool's Description (default: ${meta_description})" [[ -n "${desc_enter}" ]] && meta_description="${desc_enter}" if [[ ${#meta_description} -gt 255 ]]; then println ERROR "${FG_RED}ERROR${NC}: Description cannot exceed 255 characters" - waitForInput && continue + waitToProceed && continue fi getAnswerAnyCust homepage_enter "Enter Pool's Homepage (default: ${meta_homepage})" [[ -n "${homepage_enter}" ]] && meta_homepage="${homepage_enter}" if [[ ! "${meta_homepage}" =~ https?://.* || ${#meta_homepage} -gt 64 ]]; then println ERROR "${FG_RED}ERROR${NC}: invalid URL format or more than 64 chars in length" - waitForInput && continue + waitToProceed && continue fi println DEBUG "\nOptionally set an extended metadata URL?" select_opt "[n] No" "[y] Yes" @@ -1995,7 +2090,7 @@ function main { [[ -n "${extended_enter}" ]] && meta_extended="${extended_enter}" if [[ ! "${meta_extended}" =~ https?://.* || ${#meta_extended} -gt 64 ]]; then println ERROR "${FG_RED}ERROR${NC}: invalid extended URL format or more than 64 chars in length" - waitForInput && continue + waitToProceed && continue else meta_extended_option=",\"extended\":\"${meta_extended}\"" fi @@ -2006,19 +2101,19 @@ function main { metadata_size=$(stat -c%s "${new_pool_meta_file}") if [[ ${metadata_size} -gt 512 ]]; then println ERROR "\n${FG_RED}ERROR${NC}: Total metadata size cannot exceed 512 chars in length, current length: ${metadata_size}" - waitForInput && continue + waitToProceed && continue else cp -f "${new_pool_meta_file}" "${pool_meta_file}" fi println DEBUG "\n${FG_YELLOW}Please host file ${pool_meta_file} as-is at ${meta_json_url}${NC}" - waitForInput "Press any key to proceed with registration after metadata file is uploaded" + waitToProceed "Press any key to proceed with registration after metadata file is uploaded" fi relay_output="" relay_array=() println DEBUG "\n# Pool Relay Registration" if [[ -f "${pool_config}" && $(jq '.relays | length' "${pool_config}") -gt 0 ]]; then println DEBUG "\nPrevious relay configuration:\n" - jq -r '["TYPE","ADDRESS","PORT"], (.relays[] | [.type //"-",.address //"-",.port //"-"]) | @tsv' "${pool_config}" | column -t >&3 + jq -r '["TYPE","ADDRESS","PORT"], (.relays[] | [.type //"-",.address //"-",.port //"-"]) | @tsv' "${pool_config}" | column -t println DEBUG "\nReuse previous relay configuration?" select_opt "[y] Yes" "[n] No" "[Esc] Cancel" case $? in @@ -2138,33 +2233,32 @@ function main { println ERROR "${FG_RED}ERROR${NC}: main/first pool owner can NOT be a hardware wallet!" println ERROR "Use a CLI wallet as owner with enough funds to pay for pool deposit and registration transaction fee" println ERROR "Add the hardware wallet as an additional multi-owner to the pool later in the pool registration wizard" - waitForInput "Unable to reuse old configuration, please set new owner(s) & reward wallet" && owner_wallets=() && reward_wallet="" && reuse_wallets='N' && break + waitToProceed "Unable to reuse old configuration, please set new owner(s) & reward wallet" && owner_wallets=() && reward_wallet="" && reuse_wallets='N' && break else hw_owner_wallets='Y'; fi ;; 2) if [[ ${op_mode} = "online" ]]; then println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted for wallet ${FG_GREEN}${wallet_name}${NC}, please decrypt before use!" - waitForInput && continue 2 + waitToProceed && continue 2 fi ;; 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet ${FG_GREEN}${wallet_name}${NC}!" - waitForInput "Did you mean to run in Hybrid mode? press any key to return home!" && continue 2 ;; + waitToProceed "Did you mean to run in Hybrid mode? press any key to return home!" && continue 2 ;; 4) if [[ ${wallet_name} != "${owner_wallets[0]}" && ! -f "${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" ]]; then # ignore if payment vkey is missing for multi-owner, only stake vkey important println ERROR "${FG_RED}ERROR${NC}: stake verification key missing from wallet ${FG_GREEN}${wallet_name}${NC}!" - waitForInput "Unable to reuse old configuration, please set new owner(s) & reward wallet" && owner_wallets=() && reward_wallet="" && reuse_wallets='N' && break + waitToProceed "Unable to reuse old configuration, please set new owner(s) & reward wallet" && owner_wallets=() && reward_wallet="" && reuse_wallets='N' && break fi ;; esac if [[ ${wallet_name} = "${owner_wallets[0]}" ]] && ! isWalletRegistered ${wallet_name}; then # make sure at least main owner is registered if [[ ${op_mode} = "hybrid" ]]; then println ERROR "\n${FG_RED}ERROR${NC}: wallet ${FG_GREEN}${wallet_name}${NC} not a registered wallet on chain and CNTools run in hybrid mode" println ERROR "Please first register main owner wallet to use in pool registration using 'Wallet >> Register'" - waitForInput && continue 2 + waitToProceed && continue 2 fi - getBaseAddress ${wallet_name} - getBalance ${base_addr} - if [[ ${assets[lovelace]} -eq 0 ]]; then + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -eq 0 ]]; then println ERROR "${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}, needed to pay for registration fee" - waitForInput && continue 2 + waitToProceed && continue 2 fi println DEBUG "# Wallet Registration Transaction" - if ! registerStakeWallet ${wallet_name}; then waitForInput && continue 2; fi + if ! registerStakeWallet ${wallet_name}; then waitToProceed && continue 2; fi fi done @@ -2174,7 +2268,7 @@ function main { 0) hw_reward_wallet='Y' ;; 4) if [[ ! -f "${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" ]]; then # ignore if payment vkey is missing for reward wallet, only stake vkey important println ERROR "${FG_RED}ERROR${NC}: stake verification key missing from reward wallet ${FG_GREEN}${wallet_name}${NC}!" - waitForInput "Unable to reuse old configuration, please set new owner(s) & reward wallet" && owner_wallets=() && reward_wallet="" && reuse_wallets='N' + waitToProceed "Unable to reuse old configuration, please set new owner(s) & reward wallet" && owner_wallets=() && reward_wallet="" && reuse_wallets='N' fi ;; esac fi @@ -2191,21 +2285,21 @@ function main { println DEBUG "# Select main ${FG_YELLOW}owner/pledge${NC} wallet (normal CLI wallet)" if [[ ${op_mode} = "online" ]]; then if ! selectWallet "delegate" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}"; then # ${wallet_name} populated by selectWallet function - [[ "${dir_name}" != "[Esc] Cancel" ]] && waitForInput; continue + [[ "${dir_name}" != "[Esc] Cancel" ]] && waitToProceed; continue fi getWalletType ${wallet_name} case $? in 0) println ERROR "${FG_RED}ERROR${NC}: main pool owner can NOT be a hardware wallet!" println ERROR "Use a CLI wallet as owner with enough funds to pay for pool deposit and registration transaction fee" println ERROR "Add the hardware wallet as an additional multi-owner to the pool later in the pool registration wizard" - waitForInput && continue ;; - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + waitToProceed && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "delegate" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} @@ -2214,28 +2308,20 @@ function main { if [[ ${op_mode} = "hybrid" ]]; then println ERROR "\n${FG_RED}ERROR${NC}: wallet ${FG_GREEN}${wallet_name}${NC} not a registered wallet on chain and CNTools run in hybrid mode" println ERROR "Please first register the main CLI wallet to use in pool registration using 'Wallet >> Register'" - waitForInput && continue + waitToProceed && continue fi - getBaseAddress ${wallet_name} - getBalance ${base_addr} - if [[ ${assets[lovelace]} -eq 0 ]]; then + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -eq 0 ]]; then println ERROR "${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}, needed to pay for registration fee" - waitForInput && continue + waitToProceed && continue fi println DEBUG "# Wallet Registration Transaction" - if ! registerStakeWallet ${wallet_name}; then waitForInput && continue; fi + if ! registerStakeWallet ${wallet_name}; then waitToProceed && continue; fi fi owner_wallets+=( "${wallet_name}" ) println DEBUG "Owner #1 : ${FG_GREEN}${wallet_name}${NC} added!" fi - getBaseAddress ${owner_wallets[0]} - getBalance ${base_addr} - if [[ ${assets[lovelace]} -eq 0 ]]; then - println ERROR "\n${FG_RED}ERROR${NC}: no funds available in owner wallet ${FG_GREEN}${owner_wallets[0]}${NC}" - waitForInput && continue - fi - if [[ ${reuse_wallets} = 'N' ]]; then println DEBUG "\nRegister a multi-owner pool (you need to have stake.vkey of any additional owner in a seperate wallet folder under $CNODE_HOME/priv/wallet)?" while true; do @@ -2248,10 +2334,10 @@ function main { 0) hw_owner_wallets='Y' ;; 2) if [[ ${op_mode} = "online" ]]; then println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted for wallet ${FG_GREEN}${wallet_name}${NC}, please decrypt before use!" - waitForInput && continue 2 + waitToProceed && continue 2 fi ;; 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet ${FG_GREEN}${wallet_name}${NC}!" - waitForInput "Did you mean to run in Hybrid mode? press any key to return home!" && continue 2 ;; + waitToProceed "Did you mean to run in Hybrid mode? press any key to return home!" && continue 2 ;; 4) if [[ ! -f "${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" ]]; then # ignore if payment vkey is missing println ERROR "${FG_RED}ERROR${NC}: stake verification key missing from wallet ${FG_GREEN}${wallet_name}${NC}!" println DEBUG "Add another owner?" && continue @@ -2275,14 +2361,14 @@ function main { case $? in 0) reward_wallet="${owner_wallets[0]}" ;; 1) if ! selectWallet "none" "${WALLET_STAKE_VK_FILENAME}" "${owner_wallets[0]}"; then # ${wallet_name} populated by selectWallet function - [[ "${dir_name}" != "[Esc] Cancel" ]] && waitForInput; continue + [[ "${dir_name}" != "[Esc] Cancel" ]] && waitToProceed; continue fi reward_wallet="${wallet_name}" getWalletType ${reward_wallet} case $? in 0) hw_reward_wallet='Y' ;; 4) if [[ ! -f "${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" ]]; then # ignore if payment vkey is missing - println ERROR "${FG_RED}ERROR${NC}: stake verification key missing from wallet ${FG_GREEN}${wallet_name}${NC}!" && waitForInput && continue + println ERROR "${FG_RED}ERROR${NC}: stake verification key missing from wallet ${FG_GREEN}${wallet_name}${NC}!" && waitToProceed && continue fi ;; esac ;; @@ -2290,6 +2376,12 @@ function main { esac fi + getWalletBalance ${owner_wallets[0]} true true false true + if [[ ${base_lovelace} -eq 0 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: no funds available in owner wallet ${FG_GREEN}${owner_wallets[0]}${NC}" + waitToProceed && continue + fi + multi_owner_output="" for wallet_name in "${owner_wallets[@]}"; do [[ "${wallet_name}" = "${owner_wallets[0]}" ]] && continue # skip main owner @@ -2326,16 +2418,20 @@ function main { if [[ ${isHWpool} = 'Y' ]]; then if ! unlockHWDevice "issue the opcert"; then return 1; fi println ACTION "cardano-hw-cli node issue-op-cert --kes-verification-key-file ${pool_hotkey_vk_file} --hw-signing-file ${pool_coldkey_sk_file} --operational-certificate-issue-counter-file ${pool_opcert_counter_file} --kes-period ${current_kes_period} --out-file ${pool_opcert_file}" - ! cardano-hw-cli node issue-op-cert \ + if ! stdout=$(cardano-hw-cli node issue-op-cert \ --kes-verification-key-file "${pool_hotkey_vk_file}" \ --hw-signing-file "${pool_coldkey_sk_file}" \ --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" \ --kes-period "${current_kes_period}" \ - --out-file "${pool_opcert_file}" \ - && return 1 + --out-file "${pool_opcert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during HW operational certificate creation!\n${stdout}" + return 1 + fi else println ACTION "${CCLI} ${NETWORK_ERA} node issue-op-cert --kes-verification-key-file ${pool_hotkey_vk_file} --cold-signing-key-file ${pool_coldkey_sk_file} --operational-certificate-issue-counter-file ${pool_opcert_counter_file} --kes-period ${current_kes_period} --out-file ${pool_opcert_file}" - ${CCLI} ${NETWORK_ERA} node issue-op-cert --kes-verification-key-file "${pool_hotkey_vk_file}" --cold-signing-key-file "${pool_coldkey_sk_file}" --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" --kes-period "${current_kes_period}" --out-file "${pool_opcert_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} node issue-op-cert --kes-verification-key-file "${pool_hotkey_vk_file}" --cold-signing-key-file "${pool_coldkey_sk_file}" --operational-certificate-issue-counter-file "${pool_opcert_counter_file}" --kes-period "${current_kes_period}" --out-file "${pool_opcert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during operational certificate creation!\n${stdout}"; waitToProceed && continue + fi fi fi @@ -2347,29 +2443,33 @@ function main { println DEBUG "${FG_LGRAY}${pool_hotkey_sk_file}${NC}" println DEBUG "${FG_LGRAY}${pool_opcert_file}${NC}" println DEBUG "${FG_LGRAY}${pool_saved_kes_start}${NC}" - waitForInput "press any key to continue" + waitToProceed "press any key to continue" fi fi println LOG "creating registration certificate" println ACTION "${CCLI} ${NETWORK_ERA} stake-pool registration-certificate --cold-verification-key-file ${pool_coldkey_vk_file} --vrf-verification-key-file ${pool_vrf_vk_file} --pool-pledge ${pledge_lovelace} --pool-cost ${cost_lovelace} --pool-margin ${margin_fraction} --pool-reward-account-verification-key-file ${reward_stake_vk_file} --pool-owner-stake-verification-key-file ${owner_stake_vk_file} ${multi_owner_output} --metadata-url ${meta_json_url} --metadata-hash \$\(${CCLI} ${NETWORK_ERA} stake-pool metadata-hash --pool-metadata-file ${pool_meta_file} \) ${relay_output} ${NETWORK_IDENTIFIER} --out-file ${pool_regcert_file}" - ${CCLI} ${NETWORK_ERA} stake-pool registration-certificate --cold-verification-key-file "${pool_coldkey_vk_file}" --vrf-verification-key-file "${pool_vrf_vk_file}" --pool-pledge ${pledge_lovelace} --pool-cost ${cost_lovelace} --pool-margin ${margin_fraction} --pool-reward-account-verification-key-file "${reward_stake_vk_file}" --pool-owner-stake-verification-key-file "${owner_stake_vk_file}" ${multi_owner_output} --metadata-url "${meta_json_url}" --metadata-hash "$(${CCLI} ${NETWORK_ERA} stake-pool metadata-hash --pool-metadata-file ${pool_meta_file} )" ${relay_output} ${NETWORK_IDENTIFIER} --out-file "${pool_regcert_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-pool registration-certificate --cold-verification-key-file "${pool_coldkey_vk_file}" --vrf-verification-key-file "${pool_vrf_vk_file}" --pool-pledge ${pledge_lovelace} --pool-cost ${cost_lovelace} --pool-margin ${margin_fraction} --pool-reward-account-verification-key-file "${reward_stake_vk_file}" --pool-owner-stake-verification-key-file "${owner_stake_vk_file}" ${multi_owner_output} --metadata-url "${meta_json_url}" --metadata-hash "$(${CCLI} ${NETWORK_ERA} stake-pool metadata-hash --pool-metadata-file ${pool_meta_file} )" ${relay_output} ${NETWORK_IDENTIFIER} --out-file "${pool_regcert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake pool registration certificate creation!\n${stdout}"; waitToProceed && continue + fi delegate_owner_wallet='N' if [[ ${SUBCOMMAND} = "register" ]]; then if [[ ${hw_owner_wallets} = 'Y' || ${hw_reward_wallet} = 'Y' || ${isHWpool} = 'Y' ]]; then println DEBUG "\n${FG_BLUE}INFO${NC}: hardware wallet included as reward or multi-owner or hardware pool, automatic owner/reward wallet delegation disabled" println DEBUG "${FG_BLUE}INFO${NC}: ${FG_YELLOW}please manually delegate all wallets to the pool!!!${NC}" - waitForInput "press any key to continue" + waitToProceed "press any key to continue" else println LOG "creating delegation certificate for main owner wallet" println ACTION "${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate --stake-verification-key-file ${owner_stake_vk_file} --cold-verification-key-file ${pool_coldkey_vk_file} --out-file ${owner_delegation_cert_file}" - ${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate --stake-verification-key-file "${owner_stake_vk_file}" --cold-verification-key-file "${pool_coldkey_vk_file}" --out-file "${owner_delegation_cert_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate --stake-verification-key-file "${owner_stake_vk_file}" --cold-verification-key-file "${pool_coldkey_vk_file}" --out-file "${owner_delegation_cert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake delegation certificate creation!\n${stdout}"; waitToProceed && continue + fi delegate_owner_wallet='Y' if [[ "${owner_wallets[0]}" != "${reward_wallet}" ]]; then println DEBUG "\n${FG_BLUE}INFO${NC}: reward wallet not the same as owner, automatic reward wallet delegation disabled" println DEBUG "${FG_BLUE}INFO${NC}: ${FG_YELLOW}please manually delegate reward wallet to the pool!!!${NC}" - waitForInput "press any key to continue" + waitToProceed "press any key to continue" fi fi fi @@ -2394,7 +2494,7 @@ function main { else [[ -f "${pool_regcert_file}.tmp" ]] && mv -f "${pool_regcert_file}.tmp" "${pool_regcert_file}" # restore reg cert backup fi - [[ $rc -eq 1 ]] && waitForInput && continue + [[ $rc -eq 1 ]] && waitToProceed && continue fi # Save pool config @@ -2420,7 +2520,7 @@ function main { echo if [[ ${op_mode} = "online" ]]; then getBaseAddress ${owner_wallets[0]} - if ! verifyTx ${base_addr}; then waitForInput && continue; fi + if ! verifyTx ${base_addr}; then waitToProceed && continue; fi echo if [[ ${SUBCOMMAND} = "register" ]]; then println "Pool ${FG_GREEN}${pool_name}${NC} successfully registered!" @@ -2448,13 +2548,30 @@ function main { echo if [[ ${op_mode} = "online" ]]; then total_pledge=0 - for wallet_name in "${owner_wallets[@]}"; do - getBaseAddress ${wallet_name} - getBalance ${base_addr} - total_pledge=$(( total_pledge + assets[lovelace] )) - getRewards ${wallet_name} - [[ ${reward_lovelace} -gt 0 ]] && total_pledge=$(( total_pledge + reward_lovelace )) - done + if [[ -n ${KOIOS_API} ]]; then + addr_list=() + reward_addr_list=() + for wallet_name in "${owner_wallets[@]}"; do + getBaseAddress ${wallet_name} && addr_list+=(${base_addr}) + getRewardAddress ${wallet_name} && reward_addr_list+=(${reward_addr}) + done + [[ ${#addr_list[@]} -gt 0 ]] && getBalanceKoios false + [[ ${#reward_addr_list[@]} -gt 0 ]] && getRewardInfoKoios + for key in "${!assets[@]}"; do + [[ ${key} = *lovelace ]] && total_pledge=$(( total_pledge + assets[${key}] )) + done + for value in "${rewards_available[@]}"; do + [[ ${value} -gt 0 ]] && total_pledge=$(( total_pledge + value )) + done + else + for wallet_name in "${owner_wallets[@]}"; do + getBaseAddress ${wallet_name} + getBalance ${base_addr} + total_pledge=$(( total_pledge + assets[lovelace] )) + getWalletRewards ${wallet_name} + [[ ${reward_lovelace} -gt 0 ]] && total_pledge=$(( total_pledge + reward_lovelace )) + done + fi println DEBUG "${FG_BLUE}INFO${NC}: Total balance in ${FG_LBLUE}${#owner_wallets[@]}${NC} owner/pledge wallet(s) are: ${FG_LBLUE}$(formatLovelace ${total_pledge})${NC} ADA" if [[ ${total_pledge} -lt ${pledge_lovelace} ]]; then println ERROR "${FG_YELLOW}Not enough funds in owner/pledge wallet(s) to meet set pledge, please manually verify!!!${NC}" @@ -2467,18 +2584,18 @@ function main { println DEBUG "${FG_BLUE}INFO${NC}: please verify that all owner/reward wallets are delegated to the pool, if not do so!" fi fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### retire) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> POOL >> RETIRE" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitForInput && continue - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available to pay for pool de-registration!${NC}" && waitForInput && continue + [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available to pay for pool de-registration!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -2487,28 +2604,27 @@ function main { if [[ ${op_mode} = "online" ]]; then selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getPoolType ${pool_name} case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: signing keys missing from pool!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: signing keys missing from pool!" && waitToProceed && continue ;; esac else selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getPoolType ${pool_name} fi echo epoch=$(getEpoch) - poolRetireMaxEpoch=$(jq -r '.poolRetireMaxEpoch' <<< "${PROT_PARAMS}") println DEBUG "Current epoch: ${FG_LBLUE}${epoch}${NC}" epoch_start=$((epoch + 1)) - epoch_end=$((epoch + poolRetireMaxEpoch)) + epoch_end=$((epoch + POOL_RETIRE_MAX_EPOCH)) println DEBUG "earliest epoch to retire pool is ${FG_LBLUE}${epoch_start}${NC} and latest ${FG_LBLUE}${epoch_end}${NC}" echo getAnswerAnyCust epoch_enter "Enter epoch in which to retire pool (blank for ${epoch_start})" @@ -2516,38 +2632,33 @@ function main { echo if [[ ${epoch_enter} -lt ${epoch_start} || ${epoch_enter} -gt ${epoch_end} ]]; then println ERROR "${FG_RED}ERROR${NC}: epoch invalid, valid range: ${epoch_start}-${epoch_end}" - waitForInput && continue + waitToProceed && continue fi println DEBUG "# Select wallet for pool de-registration transaction fee" if [[ ${op_mode} = "online" ]]; then selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for pool de-registration transaction fee!" && waitForInput && continue ;; - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for pool de-registration transaction fee!" && waitToProceed && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for pool de-registration transaction fee!" && waitForInput && continue ;; + 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for pool de-registration transaction fee!" && waitToProceed && continue ;; esac fi - getBaseAddress ${wallet_name} - getPayAddress ${wallet_name} - getBalance ${base_addr} - base_lovelace=${assets[lovelace]} - getBalance ${pay_addr} - pay_lovelace=${assets[lovelace]} + getWalletBalance ${wallet_name} true true true true if [[ ${pay_lovelace} -gt 0 && ${base_lovelace} -gt 0 ]]; then # Both payment and base address available with funds, let user choose what to use println DEBUG "\n# Select wallet address to use" @@ -2557,47 +2668,51 @@ function main { fi select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" case $? in - 0) addr="${base_addr}" ;; - 1) addr="${pay_addr}" ;; + 0) addr="${base_addr}"; lovelace=${base_lovelace} ;; + 1) addr="${pay_addr}"; lovelace=${pay_lovelace} ;; 2) continue ;; esac elif [[ ${pay_lovelace} -gt 0 ]]; then addr="${pay_addr}" + lovelace=${pay_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then println DEBUG "\n$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" fi elif [[ ${base_lovelace} -gt 0 ]]; then addr="${base_addr}" + lovelace=${base_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then println DEBUG "\n$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "\n${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" - waitForInput && continue + waitToProceed && continue fi pool_deregcert_file="${POOL_FOLDER}/${pool_name}/${POOL_DEREGCERT_FILENAME}" pool_regcert_file="${POOL_FOLDER}/${pool_name}/${POOL_REGCERT_FILENAME}" println LOG "creating de-registration cert" println ACTION "${CCLI} ${NETWORK_ERA} stake-pool deregistration-certificate --cold-verification-key-file ${pool_coldkey_vk_file} --epoch ${epoch_enter} --out-file ${pool_deregcert_file}" - ${CCLI} ${NETWORK_ERA} stake-pool deregistration-certificate --cold-verification-key-file ${pool_coldkey_vk_file} --epoch ${epoch_enter} --out-file ${pool_deregcert_file} + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-pool deregistration-certificate --cold-verification-key-file ${pool_coldkey_vk_file} --epoch ${epoch_enter} --out-file ${pool_deregcert_file} 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake pool deregistration certificate creation!\n${stdout}"; waitToProceed && continue + fi echo if ! deRegisterPool; then - waitForInput && continue + waitToProceed && continue fi [[ -f "${pool_regcert_file}" ]] && rm -f ${pool_regcert_file} # delete registration cert echo - if ! verifyTx ${addr}; then waitForInput && continue; fi + if ! verifyTx ${addr}; then waitToProceed && continue; fi echo println "Pool ${FG_GREEN}${pool_name}${NC} set to be retired in epoch ${FG_LBLUE}${epoch_enter}${NC}" println "Pool deposit will be returned to owner reward address after its retired" - waitForInput && continue + waitToProceed && continue ;; ################################################################### list) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> POOL >> LIST" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue current_epoch=$(getEpoch) while IFS= read -r -d '' pool; do echo @@ -2606,7 +2721,7 @@ function main { pool_regcert_file="${pool}/${POOL_REGCERT_FILENAME}" isPoolRegistered "${pool_name}" case $? in - 0) println "ERROR" "${FG_RED}KOIOS_API ERROR${NC}: ${error_msg}" && waitForInput && continue ;; + 0) println "ERROR" "${FG_RED}KOIOS_API ERROR${NC}: ${error_msg}" && waitToProceed && continue ;; 1) pool_registered="${FG_RED}NO${NC}" ;; 2) pool_registered="${FG_GREEN}YES${NC}" ;; 3) if [[ ${current_epoch} -lt ${p_retiring_epoch} ]]; then @@ -2614,6 +2729,7 @@ function main { else pool_registered="${FG_RED}NO${NC} - Retired in epoch ${FG_LBLUE}${p_retiring_epoch}${NC}" fi ;; + 4) pool_registered="${FG_RED}NO${NC} - Retired in epoch ${FG_LBLUE}${p_retiring_epoch}${NC}" ;; esac enc_files=$(find "${pool}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c) if [[ ${enc_files} -gt 0 ]]; then @@ -2625,7 +2741,7 @@ function main { [[ -n ${pool_id_bech32} ]] && println "$(printf "%-21s : ${FG_LGRAY}%s${NC}" "ID (bech32)" "${pool_id_bech32}")" println "$(printf "%-21s : %s" "Registered" "${pool_registered}")" unset pool_kes_start - if [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then getNodeMetrics else [[ -f "${pool}/${POOL_CURRENT_KES_START}" ]] && pool_kes_start="$(cat "${pool}/${POOL_CURRENT_KES_START}")" @@ -2647,7 +2763,7 @@ function main { fi done < <(find "${POOL_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) echo - waitForInput && continue + waitToProceed && continue ;; ################################################################### show) clear @@ -2655,47 +2771,50 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> POOL >> SHOW" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, locally saved info shown!" fi tput sc selectPool "all" "${POOL_ID_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac current_epoch=$(getEpoch) getPoolID ${pool_name} tput rc && tput ed - if [[ ${CNTOOLS_MODE} = "CONNECTED" && -z ${KOIOS_API} ]]; then - println DEBUG "Koios API disabled/unreachable, do you want to proceed querying pool parameters from node?" - println DEBUG "This is a heavy process and requiring several gigabytes of memory on MainNet." - select_opt "[y] Yes" "[n] No, abort" - [[ $? -eq 1 ]] && continue + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then tput sc && println DEBUG "Quering pool parameters from node, can take a while...\n" println ACTION "${CCLI} ${NETWORK_ERA} query pool-params --stake-pool-id ${pool_id_bech32} ${NETWORK_IDENTIFIER}" if ! pool_params=$(${CCLI} ${NETWORK_ERA} query pool-params --stake-pool-id ${pool_id_bech32} ${NETWORK_IDENTIFIER} 2>&1); then tput rc && tput ed println ERROR "${FG_RED}ERROR${NC}: pool-params query failed: ${pool_params}" - waitForInput && continue + waitToProceed && continue fi tput rc && tput ed fi if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then pool_registered="${FG_LGRAY}status unavailable in offline mode${NC}" - elif [[ -z ${KOIOS_API} ]]; then - ledger_pParams=$(jq -r '.poolParams // empty' <<< ${pool_params}) - ledger_fPParams=$(jq -r '.futurePoolParams // empty' <<< ${pool_params}) - ledger_retiring=$(jq -r '.retiring // empty' <<< ${pool_params}) + elif [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + ledger_pParams=$(jq -r '.[].poolParams // empty' <<< ${pool_params}) + ledger_fPParams=$(jq -r '.[].futurePoolParams // empty' <<< ${pool_params}) + ledger_retiring=$(jq -r '.[].retiring // empty' <<< ${pool_params}) [[ -z ${ledger_retiring} ]] && p_retiring_epoch=0 || p_retiring_epoch=${ledger_retiring} [[ -z "${ledger_fPParams}" ]] && ledger_fPParams="${ledger_pParams}" [[ -n "${ledger_pParams}" ]] && pool_registered="${FG_GREEN}YES${NC}" || pool_registered="${FG_RED}NO${NC}" + if [[ ${p_retiring_epoch} -gt 0 ]]; then + if [[ ${current_epoch} -lt ${p_retiring_epoch} ]]; then + pool_registered="${FG_YELLOW}YES${NC} - Retiring in epoch ${FG_LBLUE}${p_retiring_epoch}${NC}" + else + pool_registered="${FG_RED}NO${NC} - Retired in epoch ${FG_LBLUE}${p_retiring_epoch}${NC}" + fi + fi else - println DEBUG "\n${FG_YELLOW}> Querying Koios API for pool information (some data can have a delay of up to 10min)${NC}" + println OFF "\n${FG_YELLOW}> Querying Koios API for pool information (some data can have a small delay)${NC}" isPoolRegistered ${pool_name} # variables set in isPoolRegistered [pool_info, error_msg, p_] case $? in - 0) println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${error_msg}" && waitForInput && continue ;; + 0) println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${error_msg}" && waitToProceed && continue ;; 1) pool_registered="${FG_RED}NO${NC}" ;; 2) pool_registered="${FG_GREEN}YES${NC}" ;; 3) if [[ ${current_epoch} -lt ${p_retiring_epoch} ]]; then @@ -2703,6 +2822,7 @@ function main { else pool_registered="${FG_RED}NO${NC} - Retired in epoch ${FG_LBLUE}${p_retiring_epoch}${NC}" fi ;; + 4) pool_registered="${FG_RED}NO${NC} - Retired in epoch ${FG_LBLUE}${p_retiring_epoch}${NC}" ;; esac fi echo @@ -2742,9 +2862,11 @@ function main { println "$(printf " %-19s : ${FG_LGRAY}%s${NC}" "Description" "$(jq -r .description "$TMP_DIR/url_poolmeta.json")")" println "$(printf " %-19s : ${FG_LGRAY}%s${NC}" "URL" "${meta_json_url}")" println ACTION "${CCLI} ${NETWORK_ERA} stake-pool metadata-hash --pool-metadata-file ${TMP_DIR}/url_poolmeta.json" - meta_hash_url="$( ${CCLI} ${NETWORK_ERA} stake-pool metadata-hash --pool-metadata-file "${TMP_DIR}/url_poolmeta.json" )" + if ! meta_hash_url=$(${CCLI} ${NETWORK_ERA} stake-pool metadata-hash --pool-metadata-file "${TMP_DIR}/url_poolmeta.json" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during metadata hash creation!\n${meta_hash_url}"; waitToProceed && continue + fi println "$(printf " %-19s : ${FG_LGRAY}%s${NC}" "Hash URL" "${meta_hash_url}")" - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then meta_hash_pParams=$(jq -r '.metadata.hash //empty' <<< "${ledger_pParams}") meta_hash_fPParams=$(jq -r '.metadata.hash //empty' <<< "${ledger_fPParams}") else @@ -2783,7 +2905,7 @@ function main { done < <(jq -r '.relays[] | "\(.type) \(.address) \(.port)"' "${pool_config}") elif [[ ${pool_registered} = *YES* ]]; then # get pledge - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then pParams_pledge=$(jq -r '.pledge //0' <<< "${ledger_pParams}") fPParams_pledge=$(jq -r '.pledge //0' <<< "${ledger_fPParams}") else @@ -2800,7 +2922,7 @@ function main { [[ -n ${KOIOS_API} ]] && getPriceString ${p_live_pledge} && println "$(printf "%-21s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Live Pledge" "$(formatLovelace "${p_live_pledge}")")" # get margin - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then pParams_margin=$(LC_NUMERIC=C printf "%.4f" "$(jq -r '.margin //0' <<< "${ledger_pParams}")") fPParams_margin=$(LC_NUMERIC=C printf "%.4f" "$(jq -r '.margin //0' <<< "${ledger_fPParams}")") else @@ -2814,7 +2936,7 @@ function main { fi # get fixed cost - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then pParams_cost=$(jq -r '.cost //0' <<< "${ledger_pParams}") fPParams_cost=$(jq -r '.cost //0' <<< "${ledger_fPParams}") else @@ -2828,7 +2950,7 @@ function main { fi # get relays - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then relays=$(jq -c '.relays[] //empty' <<< "${ledger_fPParams}") if [[ ${relays} != $(jq -c '.relays[] //empty' <<< "${ledger_pParams}") ]]; then println "$(printf "%-23s ${FG_YELLOW}%s${NC}" "" "Relay(s) updated, showing latest registered")" @@ -2840,7 +2962,7 @@ function main { if [[ -n "${relays}" ]]; then while read -r relay; do relay_addr=""; relay_port="" - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then relay_addr="$(jq -r '."single host address".IPv4 //empty' <<< ${relay})" if [[ -n ${relay_addr} ]]; then relay_port="$(jq -r '."single host address".port //empty' <<< ${relay})" @@ -2881,7 +3003,7 @@ function main { fi # get owners - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then owners=$(jq -rc '.owners[] // empty' <<< "${ledger_fPParams}") if [[ ${owners} != $(jq -rc '.owners[] // empty' <<< "${ledger_pParams}") ]]; then println "$(printf "%-23s ${FG_YELLOW}%s${NC}" "" "Owner(s) updated, showing latest registered")" @@ -2902,7 +3024,7 @@ function main { done <<< "${owners}" # get reward account - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then reward_account=$(jq -r '.rewardAccount.credential."key hash" // empty' <<< "${ledger_fPParams}") if [[ ${reward_account} != $(jq -r '.rewardAccount.credential."key hash" // empty' <<< "${ledger_pParams}") ]]; then println "$(printf "%-23s ${FG_YELLOW}%s${NC}" "" "Reward account updated, showing latest registered")" @@ -2920,7 +3042,7 @@ function main { fi fi - if [[ -z ${KOIOS_API} ]]; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then # get stake distribution println "ACTION" "LC_NUMERIC=C printf %.10f \$(${CCLI} ${NETWORK_ERA} query stake-distribution ${NETWORK_IDENTIFIER} | grep ${pool_id_bech32} | tr -s ' ' | cut -d ' ' -f 2))" stake_pct=$(fractionToPCT "$(LC_NUMERIC=C printf "%.10f" "$(${CCLI} ${NETWORK_ERA} query stake-distribution ${NETWORK_IDENTIFIER} | grep "${pool_id_bech32}" | tr -s ' ' | cut -d ' ' -f 2)")") @@ -2940,7 +3062,7 @@ function main { if [[ -n ${KOIOS_API} ]]; then [[ ${p_op_cert_counter} != null ]] && kes_counter_str="${FG_LBLUE}${p_op_cert_counter}${FG_LGRAY} - use counter ${FG_LBLUE}$((p_op_cert_counter+1))${FG_LGRAY} for rotation in offline mode.${NC}" || kes_counter_str="${FG_LGRAY}No blocks minted so far with active operational certificate. Use counter ${FG_LBLUE}0${FG_LGRAY} for rotation in offline mode.${NC}" println "$(printf "%-21s : %s" "KES counter" "${kes_counter_str}")" - elif [[ ${CNTOOLS_MODE} = "CONNECTED" ]]; then + elif [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then pool_opcert_file="${POOL_FOLDER}/${pool_name}/${POOL_OPCERT_FILENAME}" println ACTION "${CCLI} ${NETWORK_ERA} query kes-period-info --op-cert-file ${pool_opcert_file} ${NETWORK_IDENTIFIER}" if ! kes_period_info=$(${CCLI} ${NETWORK_ERA} query kes-period-info --op-cert-file "${pool_opcert_file}" ${NETWORK_IDENTIFIER}); then @@ -2973,7 +3095,7 @@ function main { fi fi fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### rotate) clear @@ -2984,25 +3106,25 @@ function main { if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, please grab correct counter value from online node using pool info!\n" fi - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue println DEBUG "# Select pool to rotate KES keys on" selectPool "all" "${POOL_COLDKEY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then getAnswerAnyCust new_counter "Enter new counter number" if ! isNumber ${new_counter}; then println ERROR "\n${FG_RED}ERROR${NC}: not a number" - waitForInput && continue + waitToProceed && continue fi if ! rotatePoolKeys ${new_counter}; then - waitForInput && continue + waitToProceed && continue fi else if ! rotatePoolKeys; then - waitForInput && continue + waitToProceed && continue fi fi echo @@ -3017,7 +3139,7 @@ function main { echo fi println DEBUG "Restart your pool node for changes to take effect" - waitForInput && continue + waitToProceed && continue ;; ################################################################### decrypt) clear @@ -3025,11 +3147,11 @@ function main { println " >> POOL >> DECRYPT" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue println DEBUG "# Select pool to decrypt" selectPool "encrypted" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac filesUnlocked=0 @@ -3046,7 +3168,7 @@ function main { println "# Decrypting GPG encrypted pool files" if ! getPasswordCust; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" - waitForInput && continue + waitToProceed && continue fi while IFS= read -r -d '' file; do decryptFile "${file}" "${password}" && \ @@ -3064,7 +3186,7 @@ function main { println DEBUG "${FG_YELLOW}Pool files are now unprotected${NC}" println DEBUG "Use 'POOL >> ENCRYPT / LOCK' to re-lock" fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### encrypt) clear @@ -3072,11 +3194,11 @@ function main { println " >> POOL >> ENCRYPT" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue println DEBUG "# Select pool to encrypt" selectPool "encrypted" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac filesLocked=0 @@ -3086,7 +3208,7 @@ function main { println DEBUG "# Encrypting sensitive pool keys with GPG" if ! getPasswordCust confirm; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" - waitForInput && continue + waitToProceed && continue fi keyFiles=( "${POOL_FOLDER}/${pool_name}/${POOL_COLDKEY_SK_FILENAME}" @@ -3102,7 +3224,7 @@ function main { else echo println DEBUG "${FG_YELLOW}NOTE${NC}: found GPG encrypted files in folder, please decrypt/unlock pool files before encrypting" - waitForInput && continue + waitToProceed && continue fi echo println DEBUG "# Write protecting all pool files with 400 permission and if enabled 'chattr +i'" @@ -3120,7 +3242,7 @@ function main { println DEBUG "${FG_BLUE}INFO${NC}: pool files are now protected" println DEBUG "Use 'POOL >> DECRYPT / UNLOCK' to unlock" fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### vote) clear @@ -3131,14 +3253,14 @@ function main { if ! cmdAvailable "xxd"; then myExit 1 "xxd is a hexdump tool to generate the CBOR encoded poll answer" fi - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitForInput && continue - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available to pay for poll ballot casts!${NC}" && waitForInput && continue + [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available to pay for poll ballot casts!${NC}" && waitToProceed && continue if [[ -z ${KOIOS_API} ]]; then - echo && println ERROR "${FG_YELLOW}Koios API required!${NC}" && waitForInput && continue + echo && println ERROR "${FG_YELLOW}Koios API required!${NC}" && waitToProceed && continue fi if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -3148,7 +3270,9 @@ function main { NETWORK_NAME_LOWER=$(echo "$NETWORK_NAME" | awk '{print tolower($0)}') println LOG "Query ${NETWORK_NAME} polls ..." println ACTION "curl -sSL -f -H \"Content-Type: application/json\" ${CIP0094_POLL_URL}" - ! polls=$(curl -sSL -f -H "Content-Type: application/json" "${CIP0094_POLL_URL}" | jq -r .networks.${NETWORK_NAME_LOWER} 2>&1) && waitForInput && continue + if ! polls=$(curl -sSL -f -H "Content-Type: application/json" "${CIP0094_POLL_URL}" | jq -r .networks.${NETWORK_NAME_LOWER} 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during CIP0094 poll query!\n${polls}"; waitToProceed && continue + fi poll_index=$(echo $polls | jq '. | length') if [[ "$poll_index" -gt 0 ]]; then poll_index_act=0 @@ -3182,18 +3306,18 @@ function main { if [[ ${op_mode} = "online" ]]; then selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getPoolType ${pool_name} case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: signing keys missing from pool!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: signing keys missing from pool!" && waitToProceed && continue ;; esac else selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getPoolType ${pool_name} @@ -3203,32 +3327,27 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for transaction fee!" && waitForInput && continue ;; - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for transaction fee!" && waitToProceed && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for transaction fee!" && waitForInput && continue ;; + 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for transaction fee!" && waitToProceed && continue ;; esac fi - getBaseAddress ${wallet_name} - getPayAddress ${wallet_name} - getBalance ${base_addr} - base_lovelace=${assets[lovelace]} - getBalance ${pay_addr} - pay_lovelace=${assets[lovelace]} + getWalletBalance ${wallet_name} true true true true if [[ ${pay_lovelace} -gt 0 && ${base_lovelace} -gt 0 ]]; then # Both payment and base address available with funds, let user choose what to use println DEBUG "\n# Select wallet address to use" @@ -3238,28 +3357,32 @@ function main { fi select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" case $? in - 0) addr="${base_addr}" ;; - 1) addr="${pay_addr}" ;; + 0) addr="${base_addr}"; lovelace=${base_lovelace} ;; + 1) addr="${pay_addr}"; lovelace=${pay_lovelace} ;; 2) continue ;; esac elif [[ ${pay_lovelace} -gt 0 ]]; then addr="${pay_addr}" + lovelace=${pay_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then println DEBUG "\n$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" fi elif [[ ${base_lovelace} -gt 0 ]]; then addr="${base_addr}" + lovelace=${base_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then println DEBUG "\n$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "\n${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" - waitForInput && continue + waitToProceed && continue fi echo echo "Query ${NETWORK_NAME} ${poll_txId} metadata from Koios API..." println ACTION "curl -sSL -f -X POST -H \"Content-Type: application/json\" -d '{\"_tx_hashes\":[\"${poll_txId}\"]}' ${KOIOS_API}/tx_metadata" - ! tx=$(curl -sSL -f -X POST -H "Content-Type: application/json" -d '{"_tx_hashes":["'${poll_txId}'"]}' "${KOIOS_API}/tx_metadata" 2>&1) && error_msg=${tx} && waitForInput && continue + if ! tx=$(curl -sSL -f -X POST -H "Content-Type: application/json" -d '{"_tx_hashes":["'${poll_txId}'"]}' "${KOIOS_API}/tx_metadata" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during Koios tx metadata query!\n${tx}"; waitToProceed && continue + fi tx_meta=$(echo ${tx} | jq -r ".[0].metadata.\"94\" // empty" 2> /dev/null ) if [[ ! -z ${tx_meta} ]]; then echo "OK: Metadata has a CIP-0094 label" @@ -3276,7 +3399,7 @@ function main { #Add QuestionStrings questionStrLength=$(jq -r ".\"0\" | length" <<< ${tx_meta} 2> /dev/null) if [[ ${questionStrLength} -eq 0 ]]; then - echo -e "\n${FG_RED}ERROR - No question string included\n${NC}" && waitForInput && continue + echo -e "\n${FG_RED}ERROR - No question string included\n${NC}" && waitToProceed && continue fi cborStr+=$(to_cbor "array" ${questionStrLength}) #array with the number of entries for (( tmpCnt=0; tmpCnt<${questionStrLength}; tmpCnt++ )) @@ -3289,7 +3412,7 @@ function main { #Add OptionsStrings optionsStrLength=$(jq -r ".\"1\" | length" <<< ${tx_meta} 2> /dev/null) if [[ ${optionsStrLength} -eq 0 ]]; then - echo -e "\n${FG_RED}ERROR - No option strings included\n${NC}" && waitForInput && continue + echo -e "\n${FG_RED}ERROR - No option strings included\n${NC}" && waitToProceed && continue fi cborStr+=$(to_cbor "array" ${optionsStrLength}) #array with the number of options @@ -3320,7 +3443,7 @@ function main { do read -p $'Please indicate an answer (by index): ' answer if [[ ${answer} == "" ]]; then - echo && println "${FG_YELLOW}No answer${NC}" && waitForInput && continue + echo && println "${FG_YELLOW}No answer${NC}" && waitToProceed && continue fi done echo @@ -3351,14 +3474,12 @@ function main { DEFAULTEDITOR="$(command -v nano &>/dev/null && echo 'nano' || echo 'vi')" println OFF "\nA maximum of 64 characters(bytes) is allowed per line." println OFF "${FG_YELLOW}Please don't change default file path when saving.${NC}" - exec >&6 2>&7 # normal stdout/stderr - waitForInput "press any key to open '${FG_LGRAY}${DEFAULTEDITOR}${NC}' text editor" + waitToProceed "press any key to open '${FG_LGRAY}${DEFAULTEDITOR}${NC}' text editor" ${DEFAULTEDITOR} "${metafile}" - exec >&8 2>&9 # custom stdout/stderr if [[ ! -f "${metafile}" ]]; then println ERROR "${FG_RED}ERROR${NC}: file not found" println ERROR "File: ${FG_LGRAY}${metafile}${NC}" - waitForInput && continue + waitToProceed && continue fi tput cuu 4 && tput ed if [[ ! -s ${metafile} ]]; then @@ -3380,27 +3501,27 @@ function main { error="${FG_RED}ERROR${NC}: ${tx_msg}" && break fi done < "${metafile}" - [[ -n ${error} ]] && println ERROR "${error}" && waitForInput && continue + [[ -n ${error} ]] && println ERROR "${error}" && waitToProceed && continue jq -c . <<< "${tx_msg}" > "${metafile}" - jq -r . "${metafile}" >&3 && echo + jq -r . "${metafile}" && echo println LOG "Transaction message: ${tx_msg}" fi ;; esac if ! submitPoll; then - waitForInput && continue + waitToProceed && continue fi else echo && println "${FG_YELLOW}Cannot find valid metadata for this transaction${NC}" - waitForInput && continue + waitToProceed && continue fi else echo && println "${FG_YELLOW}There are currently no active polls in ${NETWORK_NAME}${NC}" - waitForInput && continue + waitToProceed && continue fi else echo && println "${FG_YELLOW}There are currently no polls in ${NETWORK_NAME}${NC}" - waitForInput && continue + waitToProceed && continue fi ;; ################################################################### esac # pool sub OPERATION @@ -3435,18 +3556,18 @@ function main { [[ -z "${offline_tx}" ]] && continue if [[ ! -f "${offline_tx}" ]]; then println ERROR "${FG_RED}ERROR${NC}: file not found: ${offline_tx}" - waitForInput && continue + waitToProceed && continue elif ! offlineJSON=$(jq -erc . "${offline_tx}"); then println ERROR "${FG_RED}ERROR${NC}: invalid JSON file: ${offline_tx}" - waitForInput && continue + waitToProceed && continue fi - if ! otx_type="$(jq -er '.type' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'type' not found in: ${offline_tx}" && waitForInput && continue; fi - if ! otx_date_created="$(jq -er '."date-created"' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'date-created' not found in: ${offline_tx}" && waitForInput && continue; fi - if ! otx_date_expire="$(jq -er '."date-expire"' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'date-expire' not found in: ${offline_tx}" && waitForInput && continue; fi - if ! otx_txFee=$(jq -er '.txFee' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txFee' not found in: ${offline_tx}" && waitForInput && continue; fi - if ! otx_txBody=$(jq -er '.txBody' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txBody' not found in: ${offline_tx}" && waitForInput && continue; fi + if ! otx_type="$(jq -er '.type' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'type' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_date_created="$(jq -er '."date-created"' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'date-created' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_date_expire="$(jq -er '."date-expire"' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'date-expire' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_txFee=$(jq -er '.txFee' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txFee' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_txBody=$(jq -er '.txBody' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txBody' not found in: ${offline_tx}" && waitToProceed && continue; fi echo -e "${otx_txBody}" > "${TMP_DIR}"/tx.raw - [[ $(jq -r '."signed-txBody" | length' <<< ${offlineJSON}) -gt 0 ]] && println ERROR "${FG_RED}ERROR${NC}: transaction already signed, please submit transaction to complete!" && waitForInput && continue + [[ $(jq -r '."signed-txBody" | length' <<< ${offlineJSON}) -gt 0 ]] && println ERROR "${FG_RED}ERROR${NC}: transaction already signed, please submit transaction to complete!" && waitToProceed && continue println DEBUG "Transaction type : ${FG_GREEN}${otx_type}${NC}" if wallet_name=$(jq -er '."wallet-name"' <<< ${offlineJSON}); then println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA, payed by ${FG_GREEN}${wallet_name}${NC}" @@ -3458,7 +3579,7 @@ function main { if [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]]; then println DEBUG "Expire : ${FG_RED}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" println ERROR "\n${FG_RED}ERROR${NC}: offline transaction expired! please create a new one with long enough Time To Live (TTL)" - waitForInput && continue + waitToProceed && continue else println DEBUG "Expire : ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" fi @@ -3545,34 +3666,38 @@ function main { elif [[ ${otx_signing_name} = "Asset "* ]]; then dialog_start_path="${POOL_FOLDER}" else dialog_start_path="${WALLET_FOLDER}"; fi fileDialog "\nEnter path to ${otx_signing_name}" "${dialog_start_path}/" - [[ ! -f "${file}" ]] && println ERROR "${FG_RED}ERROR${NC}: file not found: ${file}" && waitForInput && continue 2 + [[ ! -f "${file}" ]] && println ERROR "${FG_RED}ERROR${NC}: file not found: ${file}" && waitToProceed && continue 2 if [[ ${file} = "${ASSET_POLICY_SCRIPT_FILENAME}" ]]; then if ! grep -q "$(_jq '.script.keyHash')" "${file}"; then println ERROR "${FG_RED}ERROR${NC}: script file provided doesn't match with script hash in offline transaction for: ${otx_signing_name}" println ERROR "Provided asset script keyHash: $(jq -r '.keyHash' "${file}")" println ERROR "Transaction asset script keyHash: $(_jq '.script.keyHash')" - waitForInput && continue 2 + waitToProceed && continue 2 fi elif [[ $(jq -er '.description' "${file}" 2>/dev/null) = *"Hardware"* ]]; then if ! grep -q "${otx_vkey_cborHex:4}" "${file}"; then # strip 5820 prefix println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in offline transaction for: ${otx_signing_name}" println ERROR "Provided hardware signing key's verification cborXPubKeyHex: $(jq -r .cborXPubKeyHex "${file}")" println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex:4}" - waitForInput && continue 2 + waitToProceed && continue 2 fi else println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${file} --verification-key-file ${TMP_DIR}/tmp.vkey" - if ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${file}" --verification-key-file "${TMP_DIR}"/tmp.vkey; then waitForInput && continue 2; fi + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${file}" --verification-key-file "${TMP_DIR}"/tmp.vkey 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during verification key creation!\n${stdout}"; waitToProceed && continue 2 + fi if [[ $(jq -r '.type' "${file}") = *"Extended"* ]]; then println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/tmp.vkey --verification-key-file ${TMP_DIR}/tmp2.vkey" - if ! ${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey"; then waitForInput && continue 2; fi + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during non-extended verification key creation!\n${stdout}"; waitToProceed && continue 2 + fi mv -f "${TMP_DIR}/tmp2.vkey" "${TMP_DIR}/tmp.vkey" fi if [[ ${otx_vkey_cborHex} != $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey) ]]; then println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in offline transaction for: ${otx_signing_name}" println ERROR "Provided signing key's verification cborHex: $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey)" println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex}" - waitForInput && continue 2 + waitToProceed && continue 2 fi fi @@ -3580,8 +3705,8 @@ function main { tx_sign_files+=( "${file}" ) done if [[ ${#tx_sign_files[@]} -gt 0 ]]; then - if ! witnessTx "${TMP_DIR}/tx.raw" "${tx_sign_files[@]}"; then waitForInput && continue; fi - if ! assembleTx "${TMP_DIR}/tx.raw"; then waitForInput && continue; fi + if ! witnessTx "${TMP_DIR}/tx.raw" "${tx_sign_files[@]}"; then waitToProceed && continue; fi + if ! assembleTx "${TMP_DIR}/tx.raw"; then waitToProceed && continue; fi echo if jq ". += { \"signed-txBody\": $(jq -c . "${tx_signed}") }" <<< "${offlineJSON}" > "${offline_tx}"; then println "Offline transaction successfully signed" @@ -3636,7 +3761,7 @@ function main { println DEBUG "\nFound a match for ${otx_signing_name}, use this file ? : ${FG_LGRAY}${skey_path}${NC}" select_opt "[y] Yes" "[n] No, continue with manual selection" "[s] Skip" case $? in - 0) if ! witnessTx "${TMP_DIR}/tx.raw" "${skey_path}"; then waitForInput && continue 2; fi + 0) if ! witnessTx "${TMP_DIR}/tx.raw" "${skey_path}"; then waitToProceed && continue 2; fi if ! offlineJSON=$(jq ".witness += [{ name: \"${otx_signing_name}\", witnessBody: $(jq -c . "${tx_witness_files[0]}") }]" <<< ${offlineJSON}); then return 1; fi jq -r . <<< "${offlineJSON}" > "${offline_tx}" # save this witness to disk continue ;; @@ -3652,30 +3777,34 @@ function main { case ${selection} in 0) [[ ${otx_signing_name} = "Pool "* ]] && dialog_start_path="${POOL_FOLDER}" || dialog_start_path="${WALLET_FOLDER}" fileDialog "Enter path to ${otx_signing_name}" "${dialog_start_path}/" - [[ ! -f "${file}" ]] && println ERROR "${FG_RED}ERROR${NC}: file not found: ${file}" && waitForInput && continue 2 + [[ ! -f "${file}" ]] && println ERROR "${FG_RED}ERROR${NC}: file not found: ${file}" && waitToProceed && continue 2 if [[ $(jq -r '.description' "${file}") = *"Hardware"* ]]; then if ! grep -q "${otx_vkey_cborHex:4}" "${file}"; then # strip 5820 prefix println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in offline transaction for: ${otx_signing_name}" println ERROR "Provided hardware signing key's verification cborXPubKeyHex: $(jq -r .cborXPubKeyHex "${file}")" println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex:4}" - waitForInput && continue 2 + waitToProceed && continue 2 fi else println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${file} --verification-key-file ${TMP_DIR}/tmp.vkey" - if ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${file}" --verification-key-file "${TMP_DIR}"/tmp.vkey; then waitForInput && continue 2; fi + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${file}" --verification-key-file "${TMP_DIR}"/tmp.vkey 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during verification key creation!\n${stdout}"; waitToProceed && continue 2 + fi if [[ $(jq -r '.type' "${file}") = *"Extended"* ]]; then println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/tmp.vkey --verification-key-file ${TMP_DIR}/tmp2.vkey" - if ! ${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey"; then waitForInput && continue 2; fi + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during non-extended verification key creation!\n${stdout}"; waitToProceed && continue 2 + fi mv -f "${TMP_DIR}/tmp2.vkey" "${TMP_DIR}/tmp.vkey" fi if [[ ${otx_vkey_cborHex} != $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey) ]]; then println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in offline transaction for: ${otx_signing_name}" println ERROR "Provided signing key's verification cborHex: $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey)" println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex}" - waitForInput && continue 2 + waitToProceed && continue 2 fi fi - if ! witnessTx "${TMP_DIR}/tx.raw" "${file}"; then waitForInput && continue 2; fi + if ! witnessTx "${TMP_DIR}/tx.raw" "${file}"; then waitToProceed && continue 2; fi if ! offlineJSON=$(jq ".witness += [{ name: \"${otx_signing_name}\", witnessBody: $(jq -c . "${tx_witness_files[0]}") }]" <<< ${offlineJSON}); then return 1; fi jq -r . <<< "${offlineJSON}" > "${offline_tx}" # save this witness to disk ;; @@ -3691,7 +3820,7 @@ function main { jq -r . <<< "$(_jq '.witnessBody')" > "${tx_witness}" tx_witness_files+=( "${tx_witness}" ) done - if ! assembleTx "${TMP_DIR}/tx.raw"; then waitForInput && continue; fi + if ! assembleTx "${TMP_DIR}/tx.raw"; then waitToProceed && continue; fi if jq ". += { \"signed-txBody\": $(jq -c . "${tx_signed}") }" <<< "${offlineJSON}" > "${offline_tx}"; then println "Offline transaction successfully assembled and signed by all signing keys" println "please move ${offline_tx} back to online node and submit before ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}!" @@ -3702,9 +3831,9 @@ function main { println "Offline transaction need to be signed by ${FG_LBLUE}$(jq -r '."signing-file" | length' <<< "${offlineJSON}")${NC} signing keys, signed by ${FG_LBLUE}$(jq -r '.witness | length' <<< "${offlineJSON}")${NC} so far!" fi ;; - *) println ERROR "${FG_RED}ERROR${NC}: unsupported offline tx type: ${otx_type}" && waitForInput && continue ;; + *) println ERROR "${FG_RED}ERROR${NC}: unsupported offline tx type: ${otx_type}" && waitToProceed && continue ;; esac - waitForInput && continue + waitToProceed && continue ;; ################################################################### submit) clear @@ -3713,7 +3842,7 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue fi echo fileDialog "Enter path to offline tx file to submit" "${TMP_DIR}/" && echo @@ -3721,17 +3850,17 @@ function main { [[ -z "${offline_tx}" ]] && continue if [[ ! -f "${offline_tx}" ]]; then println ERROR "${FG_RED}ERROR${NC}: file not found: ${offline_tx}" - waitForInput && continue + waitToProceed && continue elif ! offlineJSON=$(jq -erc . "${offline_tx}"); then println ERROR "${FG_RED}ERROR${NC}: invalid JSON file: ${offline_tx}" - waitForInput && continue - fi - if ! otx_type=$(jq -er '.type' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'type' not found in: ${offline_tx}" && waitForInput && continue; fi - if ! otx_date_created=$(jq -er '."date-created"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'date-created' not found in: ${offline_tx}" && waitForInput && continue; fi - if ! otx_date_expire=$(jq -er '."date-expire"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'date-expire' not found in: ${offline_tx}" && waitForInput && continue; fi - if ! otx_txFee=$(jq -er '.txFee' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txFee' not found in: ${offline_tx}" && waitForInput && continue; fi - if ! otx_signed_txBody=$(jq -er '."signed-txBody"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'signed-txBody' not found in: ${offline_tx}" && waitForInput && continue; fi - [[ $(jq 'length' <<< ${otx_signed_txBody}) -eq 0 ]] && println ERROR "${FG_RED}ERROR${NC}: transaction not signed, please sign transaction first!" && waitForInput && continue + waitToProceed && continue + fi + if ! otx_type=$(jq -er '.type' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'type' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_date_created=$(jq -er '."date-created"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'date-created' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_date_expire=$(jq -er '."date-expire"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'date-expire' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_txFee=$(jq -er '.txFee' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txFee' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_signed_txBody=$(jq -er '."signed-txBody"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'signed-txBody' not found in: ${offline_tx}" && waitToProceed && continue; fi + [[ $(jq 'length' <<< ${otx_signed_txBody}) -eq 0 ]] && println ERROR "${FG_RED}ERROR${NC}: transaction not signed, please sign transaction first!" && waitToProceed && continue println DEBUG "Transaction type : ${FG_YELLOW}${otx_type}${NC}" if jq -er '."wallet-name"' &>/dev/null <<< ${offlineJSON}; then println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA, payed by ${FG_GREEN}$(jq -r '."wallet-name"' <<< ${offlineJSON})${NC}" @@ -3742,7 +3871,7 @@ function main { if [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]]; then println DEBUG "Expire : ${FG_RED}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" println ERROR "\n${FG_RED}ERROR${NC}: offline transaction expired! please create a new one with long enough Time To Live (TTL)" - waitForInput && continue + waitToProceed && continue else println DEBUG "Expire : ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" fi @@ -3794,7 +3923,7 @@ function main { 1) continue ;; esac echo -e "${otx_signed_txBody}" > "${tx_signed}" - if ! submitTx "${tx_signed}"; then waitForInput && continue; fi + if ! submitTx "${tx_signed}"; then waitToProceed && continue; fi if [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]]; then if otx_pool_name=$(jq -er '."pool-name"' <<< ${offlineJSON}); then if ! jq '."pool-reg-cert"' <<< "${offlineJSON}" > "${POOL_FOLDER}/${otx_pool_name}/${POOL_REGCERT_FILENAME}"; then println ERROR "${FG_RED}ERROR${NC}: failed to write pool cert to disk"; fi @@ -3813,9 +3942,9 @@ function main { 1) : ;; esac ;; - *) println ERROR "${FG_RED}ERROR${NC}: unsupported offline tx type: ${otx_type}" && waitForInput && continue ;; + *) println ERROR "${FG_RED}ERROR${NC}: unsupported offline tx type: ${otx_type}" && waitToProceed && continue ;; esac - waitForInput && continue + waitToProceed && continue ;; ################################################################### esac # transaction sub OPERATION done # Transaction loop @@ -3827,7 +3956,7 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" if ! command -v sqlite3 >/dev/null; then println ERROR "${FG_RED}ERROR${NC}: sqlite3 not found!" - waitForInput && continue + waitToProceed && continue fi current_epoch=$(getEpoch) println DEBUG "Current epoch: ${FG_LBLUE}${current_epoch}${NC}\n" @@ -3838,7 +3967,7 @@ function main { epoch_enter=${epoch_enter:-10} if ! isNumber ${epoch_enter}; then println ERROR "\n${FG_RED}ERROR${NC}: not a number" - waitForInput && continue + waitToProceed && continue fi view=1; view_output="${FG_YELLOW}[b] Block View${NC} | [i] Info" while true; do @@ -3856,9 +3985,9 @@ function main { [[ ${ideal_len} -lt 5 ]] && ideal_len=5 luck_len=$(sqlite3 "${BLOCKLOG_DB}" "SELECT LENGTH(max_performance) FROM epochdata WHERE epoch BETWEEN ${first_epoch} and ${current_epoch} ORDER BY LENGTH(max_performance) DESC LIMIT 1;") [[ $((luck_len+1)) -le 4 ]] && luck_len=4 || luck_len=$((luck_len+1)) - printf '|' >&3; printf "%$((5+6+ideal_len+luck_len+7+9+6+7+6+7+27+2))s" | tr " " "=" >&3; printf '|\n' >&3 - printf "| %-5s | %-6s | %-${ideal_len}s | %-${luck_len}s | ${FG_LBLUE}%-7s${NC} | ${FG_GREEN}%-9s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} |\n" "Epoch" "Leader" "Ideal" "Luck" "Adopted" "Confirmed" "Missed" "Ghosted" "Stolen" "Invalid" >&3 - printf '|' >&3; printf "%$((5+6+ideal_len+luck_len+7+9+6+7+6+7+27+2))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((5+6+ideal_len+luck_len+7+9+6+7+6+7+27+2))s" | tr " " "="; printf '|\n' + printf "| %-5s | %-6s | %-${ideal_len}s | %-${luck_len}s | ${FG_LBLUE}%-7s${NC} | ${FG_GREEN}%-9s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} |\n" "Epoch" "Leader" "Ideal" "Luck" "Adopted" "Confirmed" "Missed" "Ghosted" "Stolen" "Invalid" + printf '|'; printf "%$((5+6+ideal_len+luck_len+7+9+6+7+6+7+27+2))s" | tr " " "="; printf '|\n' while [[ ${current_epoch} -gt ${first_epoch} ]]; do invalid_cnt=$(sqlite3 "${BLOCKLOG_DB}" "SELECT COUNT(*) FROM blocklog WHERE epoch=${current_epoch} AND status='invalid';" 2>/dev/null) missed_cnt=$(sqlite3 "${BLOCKLOG_DB}" "SELECT COUNT(*) FROM blocklog WHERE epoch=${current_epoch} AND status='missed';" 2>/dev/null) @@ -3873,10 +4002,10 @@ function main { else epoch_stats[1]="${epoch_stats[1]}%" fi - printf "| ${FG_LGRAY}%-5s${NC} | ${FG_LGRAY}%-6s${NC} | ${FG_LGRAY}%-${ideal_len}s${NC} | ${FG_LGRAY}%-${luck_len}s${NC} | ${FG_LBLUE}%-7s${NC} | ${FG_GREEN}%-9s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} |\n" "${current_epoch}" "${leader_cnt}" "${epoch_stats[0]}" "${epoch_stats[1]}" "${adopted_cnt}" "${confirmed_cnt}" "${missed_cnt}" "${ghosted_cnt}" "${stolen_cnt}" "${invalid_cnt}" >&3 + printf "| ${FG_LGRAY}%-5s${NC} | ${FG_LGRAY}%-6s${NC} | ${FG_LGRAY}%-${ideal_len}s${NC} | ${FG_LGRAY}%-${luck_len}s${NC} | ${FG_LBLUE}%-7s${NC} | ${FG_GREEN}%-9s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} |\n" "${current_epoch}" "${leader_cnt}" "${epoch_stats[0]}" "${epoch_stats[1]}" "${adopted_cnt}" "${confirmed_cnt}" "${missed_cnt}" "${ghosted_cnt}" "${stolen_cnt}" "${invalid_cnt}" ((current_epoch--)) done - printf '|' >&3; printf "%$((5+6+ideal_len+luck_len+7+9+6+7+6+7+27+2))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((5+6+ideal_len+luck_len+7+9+6+7+6+7+27+2))s" | tr " " "="; printf '|\n' else println OFF "Block Status:\n" println OFF "Leader - Scheduled to make block at this slot" @@ -3909,7 +4038,7 @@ function main { [[ -z "${epoch_enter}" ]] && epoch_enter=${current_epoch} if [[ $(sqlite3 "${BLOCKLOG_DB}" "SELECT EXISTS(SELECT 1 FROM blocklog WHERE epoch=${epoch_enter} LIMIT 1);" 2>/dev/null) -eq 0 ]]; then println "No blocks in epoch ${epoch_enter}" - waitForInput && continue + waitToProceed && continue fi view=1; view_output="${FG_YELLOW}[1] View 1${NC} | [2] View 2 | [3] View 3 | [i] Info" while true; do @@ -3935,11 +4064,11 @@ function main { fi [[ ${#epoch_stats[0]} -gt 5 ]] && ideal_len=${#epoch_stats[0]} || ideal_len=5 [[ ${#epoch_stats[1]} -gt 4 ]] && luck_len=${#epoch_stats[1]} || luck_len=4 - printf '|' >&3; printf "%$((6+ideal_len+luck_len+7+9+6+7+6+7+24+2))s" | tr " " "=" >&3; printf '|\n' >&3 - printf "| %-6s | %-${ideal_len}s | %-${luck_len}s | ${FG_LBLUE}%-7s${NC} | ${FG_GREEN}%-9s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} |\n" "Leader" "Ideal" "Luck" "Adopted" "Confirmed" "Missed" "Ghosted" "Stolen" "Invalid" >&3 - printf '|' >&3; printf "%$((6+ideal_len+luck_len+7+9+6+7+6+7+24+2))s" | tr " " "=" >&3; printf '|\n' >&3 - printf "| ${FG_LGRAY}%-6s${NC} | ${FG_LGRAY}%-${ideal_len}s${NC} | ${FG_LGRAY}%-${luck_len}s${NC} | ${FG_LBLUE}%-7s${NC} | ${FG_GREEN}%-9s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} |\n" "${leader_cnt}" "${epoch_stats[0]}" "${epoch_stats[1]}" "${adopted_cnt}" "${confirmed_cnt}" "${missed_cnt}" "${ghosted_cnt}" "${stolen_cnt}" "${invalid_cnt}" >&3 - printf '|' >&3; printf "%$((6+ideal_len+luck_len+7+9+6+7+6+7+24+2))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((6+ideal_len+luck_len+7+9+6+7+6+7+24+2))s" | tr " " "="; printf '|\n' + printf "| %-6s | %-${ideal_len}s | %-${luck_len}s | ${FG_LBLUE}%-7s${NC} | ${FG_GREEN}%-9s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} |\n" "Leader" "Ideal" "Luck" "Adopted" "Confirmed" "Missed" "Ghosted" "Stolen" "Invalid" + printf '|'; printf "%$((6+ideal_len+luck_len+7+9+6+7+6+7+24+2))s" | tr " " "="; printf '|\n' + printf "| ${FG_LGRAY}%-6s${NC} | ${FG_LGRAY}%-${ideal_len}s${NC} | ${FG_LGRAY}%-${luck_len}s${NC} | ${FG_LBLUE}%-7s${NC} | ${FG_GREEN}%-9s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} | ${FG_RED}%-6s${NC} | ${FG_RED}%-7s${NC} |\n" "${leader_cnt}" "${epoch_stats[0]}" "${epoch_stats[1]}" "${adopted_cnt}" "${confirmed_cnt}" "${missed_cnt}" "${ghosted_cnt}" "${stolen_cnt}" "${invalid_cnt}" + printf '|'; printf "%$((6+ideal_len+luck_len+7+9+6+7+6+7+24+2))s" | tr " " "="; printf '|\n' echo # print block table block_cnt=1 @@ -3957,40 +4086,40 @@ function main { hash_len=$(sqlite3 "${BLOCKLOG_DB}" "SELECT LENGTH(hash) FROM blocklog WHERE epoch=${epoch_enter} ORDER BY LENGTH(hash) DESC LIMIT 1;") [[ ${hash_len} -lt 4 ]] && hash_len=4 if [[ ${view} -eq 1 ]]; then - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+17))s" | tr " " "=" >&3; printf '|\n' >&3 - printf "| %-${#leader_cnt}s | %-${status_len}s | %-${block_len}s | %-${slot_len}s | %-${slot_in_epoch_len}s | %-${at_len}s |\n" "#" "Status" "Block" "Slot" "SlotInEpoch" "Scheduled At" >&3 - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+17))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+17))s" | tr " " "="; printf '|\n' + printf "| %-${#leader_cnt}s | %-${status_len}s | %-${block_len}s | %-${slot_len}s | %-${slot_in_epoch_len}s | %-${at_len}s |\n" "#" "Status" "Block" "Slot" "SlotInEpoch" "Scheduled At" + printf '|'; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+17))s" | tr " " "="; printf '|\n' while IFS='|' read -r status block slot slot_in_epoch at; do at=$(TZ="${BLOCKLOG_TZ}" date '+%F %T %Z' --date="${at}") [[ ${block} -eq 0 ]] && block="-" - printf "| ${FG_LGRAY}%-${#leader_cnt}s${NC} | ${FG_LGRAY}%-${status_len}s${NC} | ${FG_LGRAY}%-${block_len}s${NC} | ${FG_LGRAY}%-${slot_len}s${NC} | ${FG_LGRAY}%-${slot_in_epoch_len}s${NC} | ${FG_LGRAY}%-${at_len}s${NC} |\n" "${block_cnt}" "${status}" "${block}" "${slot}" "${slot_in_epoch}" "${at}" >&3 + printf "| ${FG_LGRAY}%-${#leader_cnt}s${NC} | ${FG_LGRAY}%-${status_len}s${NC} | ${FG_LGRAY}%-${block_len}s${NC} | ${FG_LGRAY}%-${slot_len}s${NC} | ${FG_LGRAY}%-${slot_in_epoch_len}s${NC} | ${FG_LGRAY}%-${at_len}s${NC} |\n" "${block_cnt}" "${status}" "${block}" "${slot}" "${slot_in_epoch}" "${at}" ((block_cnt++)) done < <(sqlite3 "${BLOCKLOG_DB}" "SELECT status, block, slot, slot_in_epoch, at FROM blocklog WHERE epoch=${epoch_enter} ORDER BY slot;" 2>/dev/null) - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+17))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+17))s" | tr " " "="; printf '|\n' elif [[ ${view} -eq 2 ]]; then - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+slot_len+size_len+hash_len+14))s" | tr " " "=" >&3; printf '|\n' >&3 - printf "| %-${#leader_cnt}s | %-${status_len}s | %-${slot_len}s | %-${size_len}s | %-${hash_len}s |\n" "#" "Status" "Slot" "Size" "Hash" >&3 - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+slot_len+size_len+hash_len+14))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((${#leader_cnt}+status_len+slot_len+size_len+hash_len+14))s" | tr " " "="; printf '|\n' + printf "| %-${#leader_cnt}s | %-${status_len}s | %-${slot_len}s | %-${size_len}s | %-${hash_len}s |\n" "#" "Status" "Slot" "Size" "Hash" + printf '|'; printf "%$((${#leader_cnt}+status_len+slot_len+size_len+hash_len+14))s" | tr " " "="; printf '|\n' while IFS='|' read -r status slot size hash; do [[ ${size} -eq 0 ]] && size="-" [[ -z ${hash} ]] && hash="-" - printf "| ${FG_LGRAY}%-${#leader_cnt}s${NC} | ${FG_LGRAY}%-${status_len}s${NC} | ${FG_LGRAY}%-${slot_len}s${NC} | ${FG_LGRAY}%-${size_len}s${NC} | ${FG_LGRAY}%-${hash_len}s${NC} |\n" "${block_cnt}" "${status}" "${slot}" "${size}" "${hash}" >&3 + printf "| ${FG_LGRAY}%-${#leader_cnt}s${NC} | ${FG_LGRAY}%-${status_len}s${NC} | ${FG_LGRAY}%-${slot_len}s${NC} | ${FG_LGRAY}%-${size_len}s${NC} | ${FG_LGRAY}%-${hash_len}s${NC} |\n" "${block_cnt}" "${status}" "${slot}" "${size}" "${hash}" ((block_cnt++)) done < <(sqlite3 "${BLOCKLOG_DB}" "SELECT status, slot, size, hash FROM blocklog WHERE epoch=${epoch_enter} ORDER BY slot;" 2>/dev/null) - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+slot_len+size_len+hash_len+14))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((${#leader_cnt}+status_len+slot_len+size_len+hash_len+14))s" | tr " " "="; printf '|\n' elif [[ ${view} -eq 3 ]]; then - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+size_len+hash_len+23))s" | tr " " "=" >&3; printf '|\n' >&3 - printf "| %-${#leader_cnt}s | %-${status_len}s | %-${block_len}s | %-${slot_len}s | %-${slot_in_epoch_len}s | %-${at_len}s | %-${size_len}s | %-${hash_len}s |\n" "#" "Status" "Block" "Slot" "SlotInEpoch" "Scheduled At" "Size" "Hash" >&3 - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+size_len+hash_len+23))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+size_len+hash_len+23))s" | tr " " "="; printf '|\n' + printf "| %-${#leader_cnt}s | %-${status_len}s | %-${block_len}s | %-${slot_len}s | %-${slot_in_epoch_len}s | %-${at_len}s | %-${size_len}s | %-${hash_len}s |\n" "#" "Status" "Block" "Slot" "SlotInEpoch" "Scheduled At" "Size" "Hash" + printf '|'; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+size_len+hash_len+23))s" | tr " " "="; printf '|\n' while IFS='|' read -r status block slot slot_in_epoch at size hash; do at=$(TZ="${BLOCKLOG_TZ}" date '+%F %T %Z' --date="${at}") [[ ${block} -eq 0 ]] && block="-" [[ ${size} -eq 0 ]] && size="-" [[ -z ${hash} ]] && hash="-" - printf "| ${FG_LGRAY}%-${#leader_cnt}s${NC} | ${FG_LGRAY}%-${status_len}s${NC} | ${FG_LGRAY}%-${block_len}s${NC} | ${FG_LGRAY}%-${slot_len}s${NC} | ${FG_LGRAY}%-${slot_in_epoch_len}s${NC} | ${FG_LGRAY}%-${at_len}s${NC} | ${FG_LGRAY}%-${size_len}s${NC} | ${FG_LGRAY}%-${hash_len}s${NC} |\n" "${block_cnt}" "${status}" "${block}" "${slot}" "${slot_in_epoch}" "${at}" "${size}" "${hash}" >&3 + printf "| ${FG_LGRAY}%-${#leader_cnt}s${NC} | ${FG_LGRAY}%-${status_len}s${NC} | ${FG_LGRAY}%-${block_len}s${NC} | ${FG_LGRAY}%-${slot_len}s${NC} | ${FG_LGRAY}%-${slot_in_epoch_len}s${NC} | ${FG_LGRAY}%-${at_len}s${NC} | ${FG_LGRAY}%-${size_len}s${NC} | ${FG_LGRAY}%-${hash_len}s${NC} |\n" "${block_cnt}" "${status}" "${block}" "${slot}" "${slot_in_epoch}" "${at}" "${size}" "${hash}" ((block_cnt++)) done < <(sqlite3 "${BLOCKLOG_DB}" "SELECT status, block, slot, slot_in_epoch, at, size, hash FROM blocklog WHERE epoch=${epoch_enter} ORDER BY slot;" 2>/dev/null) - printf '|' >&3; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+size_len+hash_len+23))s" | tr " " "=" >&3; printf '|\n' >&3 + printf '|'; printf "%$((${#leader_cnt}+status_len+block_len+slot_len+slot_in_epoch_len+at_len+size_len+hash_len+23))s" | tr " " "="; printf '|\n' elif [[ ${view} -eq 4 ]]; then println OFF "Block Status:\n" println OFF "Leader - Scheduled to make block at this slot" @@ -4022,7 +4151,7 @@ function main { ;; 2) continue ;; esac - waitForInput && continue + waitToProceed && continue ;; ################################################################### backup) clear @@ -4040,9 +4169,9 @@ function main { [[ "${dir}" != */ ]] && backup_path="${dir}/" || backup_path="${dir}" if [[ ! "${backup_path}" =~ ^/[-0-9A-Za-z_]+ ]]; then println ERROR "${FG_RED}ERROR${NC}: invalid path, please specify the full path to backup directory (space not allowed)" - waitForInput && continue + waitToProceed && continue fi - if ! mkdir -p "${backup_path}"; then println ERROR "${FG_RED}ERROR${NC}: failed to create backup directory:\n${backup_path}" && waitForInput && continue; fi + if ! mkdir -p "${backup_path}"; then println ERROR "${FG_RED}ERROR${NC}: failed to create backup directory:\n${backup_path}" && waitToProceed && continue; fi missing_keys="false" excluded_files=() [[ -d "${ASSET_FOLDER}" ]] && asset_out=" and asset ${ASSET_POLICY_SK_FILENAME}" || asset_out="" @@ -4083,19 +4212,19 @@ function main { ((backup_cnt++)) done < <(find "${item}" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null | sort -z) done - [[ ${backup_cnt} -eq 0 ]] && println "\nNo folders found to include in backup :(" && waitForInput && continue + [[ ${backup_cnt} -eq 0 ]] && println "\nNo folders found to include in backup :(" && waitToProceed && continue echo if [[ ${#excluded_files[@]} -gt 0 ]]; then println ACTION "tar ${excluded_files[*]} -cf ${backup_file} ${backup_list[*]}" - if ! output=$(tar "${excluded_files[@]}" -cf "${backup_file}" "${backup_list[@]}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during tarball creation:\n${output}" && waitForInput && continue; fi + if ! stdout=$(tar "${excluded_files[@]}" -cf "${backup_file}" "${backup_list[@]}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during tarball creation:\n${stdout}" && waitToProceed && continue; fi println ACTION "gzip ${backup_file}" - if ! output=$(gzip "${backup_file}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: gzip error:\n${output}" && waitForInput && continue; fi + if ! stdout=$(gzip "${backup_file}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: gzip error:\n${stdout}" && waitToProceed && continue; fi backup_file+=".gz" else println ACTION "tar -cf ${backup_file} ${backup_list[*]}" - if ! output=$(tar -cf "${backup_file}" "${backup_list[@]}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during tarball creation:\n${output}" && waitForInput && continue; fi + if ! stdout=$(tar -cf "${backup_file}" "${backup_list[@]}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during tarball creation:\n${stdout}" && waitToProceed && continue; fi println ACTION "gzip ${backup_file}" - if ! output=$(gzip "${backup_file}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: gzip error:\n${output}" && waitForInput && continue; fi + if ! stdout=$(gzip "${backup_file}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: gzip error:\n${stdout}" && waitToProceed && continue; fi backup_file+=".gz" while IFS= read -r -d '' wallet; do # check for missing signing keys wallet_name=$(basename ${wallet}) @@ -4138,9 +4267,9 @@ function main { backup_file=${file} if [[ ! -f "${backup_file}" ]]; then println ERROR "${FG_RED}ERROR${NC}: file not found: ${backup_file}" - waitForInput && continue + waitToProceed && continue fi - if ! restore_path="$(mktemp -d "${TMP_DIR}/restore_XXXXXXXXXX")"; then println ERROR "${FG_RED}ERROR${NC}: failed to create restore directory:\n${restore_path}" && waitForInput && continue; fi + if ! restore_path="$(mktemp -d "${TMP_DIR}/restore_XXXXXXXXXX")"; then println ERROR "${FG_RED}ERROR${NC}: failed to create restore directory:\n${restore_path}" && waitToProceed && continue; fi tmp_bkp_file="" if [ "${backup_file##*.}" = "gpg" ]; then println DEBUG "Backup GPG encrypted, enter password to decrypt" @@ -4153,11 +4282,11 @@ function main { echo else println ERROR "\n${FG_RED}ERROR${NC}: password input aborted!" - waitForInput && continue + waitToProceed && continue fi fi println ACTION "tar xfzk ${backup_file} -C ${restore_path}" - if ! output=$(tar xfzk "${backup_file}" -C "${restore_path}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during tarball extraction:\n${output}" && waitForInput && continue; fi + if ! stdout=$(tar xfzk "${backup_file}" -C "${restore_path}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during tarball extraction:\n${stdout}" && waitToProceed && continue; fi [[ -n "${tmp_bkp_file}" ]] && mv -f "${tmp_bkp_file}" "${backup_file}.gpg" && rm -f "${backup_file}" # restore original encrypted backup file restore_source=( "${restore_path}${WALLET_FOLDER}" @@ -4176,7 +4305,7 @@ function main { ((restore_cnt++)) done < <(find "${item}" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null | sort -z) done - [[ ${restore_cnt} -eq 0 ]] && println "\nNothing in backup file to restore :(" && waitForInput && continue + [[ ${restore_cnt} -eq 0 ]] && println "\nNothing in backup file to restore :(" && waitToProceed && continue echo println DEBUG "Continue with restore?" select_opt "[n] No" "[y] Yes" @@ -4202,10 +4331,10 @@ function main { done if [[ ${source_cnt} -gt 0 ]]; then archive_dest="${CNODE_HOME}/priv/archive" - if ! mkdir -p "${archive_dest}"; then println ERROR "${FG_RED}ERROR${NC}: failed to create archive directory:\n${archive_dest}" && waitForInput && continue; fi + if ! mkdir -p "${archive_dest}"; then println ERROR "${FG_RED}ERROR${NC}: failed to create archive directory:\n${archive_dest}" && waitToProceed && continue; fi archive_file="${archive_dest}/archive_$(date '+%Y%m%d%H%M%S').tar.gz" println ACTION "tar cfz ${archive_file} ${archive_list[*]}" - if ! output=$(tar cfz "${archive_file}" "${archive_list[@]}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during archive/backup:\n${output}" && waitForInput && continue; fi + if ! stdout=$(tar cfz "${archive_file}" "${archive_list[@]}" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during archive/backup:\n${stdout}" && waitToProceed && continue; fi println DEBUG "An archive of current priv folder has been taken and stored in ${FG_LGRAY}${archive_file}${NC}" println DEBUG "Please set a password to GPG encrypt the archive" if getPasswordCust confirm; then # $password variable populated by getPasswordCust function @@ -4224,13 +4353,13 @@ function main { unlockFile "${file}" done < <(find "${dest_path}" -mindepth 1 -maxdepth 1 -type f -print0 2>/dev/null) println ACTION "cp -rf ${item} $(dirname "${dest_path}")" - cp -rf "${item}" "$(dirname "${dest_path}")" + if ! stdout=$(cp -rf "${item}" "$(dirname "${dest_path}")" 2>&1); then println ERROR "${FG_RED}ERROR${NC}: during retore copy:\n${stdout}" && waitToProceed && continue; fi done println "Backup ${FG_LGRAY}$(basename "${backup_file}")${NC} successfully restored!" ;; 2) continue ;; esac - waitForInput && continue + waitToProceed && continue ;; ################################################################### advanced) @@ -4242,8 +4371,14 @@ function main { println OFF " Developer & Advanced features\n"\ " ) Metadata - create and optionally post metadata on-chain"\ " ) Multi-Asset - multi-asset nanagement"\ - " ) Delete Keys - Delete all sign/cold keys from CNTools (wallet|pool|asset)"\ + " ) Delete Keys - delete all sign/cold keys from CNTools (wallet|pool|asset)"\ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + #println OFF " Developer & Advanced features\n"\ +# " ) Metadata - create and optionally post metadata on-chain"\ +# " ) Multi-Asset - multi-asset nanagement"\ +# " ) Multi-Sig - create a multi-sig/native script wallet"\ +# " ) Delete Keys - delete all sign/cold keys from CNTools (wallet|pool|asset)"\ +# "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println DEBUG " Select Operation\n" select_opt "[m] Metadata" "[a] Multi-Asset" "[x] Delete Private Keys" "[h] Home" case $? in @@ -4252,16 +4387,24 @@ function main { 2) SUBCOMMAND="del-keys" ;; 3) break ;; esac + #select_opt "[m] Metadata" "[a] Multi-Asset" "[s] Multi-Sig" "[x] Delete Private Keys" "[h] Home" + #case $? in + # 0) SUBCOMMAND="metadata" ;; + # 1) SUBCOMMAND="multi-asset" ;; + # 2) SUBCOMMAND="multi-sig" ;; + # 3) SUBCOMMAND="del-keys" ;; + # 4) break ;; + #esac case $SUBCOMMAND in metadata) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> ADVANCED >> METADATA" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available to pay for transaction fee!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available to pay for transaction fee!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "\n${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -4286,7 +4429,7 @@ function main { metafile="${file}" if [[ ! -f "${metafile}" ]] || ! jq -er . "${metafile}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: invalid JSON format or file not found" - waitForInput && continue + waitToProceed && continue fi println DEBUG "$(cat "${metafile}")\n" ;; @@ -4294,11 +4437,11 @@ function main { getAnswerAnyCust meta_json_url "Enter URL to JSON metadata file" if [[ ! "${meta_json_url}" =~ https?://.* ]]; then println ERROR "${FG_RED}ERROR${NC}: invalid URL format" - waitForInput && continue + waitToProceed && continue fi if ! curl -sL -m ${CURL_TIMEOUT} -o "${metafile}" ${meta_json_url} || ! jq -er . "${metafile}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: metadata download failed, please make sure the URL point to a valid JSON file!" - waitForInput && continue + waitToProceed && continue fi tput rc && tput ed println "Metadata file successfully downloaded to: ${FG_LGRAY}${metafile}${NC}" @@ -4313,14 +4456,12 @@ function main { DEFAULTEDITOR="$(command -v nano &>/dev/null && echo 'nano' || echo 'vi')" println OFF "\nPaste or enter the metadata text, opening text editor ${FG_LGRAY}${DEFAULTEDITOR}${NC}" println OFF "${FG_YELLOW}Please don't change default file path when saving${NC}" - exec >&6 2>&7 # normal stdout/stderr - waitForInput "press any key to open ${DEFAULTEDITOR}" + waitToProceed "press any key to open ${DEFAULTEDITOR}" ${DEFAULTEDITOR} "${metafile}" - exec >&8 2>&9 # custom stdout/stderr if [[ ! -f "${metafile}" ]] || ! jq -er . "${metafile}" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: file not found or invalid JSON format" println ERROR "File: ${FG_LGRAY}${metafile}${NC}" - waitForInput && continue + waitToProceed && continue fi tput rc && tput ed println "Metadata file successfully saved to: ${FG_LGRAY}${metafile}${NC}" @@ -4337,33 +4478,28 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "balance" "${WALLET_PAY_SK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for metadata transaction fee!" && waitForInput && continue ;; - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for metadata transaction fee!" && waitToProceed && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "balance" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for metadata transaction fee!" && waitForInput && continue ;; + 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for metadata transaction fee!" && waitToProceed && continue ;; esac fi echo - getBaseAddress ${wallet_name} - getPayAddress ${wallet_name} - getBalance ${base_addr} - base_lovelace=${assets[lovelace]} - getBalance ${pay_addr} - pay_lovelace=${assets[lovelace]} + getWalletBalance ${wallet_name} true true true true if [[ ${pay_lovelace} -gt 0 && ${base_lovelace} -gt 0 ]]; then # Both payment and base address available with funds, let user choose what to use println DEBUG "Select source wallet address" @@ -4374,32 +4510,34 @@ function main { echo select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" case $? in - 0) addr="${base_addr}" ;; - 1) addr="${pay_addr}" ;; + 0) addr="${base_addr}"; lovelace=${base_lovelace} ;; + 1) addr="${pay_addr}"; lovelace=${pay_lovelace} ;; 2) continue ;; esac elif [[ ${pay_lovelace} -gt 0 ]]; then addr="${pay_addr}" + lovelace=${pay_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" fi elif [[ ${base_lovelace} -gt 0 ]]; then addr="${base_addr}" + lovelace=${base_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" - waitForInput && continue + waitToProceed && continue fi if ! sendMetadata; then - waitForInput && continue + waitToProceed && continue fi echo - if ! verifyTx ${addr}; then waitForInput && continue; fi + if ! verifyTx ${addr}; then waitToProceed && continue; fi echo println "Metadata successfully posted on-chain" - waitForInput && continue + waitToProceed && continue ;; ################################################################### multi-asset) while true; do # Multi-Asset loop @@ -4443,13 +4581,13 @@ function main { policy_name=${policy_name//[^[:alnum:]]/_} if [[ -z "${policy_name}" ]]; then println ERROR "${FG_RED}ERROR${NC}: Empty policy name, please retry!" - waitForInput && continue + waitToProceed && continue fi policy_folder="${ASSET_FOLDER}/${policy_name}" echo if ! mkdir -p "${policy_folder}"; then println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for policy:\n${policy_folder}" - waitForInput && continue + waitToProceed && continue fi # Policy filenames policy_sk_file="${policy_folder}/${ASSET_POLICY_SK_FILENAME}" @@ -4459,15 +4597,15 @@ function main { if [[ $(find "${policy_folder}" -type f -print0 | wc -c) -gt 0 ]]; then println "${FG_RED}WARN${NC}: A policy ${FG_GREEN}${policy_name}${NC} already exist!" println " Choose another name or delete the existing one" - waitForInput && continue + waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file ${policy_vk_file} --signing-key-file ${policy_sk_file}" - if ! ${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file "${policy_vk_file}" --signing-key-file "${policy_sk_file}"; then - println ERROR "${FG_RED}ERROR${NC}: failure during policy key creation!"; safeDel "${policy_folder}"; waitForInput && continue + if ! stdout=$(${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file "${policy_vk_file}" --signing-key-file "${policy_sk_file}" 2>&1); then + println ERROR "${FG_RED}ERROR${NC}: failure during policy key creation!\n${stdout}"; safeDel "${policy_folder}"; waitToProceed && continue fi println ACTION "${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file ${policy_vk_file}" - if ! policy_key_hash=$(${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file "${policy_vk_file}"); then - println ERROR "${FG_RED}ERROR${NC}: failure during policy verification key hashing!"; safeDel "${policy_folder}"; waitForInput && continue + if ! policy_key_hash=$(${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file "${policy_vk_file}" 2>&1); then + println ERROR "${FG_RED}ERROR${NC}: failure during policy verification key hashing!\n${policy_key_hash}"; safeDel "${policy_folder}"; waitToProceed && continue fi println DEBUG "How long do you want the policy to be valid? (0/blank=unlimited)" println DEBUG "${FG_YELLOW}Setting a limit will prevent you from minting/burning assets after the policy expire !!\nLeave blank/unlimited if unsure and just press enter${NC}" @@ -4475,7 +4613,7 @@ function main { ttl_enter=${ttl_enter:-0} if ! isNumber ${ttl_enter}; then println ERROR "\n${FG_RED}ERROR${NC}: invalid TTL number, non digit characters found: ${ttl_enter}" - safeDel "${policy_folder}"; waitForInput && continue + safeDel "${policy_folder}"; waitToProceed && continue fi if [[ ${ttl_enter} -eq 0 ]]; then echo "{ \"keyHash\": \"${policy_key_hash}\", \"type\": \"sig\" }" > "${policy_script_file}" @@ -4484,8 +4622,8 @@ function main { echo "{ \"type\": \"all\", \"scripts\": [ { \"slot\": ${ttl}, \"type\": \"before\" }, { \"keyHash\": \"${policy_key_hash}\", \"type\": \"sig\" } ] }" > "${policy_script_file}" fi println ACTION "${CCLI} ${NETWORK_ERA} transaction policyid --script-file ${policy_script_file}" - if ! policy_id=$(${CCLI} ${NETWORK_ERA} transaction policyid --script-file "${policy_script_file}"); then - println ERROR "${FG_RED}ERROR${NC}: failure during policy ID generation!"; safeDel "${policy_folder}"; waitForInput && continue + if ! policy_id=$(${CCLI} ${NETWORK_ERA} transaction policyid --script-file "${policy_script_file}" 2>&1); then + println ERROR "${FG_RED}ERROR${NC}: failure during policy ID generation!\n${policy_id}"; safeDel "${policy_folder}"; waitToProceed && continue fi echo "${policy_id}" > "${policy_id_file}" chmod 600 "${policy_folder}/"* @@ -4494,14 +4632,14 @@ function main { println "Policy ID : ${FG_LGRAY}${policy_id}${NC}" println "Policy Expire : $([[ ${ttl_enter} -eq 0 ]] && echo "${FG_LGRAY}unlimited${NC}" || echo "${FG_LGRAY}$(getDateFromSlot ${ttl} '%(%F %T %Z)T')${NC}, ${FG_LGRAY}$(timeLeft $((ttl-$(getSlotTipRef))))${NC} remaining")" println DEBUG "\nYou can now start minting your custom assets using this Policy!" - waitForInput && continue + waitToProceed && continue ;; ################################################################### list-assets) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> ADVANCED >> MULTI-ASSET >> LIST ASSETS" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies or assets found!${NC}" && waitForInput && continue + [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies or assets found!${NC}" && waitToProceed && continue while IFS= read -r -d '' policy; do echo println "Policy Name : ${FG_GREEN}$(basename "${policy}")${NC}" @@ -4525,18 +4663,18 @@ function main { println "Asset : ${FG_LGRAY}No assets minted for this policy!${NC}" fi done < <(find "${ASSET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) - waitForInput && continue + waitToProceed && continue ;; ################################################################### show-asset) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> ADVANCED >> MULTI-ASSET >> SHOW ASSET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies or assets found!${NC}" && waitForInput && continue + [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies or assets found!${NC}" && waitToProceed && continue println DEBUG "# Select minted asset to show information for" selectAsset case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac echo @@ -4573,7 +4711,7 @@ function main { a_last_action=$(jq -er '.lastAction //"-"' "${asset_file}") println "Last Updated : ${FG_LGRAY}${a_last_update}${NC}" println "Last Action : ${FG_LGRAY}${a_last_action}${NC}" - waitForInput && continue + waitToProceed && continue ;; ################################################################### decrypt-policy) clear @@ -4581,11 +4719,11 @@ function main { println " >> ADVANCED >> MULTI-ASSET >> DECRYPT / UNLOCK POLICY" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No policies available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No policies available!${NC}" && waitToProceed && continue println DEBUG "# Select policy to decrypt" selectPolicy "encrypted" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac filesUnlocked=0 @@ -4605,7 +4743,7 @@ function main { println "# Decrypting GPG encrypted policy key" if ! getPasswordCust; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" - waitForInput && continue + waitToProceed && continue fi while IFS= read -r -d '' file; do decryptFile "${file}" "${password}" && \ @@ -4623,7 +4761,7 @@ function main { println DEBUG "${FG_YELLOW}Policy files are now unprotected${NC}" println DEBUG "Use 'ADVANCED >> MULTI-ASSET >> ENCRYPT / LOCK POLICY' to re-lock" fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### encrypt-policy) clear @@ -4631,11 +4769,11 @@ function main { println " >> ADVANCED >> MULTI-ASSET >> ENCRYPT / LOCK POLICY" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No policies available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No policies available!${NC}" && waitToProceed && continue println DEBUG "# Select policy to encrypt" selectPolicy "encrypted" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac filesLocked=0 @@ -4645,7 +4783,7 @@ function main { println DEBUG "# Encrypting policy signing key with GPG" if ! getPasswordCust confirm; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" - waitForInput && continue + waitToProceed && continue fi keyFiles=( "${ASSET_FOLDER}/${policy_name}/${ASSET_POLICY_SK_FILENAME}" @@ -4661,7 +4799,7 @@ function main { else echo println DEBUG "${FG_YELLOW}NOTE${NC}: found GPG encrypted files in folder, please decrypt/unlock policy files before encrypting" - waitForInput && continue + waitToProceed && continue fi echo println DEBUG "# Write protecting all policy files with 400 permission and if enabled 'chattr +i'" @@ -4682,26 +4820,26 @@ function main { println DEBUG "${FG_BLUE}INFO${NC}: policy files are now protected" println DEBUG "Use 'ADVANCED >> MULTI-ASSET >> DECRYPT / UNLOCK POLICY' to unlock" fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### mint-asset) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> ADVANCED >> MULTI-ASSET >> MINT ASSET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi echo - [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies found!${NC}\n\nPlease first create a policy to mint asset with" && waitForInput && continue + [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies found!${NC}\n\nPlease first create a policy to mint asset with" && waitToProceed && continue println DEBUG "# Select the policy to use when minting the asset" selectPolicy "all" "${ASSET_POLICY_SK_FILENAME}" "${ASSET_POLICY_VK_FILENAME}" "${ASSET_POLICY_SCRIPT_FILENAME}" "${ASSET_POLICY_ID_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac policy_folder="${ASSET_FOLDER}/${policy_name}" @@ -4712,7 +4850,7 @@ function main { policy_id_file="${policy_folder}/${ASSET_POLICY_ID_FILENAME}" policy_id="$(cat "${policy_id_file}")" policy_ttl=$(jq -r '.scripts[0].slot //0' "${policy_script_file}") - [[ ${policy_ttl} -gt 0 && ${policy_ttl} -lt $(getSlotTipRef) ]] && println ERROR "${FG_RED}ERROR${NC}: Policy expired!" && waitForInput && continue + [[ ${policy_ttl} -gt 0 && ${policy_ttl} -lt $(getSlotTipRef) ]] && println ERROR "${FG_RED}ERROR${NC}: Policy expired!" && waitToProceed && continue echo if [[ $(find "${policy_folder}" -type f -name '*.asset' -print0 | wc -c) -gt 0 ]]; then println DEBUG "# Assets minted for this Policy\n" @@ -4734,14 +4872,14 @@ function main { println DEBUG "\nEnter an existing AssetName to mint more tokens or enter a new name to create a new Asset for this Policy" fi getAnswerAnyCust asset_name "Asset Name (empty valid)" - [[ ${asset_name} =~ ^[^[:alnum:]]$ ]] && println ERROR "${FG_RED}ERROR${NC}: Asset name should only contain alphanummeric chars!" && waitForInput && continue - [[ ${#asset_name} -gt 32 ]] && println ERROR "${FG_RED}ERROR${NC}: Asset name is limited to 32 chars in length!" && waitForInput && continue + [[ ${asset_name} =~ ^[^[:alnum:]]$ ]] && println ERROR "${FG_RED}ERROR${NC}: Asset name should only contain alphanummeric chars!" && waitToProceed && continue + [[ ${#asset_name} -gt 32 ]] && println ERROR "${FG_RED}ERROR${NC}: Asset name is limited to 32 chars in length!" && waitToProceed && continue asset_file="${policy_folder}/${asset_name// /_}.asset" echo getAnswerAnyCust assets_to_mint "Amount (commas allowed as thousand separator)" assets_to_mint="${assets_to_mint//,}" - [[ -z "${assets_to_mint}" ]] && println ERROR "${FG_RED}ERROR${NC}: Amount empty, please set a valid integer number!" && waitForInput && continue - if ! isNumber ${assets_to_mint}; then println ERROR "${FG_RED}ERROR${NC}: Invalid number, should be an integer number. Decimals not allowed!" && waitForInput && continue; fi + [[ -z "${assets_to_mint}" ]] && println ERROR "${FG_RED}ERROR${NC}: Amount empty, please set a valid integer number!" && waitToProceed && continue + if ! isNumber ${assets_to_mint}; then println ERROR "${FG_RED}ERROR${NC}: Invalid number, should be an integer number. Decimals not allowed!" && waitToProceed && continue; fi [[ -f "${asset_file}" ]] && asset_minted=$(( $(jq -r .minted "${asset_file}") + assets_to_mint )) || asset_minted=${assets_to_mint} metafile_param="" println DEBUG "\nDo you want to attach a metadata JSON file to the minting transaction?" @@ -4750,9 +4888,9 @@ function main { 0) : ;; # do nothing 1) fileDialog "Enter path to metadata JSON file" "${TMP_DIR}/" && echo metafile=${file} - [[ -z "${metafile}" ]] && println ERROR "${FG_RED}ERROR${NC}: Metadata file path empty!" && waitForInput && continue - [[ ! -f "${metafile}" ]] && println ERROR "${FG_RED}ERROR${NC}: File not found: ${metafile}" && waitForInput && continue - if ! jq -er . "${metafile}"; then println ERROR "${FG_RED}ERROR${NC}: Metadata file not a valid json file!" && waitForInput && continue; fi + [[ -z "${metafile}" ]] && println ERROR "${FG_RED}ERROR${NC}: Metadata file path empty!" && waitToProceed && continue + [[ ! -f "${metafile}" ]] && println ERROR "${FG_RED}ERROR${NC}: File not found: ${metafile}" && waitToProceed && continue + if ! jq -er . "${metafile}"; then println ERROR "${FG_RED}ERROR${NC}: Metadata file not a valid json file!" && waitToProceed && continue; fi metafile_param="--metadata-json-file ${metafile}" ;; esac @@ -4760,28 +4898,23 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "balance" "${WALLET_PAY_SK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "balance" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac fi echo - getBaseAddress ${wallet_name} - getPayAddress ${wallet_name} - getBalance ${base_addr} - base_lovelace=${assets[lovelace]} - getBalance ${pay_addr} - pay_lovelace=${assets[lovelace]} + getWalletBalance ${wallet_name} true true true true if [[ ${pay_lovelace} -gt 0 && ${base_lovelace} -gt 0 ]]; then # Both payment and base address available with funds, let user choose what to use println DEBUG "Select source wallet address" @@ -4792,33 +4925,35 @@ function main { echo select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" case $? in - 0) addr="${base_addr}" ;; - 1) addr="${pay_addr}" ;; + 0) addr="${base_addr}"; lovelace=${base_lovelace} ;; + 1) addr="${pay_addr}" ; lovelace=${pay_lovelace} ;; 2) continue ;; esac echo elif [[ ${pay_lovelace} -gt 0 ]]; then addr="${pay_addr}" + lovelace=${pay_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA\n" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" fi elif [[ ${base_lovelace} -gt 0 ]]; then addr="${base_addr}" + lovelace=${base_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA\n" "Funds :" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" - waitForInput && continue + waitToProceed && continue fi if ! mintAsset; then - waitForInput && continue + waitToProceed && continue fi if [[ ! -f "${asset_file}" ]]; then echo "{}" > "${asset_file}"; fi assetJSON=$( jq ". += {minted: \"${asset_minted}\", name: \"${asset_name}\", policyID: \"${policy_id}\", assetName: \"$(asciiToHex "${asset_name}")\", policyValidBeforeSlot: \"${policy_ttl}\", lastUpdate: \"$(date -R)\", lastAction: \"Minted $(formatAsset ${assets_to_mint})\"}" < "${asset_file}") echo -e "${assetJSON}" > "${asset_file}" echo - if ! verifyTx ${addr}; then waitForInput && continue; fi + if ! verifyTx ${addr}; then waitToProceed && continue; fi echo println "Assets successfully minted!" println "Policy Name : ${FG_GREEN}${policy_name}${NC}" @@ -4837,17 +4972,17 @@ function main { println "In Circulation : ${FG_LBLUE}$(formatAsset ${asset_minted})${NC} (local tracking)" ;; esac println DEBUG "\n${FG_YELLOW}Please note that it can take a couple of minutes before minted asset show in wallet${NC}" - waitForInput && continue + waitToProceed && continue ;; ################################################################### burn-asset) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> ADVANCED >> MULTI-ASSET >> BURN ASSET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitForInput && continue + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitForInput && continue + waitToProceed && continue else if ! selectOpMode; then continue; fi fi @@ -4856,40 +4991,35 @@ function main { if [[ ${op_mode} = "online" ]]; then selectWallet "balance" "${WALLET_PAY_SK_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet for asset burning!" && waitForInput && continue ;; - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitForInput && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitForInput && continue ;; + 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet for asset burning!" && waitToProceed && continue ;; + 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else selectWallet "balance" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac getWalletType ${wallet_name} case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet for asset burning!" && waitForInput && continue ;; + 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet for asset burning!" && waitToProceed && continue ;; esac fi # Let user choose asset on wallet to burn, both base and enterprise, fee payed with same address assets_on_wallet=() - getBaseAddress ${wallet_name} - getBalance ${base_addr} - declare -gA base_assets=(); for idx in "${!assets[@]}"; do base_assets[${idx}]=${assets[${idx}]}; done + getWalletBalance ${wallet_name} true true true true for asset in "${!base_assets[@]}"; do [[ ${asset} = "lovelace" ]] && continue IFS='.' read -ra asset_arr <<< "${asset}" [[ -z ${asset_arr[1]} ]] && asset_ascii_name="" || asset_ascii_name=$(hexToAscii ${asset_arr[1]}) assets_on_wallet+=( "${asset} (${asset_ascii_name}) [base addr]" ) done - getPayAddress ${wallet_name} - getBalance ${pay_addr} - declare -gA pay_assets=(); for idx in "${!assets[@]}"; do pay_assets[${idx}]=${assets[${idx}]}; done for asset in "${!pay_assets[@]}"; do [[ ${asset} = "lovelace" ]] && continue IFS='.' read -ra asset_arr <<< "${asset}" @@ -4897,7 +5027,7 @@ function main { assets_on_wallet+=( "${asset} (${asset_ascii_name}) [enterprise addr]" ) done echo - [[ ${#assets_on_wallet[@]} -eq 0 ]] && println ERROR "${FG_RED}ERROR${NC}: Wallet doesn't contain any assets!" && waitForInput && continue + [[ ${#assets_on_wallet[@]} -eq 0 ]] && println ERROR "${FG_RED}ERROR${NC}: Wallet doesn't contain any assets!" && waitToProceed && continue println DEBUG "# Select Asset to burn" select_opt "${assets_on_wallet[@]}" "[Esc] Cancel" selection=$? @@ -4910,10 +5040,12 @@ function main { addr=${base_addr} wallet_source="base" curr_asset_amount=${base_assets[${asset}]} + lovelace=${base_assets[lovelace]} else addr=${pay_addr} wallet_source="enterprise" curr_asset_amount=${pay_assets[${asset}]} + lovelace=${pay_assets[lovelace]} fi echo @@ -4922,7 +5054,7 @@ function main { while IFS= read -r -d '' file; do [[ ${asset_arr[0]} = "$(jq -r .policyID ${file})" ]] && asset_file="${file}" && break done < <(find "${ASSET_FOLDER}" -mindepth 2 -maxdepth 2 -type f -name '*.asset' -print0) - [[ -z "${asset_file}" ]] && println ERROR "${FG_RED}ERROR${NC}: Searched all available policies in '${ASSET_FOLDER}' for matching '.asset' file but non found!" && waitForInput && continue + [[ -z "${asset_file}" ]] && println ERROR "${FG_RED}ERROR${NC}: Searched all available policies in '${ASSET_FOLDER}' for matching '.asset' file but non found!" && waitToProceed && continue [[ ${#asset_arr[@]} -eq 1 ]] && asset_name="" || asset_name="${asset_arr[1]}" @@ -4935,14 +5067,14 @@ function main { policy_id_file="${policy_folder}/${ASSET_POLICY_ID_FILENAME}" policy_id="$(cat "${policy_id_file}")" policy_ttl=$(jq -r '.scripts[0].slot //0' "${policy_script_file}") - [[ ${policy_ttl} -gt 0 && ${policy_ttl} -lt $(getSlotTipRef) ]] && println ERROR "${FG_RED}ERROR${NC}: Policy expired!" && waitForInput && continue + [[ ${policy_ttl} -gt 0 && ${policy_ttl} -lt $(getSlotTipRef) ]] && println ERROR "${FG_RED}ERROR${NC}: Policy expired!" && waitToProceed && continue # ask amount to burn println DEBUG "Available assets to burn: ${FG_LBLUE}$(formatAsset "${curr_asset_amount}")${NC}\n" getAnswerAnyCust assets_to_burn "Amount (commas allowed as thousand separator)" assets_to_burn="${assets_to_burn//,}" [[ ${assets_to_burn} = "all" ]] && assets_to_burn=${curr_asset_amount} - if ! isNumber ${assets_to_burn}; then println ERROR "${FG_RED}ERROR${NC}: Invalid number, should be an integer number. Decimals not allowed!" && waitForInput && continue; fi - [[ ${assets_to_burn} -gt ${curr_asset_amount} ]] && println ERROR "${FG_RED}ERROR${NC}: Amount exceeding assets in address, you can only burn ${FG_LBLUE}$(formatAsset "${asset_amount}")${NC}" && waitForInput && continue + if ! isNumber ${assets_to_burn}; then println ERROR "${FG_RED}ERROR${NC}: Invalid number, should be an integer number. Decimals not allowed!" && waitToProceed && continue; fi + [[ ${assets_to_burn} -gt ${curr_asset_amount} ]] && println ERROR "${FG_RED}ERROR${NC}: Amount exceeding assets in address, you can only burn ${FG_LBLUE}$(formatAsset "${asset_amount}")${NC}" && waitToProceed && continue asset_minted=$(( $(jq -r .minted "${asset_file}") - assets_to_burn )) # Attach metadata? metafile_param="" @@ -4952,23 +5084,23 @@ function main { 0) : ;; # do nothing 1) fileDialog "Enter path to metadata JSON file" "${TMP_DIR}/" && echo metafile=${file} - [[ -z "${metafile}" ]] && println ERROR "${FG_RED}ERROR${NC}: Metadata file path empty!" && waitForInput && continue - [[ ! -f "${metafile}" ]] && println ERROR "${FG_RED}ERROR${NC}: File not found: ${metafile}" && waitForInput && continue - if ! jq -er . "${metafile}"; then println ERROR "${FG_RED}ERROR${NC}: Metadata file not a valid json file!" && waitForInput && continue; fi + [[ -z "${metafile}" ]] && println ERROR "${FG_RED}ERROR${NC}: Metadata file path empty!" && waitToProceed && continue + [[ ! -f "${metafile}" ]] && println ERROR "${FG_RED}ERROR${NC}: File not found: ${metafile}" && waitToProceed && continue + if ! jq -er . "${metafile}"; then println ERROR "${FG_RED}ERROR${NC}: Metadata file not a valid json file!" && waitToProceed && continue; fi metafile_param="--metadata-json-file ${metafile}" ;; esac echo # Call burn helper function if ! burnAsset; then - waitForInput && continue + waitToProceed && continue fi - # TODO: Update asset file + # Update asset file if [[ ! -f "${asset_file}" ]]; then echo "{}" > "${asset_file}"; fi assetJSON=$( jq ". += {minted: \"${asset_minted}\", name: \"$(hexToAscii "${asset_name}")\", policyID: \"${policy_id}\", policyValidBeforeSlot: \"${policy_ttl}\", lastUpdate: \"$(date -R)\", lastAction: \"Burned $(formatAsset ${assets_to_burn})\"}" < "${asset_file}") echo -e "${assetJSON}" > "${asset_file}" echo - if ! verifyTx ${addr}; then waitForInput && continue; fi + if ! verifyTx ${addr}; then waitToProceed && continue; fi echo println "Assets successfully burned!" println "Policy Name : ${FG_GREEN}${policy_name}${NC}" @@ -4988,7 +5120,7 @@ function main { println "In Circulation : ${FG_LBLUE}$(formatAsset ${asset_minted})${NC} (local tracking)" ;; esac println DEBUG "\n${FG_YELLOW}Please note that burned assets can take a couple of minutes before being reflected in wallet${NC}" - waitForInput && continue + waitToProceed && continue ;; ################################################################### register-asset) clear @@ -4999,13 +5131,13 @@ function main { if ! cmdAvailable "token-metadata-creator"; then println ERROR "Please follow instructions on Guild Operators site to download or build the tool:" println ERROR "${FG_YELLOW}https://cardano-community.github.io/guild-operators/Build/offchain-metadata-tools/${NC}" - waitForInput && continue + waitToProceed && continue fi - [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies found!${NC}\n\nPlease first create a policy to use for Cardano Token Registry" && waitForInput && continue + [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies found!${NC}\n\nPlease first create a policy to use for Cardano Token Registry" && waitToProceed && continue println DEBUG "# Select the policy to use for Cardano Token Registry" selectPolicy "all" "${ASSET_POLICY_SK_FILENAME}" "${ASSET_POLICY_SCRIPT_FILENAME}" "${ASSET_POLICY_ID_FILENAME}" case $? in - 1) waitForInput; continue ;; + 1) waitToProceed; continue ;; 2) continue ;; esac policy_folder="${ASSET_FOLDER}/${policy_name}" @@ -5036,8 +5168,8 @@ function main { fi println "Please enter the asset name as part of PolicyID.AssetName to create registry file for, either a previously minted coin or new" getAnswerAnyCust asset_name "Asset Name (empty valid)" - [[ ${asset_name} =~ ^[^[:alnum:]]$ ]] && println ERROR "${FG_RED}ERROR${NC}: Asset name should only contain alphanummeric chars!" && waitForInput && continue - [[ ${#asset_name} -gt 32 ]] && println ERROR "${FG_RED}ERROR${NC}: Asset name is limited to 32 chars in length!" && waitForInput && continue + [[ ${asset_name} =~ ^[^[:alnum:]]$ ]] && println ERROR "${FG_RED}ERROR${NC}: Asset name should only contain alphanummeric chars!" && waitToProceed && continue + [[ ${#asset_name} -gt 32 ]] && println ERROR "${FG_RED}ERROR${NC}: Asset name is limited to 32 chars in length!" && waitToProceed && continue asset_file="${policy_folder}/${asset_name}.asset" echo sequence_number=0 @@ -5051,21 +5183,21 @@ function main { fi println DEBUG "# Enter metadata (optional fields can be left empty)" getAnswerAnyCust meta_name "Name [${FG_RED}required${NC}] (Max. 50 chars) " - [[ -z ${meta_name} || ${#meta_name} -gt 50 ]] && println ERROR "\n${FG_RED}ERROR${NC}: Metadata name is a required field and limited to 50 chars in length!" && waitForInput && continue + [[ -z ${meta_name} || ${#meta_name} -gt 50 ]] && println ERROR "\n${FG_RED}ERROR${NC}: Metadata name is a required field and limited to 50 chars in length!" && waitToProceed && continue getAnswerAnyCust meta_desc "Description [${FG_RED}required${NC}] (Max. 500 chars)" - [[ -z ${meta_desc} || ${#meta_desc} -gt 500 ]] && println ERROR "\n${FG_RED}ERROR${NC}: Metadata description is a required field and limited to 500 chars in length!" && waitForInput && continue + [[ -z ${meta_desc} || ${#meta_desc} -gt 500 ]] && println ERROR "\n${FG_RED}ERROR${NC}: Metadata description is a required field and limited to 500 chars in length!" && waitToProceed && continue getAnswerAnyCust meta_ticker "Ticker [${FG_YELLOW}optional${NC}] (3-9 chars) " - [[ -n ${meta_ticker} && ( ${#meta_ticker} -lt 3 || ${#meta_ticker} -gt 9 ) ]] && println ERROR "\n${FG_RED}ERROR${NC}: Metadata ticker is limited to 3-9 chars in length!" && waitForInput && continue + [[ -n ${meta_ticker} && ( ${#meta_ticker} -lt 3 || ${#meta_ticker} -gt 9 ) ]] && println ERROR "\n${FG_RED}ERROR${NC}: Metadata ticker is limited to 3-9 chars in length!" && waitToProceed && continue getAnswerAnyCust meta_url "URL [${FG_YELLOW}optional${NC}] (Max. 250 chars)" - [[ -n ${meta_url} && ( ! ${meta_url} =~ https://.* || ${#meta_url} -gt 250 ) ]] && println ERROR "\n${FG_RED}ERROR${NC}: Invalid metadata URL format or greater than 250 char limit!" && waitForInput && continue + [[ -n ${meta_url} && ( ! ${meta_url} =~ https://.* || ${#meta_url} -gt 250 ) ]] && println ERROR "\n${FG_RED}ERROR${NC}: Invalid metadata URL format or greater than 250 char limit!" && waitToProceed && continue getAnswerAnyCust meta_decimals "Decimals [${FG_YELLOW}optional${NC}]" - [[ -n ${meta_decimals} ]] && ! isNumber ${meta_decimals} && println ERROR "\n${FG_RED}ERROR${NC}: Invalid decimal number" && waitForInput && continue + [[ -n ${meta_decimals} ]] && ! isNumber ${meta_decimals} && println ERROR "\n${FG_RED}ERROR${NC}: Invalid decimal number" && waitToProceed && continue fileDialog "Logo/Icon [${FG_YELLOW}optional${NC}] (PNG, <64kb) " "${TMP_DIR}/" meta_logo="${file}" if [[ -n ${meta_logo} ]]; then - [[ ! -f ${meta_logo} ]] && println ERROR "\n${FG_RED}ERROR${NC}: Logo not found!" && waitForInput && continue - [[ $(wc -c ${meta_logo} | cut -d' ' -f1) -gt 64000 ]] && println ERROR "\n${FG_RED}ERROR${NC}: Logo more than 64kb in size!" && waitForInput && continue - [[ $(file -b ${meta_logo}) != "PNG"* ]] && println ERROR "\n${FG_RED}ERROR${NC}: Logo not of PNG image type!" && waitForInput && continue + [[ ! -f ${meta_logo} ]] && println ERROR "\n${FG_RED}ERROR${NC}: Logo not found!" && waitToProceed && continue + [[ $(wc -c ${meta_logo} | cut -d' ' -f1) -gt 64000 ]] && println ERROR "\n${FG_RED}ERROR${NC}: Logo more than 64kb in size!" && waitToProceed && continue + [[ $(file -b ${meta_logo}) != "PNG"* ]] && println ERROR "\n${FG_RED}ERROR${NC}: Logo not of PNG image type!" && waitToProceed && continue fi asset_subject="${policy_id}$(asciiToHex "${asset_name}")" @@ -5083,33 +5215,33 @@ function main { [[ -n ${meta_decimals} && ${meta_decimals} -gt 0 ]] && cmd_args+=( "--decimals" "${meta_decimals}" ) [[ -n ${meta_logo} ]] && cmd_args+=( "--logo" "${meta_logo}" ) - pushd ${policy_folder} &>/dev/null || { println ERROR "\n${FG_RED}ERROR${NC}: unable to change directory to: ${policy_folder}" && waitForInput && continue; } + pushd ${policy_folder} &>/dev/null || { println ERROR "\n${FG_RED}ERROR${NC}: unable to change directory to: ${policy_folder}" && waitToProceed && continue; } # Create JSON draft println DEBUG false "\nCreating Cardano Metadata Registry JSON draft file ..." - ! meta_file=$(token-metadata-creator "${cmd_args[@]}" 2>&1) && println ERROR "\n${FG_RED}ERROR${NC}: failure during token-metadata-creator draft:\n${meta_file}" && popd >/dev/null && waitForInput && continue + ! meta_file=$(token-metadata-creator "${cmd_args[@]}" 2>&1) && println ERROR "\n${FG_RED}ERROR${NC}: failure during token-metadata-creator draft:\n${meta_file}" && popd >/dev/null && waitToProceed && continue println DEBUG " ${FG_GREEN}OK${NC}!" # Update the sequence number if needed if [[ ${sequence_number} -ne 0 ]]; then println DEBUG false "Updating sequence number to ${FG_LBLUE}${sequence_number}${NC} ..." - ! sed -i "s/\"sequenceNumber\":\ .*,/\"sequenceNumber\":\ ${sequence_number},/g" ${meta_file} && popd >/dev/null && waitForInput && continue + ! sed -i "s/\"sequenceNumber\":\ .*,/\"sequenceNumber\":\ ${sequence_number},/g" ${meta_file} && popd >/dev/null && waitToProceed && continue println DEBUG " ${FG_GREEN}OK${NC}!" fi # Signing draft file with policy signing key println DEBUG false "Signing draft file with policy signing key ..." - ! meta_file=$(token-metadata-creator entry ${asset_subject} -a "${policy_sk_file}" 2>&1) && println ERROR "\n${FG_RED}ERROR${NC}: failure during token-metadata-creator signing:\n${meta_file}" && popd >/dev/null && waitForInput && continue + ! meta_file=$(token-metadata-creator entry ${asset_subject} -a "${policy_sk_file}" 2>&1) && println ERROR "\n${FG_RED}ERROR${NC}: failure during token-metadata-creator signing:\n${meta_file}" && popd >/dev/null && waitToProceed && continue println DEBUG " ${FG_GREEN}OK${NC}!" # Finalizing the draft file println DEBUG false "Finalizing the draft file ..." - ! meta_file=$(token-metadata-creator entry ${asset_subject} --finalize 2>&1) && println ERROR "\n${FG_RED}ERROR${NC}: failure during token-metadata-creator finalize:\n${meta_file}" && popd >/dev/null && waitForInput && continue + ! meta_file=$(token-metadata-creator entry ${asset_subject} --finalize 2>&1) && println ERROR "\n${FG_RED}ERROR${NC}: failure during token-metadata-creator finalize:\n${meta_file}" && popd >/dev/null && waitToProceed && continue println DEBUG " ${FG_GREEN}OK${NC}!" # Validating the final metadata registry submission file println DEBUG false "Validating the final metadata registry submission file ..." - ! output=$(token-metadata-creator validate ${meta_file} 2>&1) && println ERROR "\n${FG_RED}ERROR${NC}: failure during token-metadata-creator validation:\n${output}" && popd >/dev/null && waitForInput && continue + ! output=$(token-metadata-creator validate ${meta_file} 2>&1) && println ERROR "\n${FG_RED}ERROR${NC}: failure during token-metadata-creator validation:\n${output}" && popd >/dev/null && waitToProceed && continue println DEBUG " ${FG_GREEN}OK${NC}!" popd &>/dev/null || println ERROR "\n${FG_RED}ERROR${NC}: unable to return to previous directory!" @@ -5134,12 +5266,113 @@ function main { ;; esac - waitForInput && continue + waitToProceed && continue ;; ################################################################### esac # advanced >> multi-asset sub OPERATION done # Multi-Asset loop ;; ################################################################### + multi-sig) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> ADVANCED >> MULTI-SIG" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + getAnswerAnyCust ms_wallet_name "Name of multi-sig wallet" + # Remove unwanted characters from wallet name + ms_wallet_name=${ms_wallet_name//[^[:alnum:]]/_} + if [[ -z "${ms_wallet_name}" ]]; then + println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" + waitToProceed && continue + fi + echo + if ! mkdir -p "${WALLET_FOLDER}/${ms_wallet_name}"; then + println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${ms_wallet_name}" + waitToProceed && continue + fi + # Wallet key filenames + ms_stake_vk_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_STAKE_VK_FILENAME}" + ms_stake_sk_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_STAKE_SK_FILENAME}" + ms_pay_script_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_PAY_SCRIPT_FILENAME}" + if [[ $(find "${WALLET_FOLDER}/${ms_wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then + println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}${ms_wallet_name}${NC} already exists" + println " Choose another name or delete the existing one" + waitToProceed && continue + fi + declare -gA key_hashes=() # key hashes as keys to assosiative array to act as a set + unset timelock_after + println OFF "Select wallet(s) / payment credentials (key hash) to include in multi-sig wallet" + while true; do + println DEBUG "\nSelect wallet or manually enter credential?" + select_opt "[w] Wallet" "[c] Payment Credential" "[Esc] Cancel" + echo + case $? in + 0) selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + getCredentials ${wallet_name} + [[ -z ${pay_cred} ]] && println ERROR "${FG_RED}ERROR${NC}: wallet payment credentials not set!" && waitToProceed && continue + key_hashes[${pay_cred}]=1 + ;; + 1) getAnswerAnyCust pay_cred "Payment Credential (key hash)" + [[ ${#pay_cred} -ne 56 ]] && println ERROR "${FG_RED}ERROR${NC}: invalid payment credential entered!" && waitToProceed && continue + key_hashes[${pay_cred}]=1 + ;; + 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; + esac + println DEBUG "\nMulti-Sig size: ${#key_hashes[@]} - Add more wallets / credentials to multi-sig?" + select_opt "[n] No" "[y] Yes" "[Esc] Cancel" + case $? in + 0) break ;; + 1) : ;; + 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; + esac + done + println DEBUG "\n${#key_hashes[@]} wallets / credentials added to multi-sig, how many are required to witness the transaction?" + getAnswerAnyCust required_sig_cnt "Required signatures" + if ! isNumber ${required_sig_cnt} || [[ ${required_sig_cnt} -lt 1 || ${required_sig_cnt} -gt ${#key_hashes[@]} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: invalid signature count entered, must be above 1 and max ${#key_hashes[@]}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue + fi + println DEBUG "\nAdd time lock to multi-sig wallet by only allowing spending from wallet after a certain epoch start?" + select_opt "[n] No" "[y] Yes" "[Esc] Cancel" + case $? in + 0) : ;; + 1) getAnswerAnyCust epoch_no "Epoch" + if ! isNumber ${epoch_no}; then println ERROR "${FG_RED}ERROR${NC}: invalid epoch number entered!"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue; fi + timelock_after=$(getEpochStart ${epoch_no}) + ;; + 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue ;; + esac + # build multi-sig script + jsonscript=$(jq -n --argjson req_sig "${required_sig_cnt}" '{type:"atLeast",required:$req_sig,scripts:[]}') + for sig in "${!key_hashes[@]}"; do + jsonscript=$(jq --arg sig "${sig}" '.scripts += [{type:"sig",keyHash:$sig}]' <<< "${jsonscript}") + done + if [[ -n ${timelock_after} ]]; then + jsonscript=$(jq -n --argjson after "${timelock_after}" --argjson sig_script "${jsonscript}" '{type:"all",scripts:[{type:"after",slot:$after},$sig_script]}') + fi + if ! stdout=$(jq -e . <<< "${jsonscript}" > "${ms_pay_script_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during script file creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file ${ms_stake_vk_file} --signing-key-file ${ms_stake_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file "${ms_stake_vk_file}" --signing-key-file "${ms_stake_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed && continue + fi + chmod 600 "${WALLET_FOLDER}/${ms_wallet_name}/"* + getBaseAddress ${ms_wallet_name} + getPayScriptAddress ${ms_wallet_name} + getRewardAddress ${ms_wallet_name} + echo + println "New Multi-Sig Wallet : ${FG_GREEN}${ms_wallet_name}${NC}" + println "Address : ${FG_LGRAY}${base_addr}${NC}" + println "Script Address : ${FG_LGRAY}${pay_script_addr}${NC}" + println "Reward Address : ${FG_LGRAY}${reward_addr}${NC}" + println DEBUG "\nYou can now send and receive ADA using the above 'Address' or 'Script Address'." + println DEBUG "Note that Script Address will not take part in staking." + waitToProceed && continue + ;; ################################################################### del-keys) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" @@ -5190,7 +5423,7 @@ function main { else println "\n${FG_LBLUE}${key_del_cnt}${NC} private key(s) found and deleted!" fi - waitForInput && continue + waitToProceed && continue ;; ################################################################### esac # advanced sub OPERATION done # Advanced loop diff --git a/scripts/cnode-helper-scripts/env b/scripts/cnode-helper-scripts/env index 704e3ba71..1079b15b4 100644 --- a/scripts/cnode-helper-scripts/env +++ b/scripts/cnode-helper-scripts/env @@ -49,11 +49,15 @@ #WALLET_PAY_SK_FILENAME="payment.skey" #WALLET_HW_PAY_SK_FILENAME="payment.hwsfile" #WALLET_PAY_ADDR_FILENAME="payment.addr" +#WALLET_PAY_SCRIPT_FILENAME="payment.script" +#WALLET_PAY_SCRIPT_ADDR_FILENAME="payment-script.addr" +#WALLET_PAY_CRED_FILENAME="payment.cred" #WALLET_BASE_ADDR_FILENAME="base.addr" #WALLET_STAKE_VK_FILENAME="stake.vkey" #WALLET_STAKE_SK_FILENAME="stake.skey" #WALLET_HW_STAKE_SK_FILENAME="stake.hwsfile" #WALLET_STAKE_ADDR_FILENAME="reward.addr" +#WALLET_STAKE_CRED_FILENAME="stake.cred" #WALLET_STAKE_CERT_FILENAME="stake.cert" #WALLET_STAKE_DEREG_FILENAME="stake.dereg" #WALLET_DELEGCERT_FILENAME="delegation.cert" @@ -764,11 +768,34 @@ slotInterval() { echo "(${SLOT_LENGTH} / ${ACTIVE_SLOTS_COEFF}) + 0.5" | bc -l | awk '{printf "%.0f\n", $1}' } +# Description : Get current protocol params from either node or Koios +# Error codes: +# 1 : CLI - node socket not available +# 2 : CLI - node response not valid json +# 3 : Koios - general error getProtocolParams() { - PROT_PARAMS="$(${CCLI} ${NETWORK_ERA} query protocol-parameters ${NETWORK_IDENTIFIER} 2>/dev/null)" - read -ra PROT_PARAMS_VERSION <<<"$(jq -r '[ .protocolVersion.major //0, .protocolVersion.minor //0]| @tsv' <<<"${PROT_PARAMS}" 2>/dev/null)" - PROT_MAJOR=${PROT_PARAMS_VERSION[0]} - PROT_MINOR=${PROT_PARAMS_VERSION[1]} + if [[ -n ${KOIOS_API} ]]; then + [[ $(type -t println) = function ]] && println ACTION "curl -sSL -f -X GET -H \"accept: application/json\" ${KOIOS_API}/cli_protocol_params" + if ! PROT_PARAMS=$(curl -sSL -f -X GET -H "accept: application/json" "${KOIOS_API}/cli_protocol_params" 2>&1); then + return 3 + fi + else + PROT_PARAMS="$(${CCLI} ${NETWORK_ERA} query protocol-parameters ${NETWORK_IDENTIFIER} 2>&1)" + if grep -q "Network.Socket.connect" <<< "${PROT_PARAMS}"; then + return 1 + elif [[ -z "${PROT_PARAMS}" ]] || ! jq -er . <<< "${PROT_PARAMS}" &>/dev/null; then + return 2 + fi + fi + # Set a collection of commonly used values + read -r PROT_MAJOR PROT_MINOR KEY_DEPOSIT POOL_DEPOSIT MIN_POOL_COST POOL_RETIRE_MAX_EPOCH <<<"$(jq -r '[ + .protocolVersion.major //0, + .protocolVersion.minor //0, + .stakeAddressDeposit //0, + .stakePoolDeposit //0, + .minPoolCost //0, + .poolRetireMaxEpoch //0 + ] | @tsv' <<<"${PROT_PARAMS}" 2>/dev/null)" PROT_VERSION="${PROT_MAJOR}.${PROT_MINOR}" [[ ${PROT_MAJOR} -eq 0 ]] && return 1 || return 0 } @@ -822,11 +849,15 @@ set_default_vars() { [[ -z ${WALLET_PAY_SK_FILENAME} ]] && WALLET_PAY_SK_FILENAME="payment.skey" [[ -z ${WALLET_HW_PAY_SK_FILENAME} ]] && WALLET_HW_PAY_SK_FILENAME="payment.hwsfile" [[ -z ${WALLET_PAY_ADDR_FILENAME} ]] && WALLET_PAY_ADDR_FILENAME="payment.addr" + [[ -z ${WALLET_PAY_SCRIPT_FILENAME} ]] && WALLET_PAY_SCRIPT_FILENAME="payment.script" + [[ -z ${WALLET_PAY_SCRIPT_ADDR_FILENAME} ]] && WALLET_PAY_SCRIPT_ADDR_FILENAME="payment-script.addr" + [[ -z ${WALLET_PAY_CRED_FILENAME} ]] && WALLET_PAY_CRED_FILENAME="payment.cred" [[ -z ${WALLET_BASE_ADDR_FILENAME} ]] && WALLET_BASE_ADDR_FILENAME="base.addr" [[ -z ${WALLET_STAKE_VK_FILENAME} ]] && WALLET_STAKE_VK_FILENAME="stake.vkey" [[ -z ${WALLET_STAKE_SK_FILENAME} ]] && WALLET_STAKE_SK_FILENAME="stake.skey" [[ -z ${WALLET_HW_STAKE_SK_FILENAME} ]] && WALLET_HW_STAKE_SK_FILENAME="stake.hwsfile" [[ -z ${WALLET_STAKE_ADDR_FILENAME} ]] && WALLET_STAKE_ADDR_FILENAME="reward.addr" + [[ -z ${WALLET_STAKE_CRED_FILENAME} ]] && WALLET_STAKE_CRED_FILENAME="stake.cred" [[ -z ${WALLET_STAKE_CERT_FILENAME} ]] && WALLET_STAKE_CERT_FILENAME="stake.cert" [[ -z ${WALLET_STAKE_DEREG_FILENAME} ]] && WALLET_STAKE_DEREG_FILENAME="stake.dereg" [[ -z ${WALLET_DELEGCERT_FILENAME} ]] && WALLET_DELEGCERT_FILENAME="delegation.cert" @@ -1112,7 +1143,7 @@ if [[ ${OFFLINE_MODE} = "N" && ! -S ${CARDANO_NODE_SOCKET_PATH} ]]; then return 2 fi -if [[ ${OFFLINE_MODE} = "N" ]] && ! getProtocolParams; then +if [[ ${OFFLINE_MODE} = "N" && -z ${KOIOS_API} ]] && ! getProtocolParams; then echo -e "${FG_RED}Failed to query protocol-parameters from node, not yet fully started?${NC}" return 2 fi diff --git a/scripts/cnode-helper-scripts/guild-deploy.sh b/scripts/cnode-helper-scripts/guild-deploy.sh index 08d4243e9..4e0b8a60f 100755 --- a/scripts/cnode-helper-scripts/guild-deploy.sh +++ b/scripts/cnode-helper-scripts/guild-deploy.sh @@ -36,7 +36,7 @@ PARENT="$(dirname $0)" get_answer() { printf "%s (yes/no): " "$*" >&2; read -r answer - while : + while : do case $answer in [Yy]*) @@ -206,11 +206,11 @@ os_dependencies() { pkgmgrcmd="env NEEDRESTART_MODE=a env DEBIAN_FRONTEND=noninteractive env DEBIAN_PRIORITY=critical apt-get" libncurses_pkg="libncursesw5" [[ -f /etc/debian_version ]] && grep -q trixie /etc/debian_version && libncurses_pkg="libncursesw6" - pkg_list="python3 pkg-config libssl-dev ${libncurses_pkg} libtinfo-dev systemd libsystemd-dev libsodium-dev tmux git jq libtool bc gnupg aptitude libtool secure-delete iproute2 tcptraceroute sqlite3 bsdmainutils libusb-1.0-0-dev libudev-dev unzip llvm clang libnuma-dev libpq-dev build-essential libffi-dev libgmp-dev zlib1g-dev make g++ autoconf automake liblmdb-dev procps" + pkg_list="python3 pkg-config libssl-dev ${libncurses_pkg} libtinfo-dev systemd libsystemd-dev libsodium-dev tmux git jq libtool bc gnupg aptitude libtool secure-delete iproute2 tcptraceroute sqlite3 bsdmainutils libusb-1.0-0-dev libudev-dev unzip llvm clang libnuma-dev libpq-dev build-essential libffi-dev libgmp-dev zlib1g-dev make g++ autoconf automake liblmdb-dev procps xxd" elif [[ "${OS_ID}" =~ rhel ]] || [[ "${OS_ID}" =~ fedora ]] || [[ "${DISTRO}" =~ Fedora ]]; then #CentOS/RHEL/Fedora/RockyLinux pkgmgrcmd="yum" - pkg_list="python3 coreutils ncurses-devel ncurses-libs openssl-devel systemd systemd-devel libsodium-devel tmux git jq gnupg2 libtool iproute bc traceroute sqlite util-linux xz wget unzip procps-ng llvm clang numactl-devel libffi-devel gmp-devel zlib-devel make gcc-c++ autoconf udev lmdb-devel" + pkg_list="python3 coreutils ncurses-devel ncurses-libs openssl-devel systemd systemd-devel libsodium-devel tmux git jq gnupg2 libtool iproute bc traceroute sqlite util-linux xz wget unzip procps-ng llvm clang numactl-devel libffi-devel gmp-devel zlib-devel make gcc-c++ autoconf udev lmdb-devel xxd" if [[ "${VERSION_ID}" == "2" ]] ; then #AmazonLinux2 pkg_list="${pkg_list} libusb ncurses-compat-libs pkgconfig srm"