From 4026b3b4d7bb2fe2c7d9e3f94acc03ec5f3bb8ea Mon Sep 17 00:00:00 2001 From: "Ola [AHLNET]" Date: Mon, 9 Sep 2024 05:34:06 +0200 Subject: [PATCH] Cntools 13.2.0 (#1819) ## Description Adds `light` mode support for all governance functions. --------- Co-authored-by: Greg Beresnev --- docs/Scripts/cntools-changelog.md | 5 + scripts/cnode-helper-scripts/cntools.library | 407 +++++++++++++------ scripts/cnode-helper-scripts/cntools.sh | 264 ++++++++---- scripts/cnode-helper-scripts/env | 29 +- 4 files changed, 508 insertions(+), 197 deletions(-) diff --git a/docs/Scripts/cntools-changelog.md b/docs/Scripts/cntools-changelog.md index 44b9f7b29..b532556ec 100644 --- a/docs/Scripts/cntools-changelog.md +++ b/docs/Scripts/cntools-changelog.md @@ -6,6 +6,11 @@ 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.2.0] - 2024-09-08 +#### Added +- `light` mode support for all governance functions. +- CIP-129 support + ## [13.1.0] - 2024-08-01 #### Added - New Vote menu option to hold everything related to voting on Cardano. diff --git a/scripts/cnode-helper-scripts/cntools.library b/scripts/cnode-helper-scripts/cntools.library index 43dec63f3..3c0b3874f 100644 --- a/scripts/cnode-helper-scripts/cntools.library +++ b/scripts/cnode-helper-scripts/cntools.library @@ -13,7 +13,7 @@ # Major: Any considerable change in the code base, big feature, workflow or breaking change from previous version 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=2 # Patch: Backwards compatible bug fixes. No additional functionality or major changes CNTOOLS_PATCH_VERSION=0 @@ -511,7 +511,7 @@ selectWallet() { reward_addr_list=() declare -gA asset_cnt=() declare -gA balances=() - declare -gA reward_pool=() + declare -gA pool_delegations=() declare -gA rewards_available=() for dir in "${dirs[@]}"; do @@ -572,7 +572,7 @@ selectWallet() { 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} + [[ -n ${delegation_pool_id} ]] && pool_delegations[${reward_addr}]=${delegation_pool_id} fi else [[ -n ${reward_addr} ]] && reward_addr_list+=(${reward_addr}) @@ -629,7 +629,7 @@ selectWallet() { elif [[ ${mode} = "delegate" ]]; then getRewardAddress ${wallet_dir} if [[ -n ${reward_addr} ]]; then - delegation_pool_id=${reward_pool[${reward_addr}]} + delegation_pool_id=${pool_delegations[${reward_addr}]} unset poolName if [[ -n ${delegation_pool_id} ]]; then while IFS= read -r -d '' pool; do @@ -1045,7 +1045,7 @@ getPayAddress() { # Command : getGovKeyInfo [wallet name] # Description : generate DRep ID and committee key hash getGovKeyInfo() { - unset drep_id drep_hash hash_type cc_cold_id cc_hot_id ms_drep_id ms_drep_hash + unset drep_id drep_id_cip129 drep_hash hash_type cc_cold_hash cc_cold_id cc_cold_id_cip129 cc_hot_hash cc_hot_id cc_hot_id_cip129 ms_drep_id ms_drep_hash drep_vk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_DREP_VK_FILENAME}" drep_sk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_DREP_SK_FILENAME}" drep_script_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_DREP_SCRIPT_FILENAME}" @@ -1059,6 +1059,12 @@ getGovKeyInfo() { ms_drep_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" ms_drep_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_SK_FILENAME}" ms_drep_id_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_ID_FILENAME}" + if [[ -f "${drep_vk_file}" && $(jq -r '.description' "${drep_vk_file}") = *"Hardware"* ]]; then # Hardware wallet + drep_sk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_HW_DREP_SK_FILENAME}" + cc_cold_sk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_HW_CC_COLD_SK_FILENAME}" + cc_hot_sk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_HW_CC_HOT_SK_FILENAME}" + ms_drep_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_HW_DREP_SK_FILENAME}" + fi [[ -f ${drep_id_file} ]] && drep_id=$(cat "${drep_id_file}") [[ -f ${cc_cold_id_file} ]] && cc_cold_id=$(cat "${cc_cold_id_file}") [[ -f ${cc_hot_id_file} ]] && cc_hot_id=$(cat "${cc_hot_id_file}") @@ -1072,6 +1078,11 @@ getGovKeyInfo() { drep_id=$(${CCLI} hash script --script-file "${drep_script_file}") printf "${drep_id}" > "${drep_id_file}" fi + if [[ -z ${ms_drep_id} && -f ${ms_drep_vk_file} ]]; then + println ACTION "${CCLI} conway governance drep id --drep-verification-key-file ${ms_drep_vk_file}" + ms_drep_id=$(${CCLI} conway governance drep id --drep-verification-key-file "${ms_drep_vk_file}") + printf "${ms_drep_id}" > "${ms_drep_id_file}" + fi if [[ -z ${cc_cold_id} && -f ${cc_cold_vk_file} ]]; then println ACTION "bech32 cc_cold <<< \$(${CCLI} conway governance committee key-hash --verification-key-file ${cc_cold_vk_file})" cc_cold_id=$(bech32 cc_cold <<< "$(${CCLI} conway governance committee key-hash --verification-key-file "${cc_cold_vk_file}")") @@ -1082,39 +1093,148 @@ getGovKeyInfo() { cc_hot_id=$(bech32 cc_hot <<< "$(${CCLI} conway governance committee key-hash --verification-key-file "${cc_hot_vk_file}")") printf "${cc_hot_id}" > "${cc_hot_id_file}" fi - if [[ -z ${ms_drep_id} && -f ${ms_drep_vk_file} ]]; then - println ACTION "${CCLI} conway governance drep id --drep-verification-key-file ${ms_drep_vk_file}" - ms_drep_id=$(${CCLI} conway governance drep id --drep-verification-key-file "${ms_drep_vk_file}") - printf "${ms_drep_id}" > "${ms_drep_id_file}" - fi if [[ -n ${drep_id} ]]; then if [[ ${drep_id} = drep* ]]; then hash_type="keyHash" drep_hash="$(bech32 <<< ${drep_id})" + drep_id_cip129="$(bech32 drep <<< "22${drep_hash}")" else hash_type="scriptHash" drep_hash="${drep_id}" - drep_id="$(bech32 drep <<< "${drep_hash}")" + drep_id="$(bech32 drep_script <<< "${drep_hash}")" + drep_id_cip129="$(bech32 drep <<< "23${drep_hash}")" fi fi if [[ -n ${ms_drep_id} ]]; then ms_drep_hash="$(bech32 <<< ${ms_drep_id})" + ms_drep_id=$(bech32 drep_script <<< "${ms_drep_hash}") + fi + if [[ -n ${cc_cold_id} ]]; then + cc_cold_hash=$(bech32 <<< "${cc_cold_id}") + cc_cold_id_cip129=$(bech32 cc_cold <<< "12${cc_cold_hash}") + fi + if [[ -n ${cc_hot_id} ]]; then + cc_hot_hash=$(bech32 <<< "${cc_hot_id}") + cc_hot_id_cip129=$(bech32 cc_hot <<< "02${cc_hot_hash}") + fi +} + +# Command : getDRepIds [type] [hash] +getDRepIds() { + unset drep_id drep_id_cip129 + [[ -z $1 || -z $2 ]] && return 1 + if [[ $1 = keyHash ]]; then + drep_id="$(bech32 drep <<< ${2})" + drep_id_cip129="$(bech32 drep <<< "22${2}")" + else + drep_id="$(bech32 drep_script <<< ${2})" + drep_id_cip129="$(bech32 drep <<< "23${2}")" + fi +} + +# Command : parseDRepId [drep_id] +parseDRepId() { + unset drep_id drep_id_cip129 drep_hash hash_type + [[ -z $1 ]] && return 1 + drep_hash=$(bech32 <<< $1) + if [[ ${#drep_hash} -eq 56 ]]; then + if [[ $1 = drep_script* ]]; then + hash_type=scriptHash + drep_id=$(bech32 drep_script <<< "${drep_hash}") + drep_id_cip129=$(bech32 drep <<< "23${drep_hash}") + else + hash_type=keyHash + drep_id=$(bech32 drep <<< "${drep_hash}") + drep_id_cip129=$(bech32 drep <<< "22${drep_hash}") + fi + elif [[ ${#drep_hash} -eq 58 ]]; then + if [[ ${drep_hash:0:2} = 23 ]]; then + hash_type=scriptHash + drep_hash=${drep_hash:2} + drep_id=$(bech32 drep_script <<< "${drep_hash}") + drep_id_cip129=$1 + else + hash_type=keyHash + drep_hash=${drep_hash:2} + drep_id=$(bech32 drep <<< "${drep_hash}") + drep_id_cip129=$1 + fi fi } +# Command : getCCIds [type] [hash] +getCCIds() { + unset cc_cold_id cc_hot_id cc_cold_id_cip129 cc_hot_id_cip129 + [[ -z $1 || -z $2 ]] && return 1 + if [[ $1 = keyHash ]]; then + cc_cold_id="$(bech32 cc_cold <<< ${2})" + cc_hot_id="$(bech32 cc_hot <<< ${2})" + cc_cold_id_cip129="$(bech32 cc_cold <<< "12${2}")" + cc_hot_id_cip129="$(bech32 cc_cold <<< "02${2}")" + else + cc_cold_id="$(bech32 cc_cold_script <<< ${2})" + cc_hot_id="$(bech32 cc_hot_script <<< ${2})" + cc_cold_id_cip129="$(bech32 cc_cold <<< "13${2}")" + cc_hot_id_cip129="$(bech32 cc_cold <<< "03${2}")" + fi +} + +# Command : getGovActionId [tx_id] [index] +getGovActionId() { + unset action_id action_id_cip129 + [[ -z $1 || -z $2 ]] && return 1 + action_id="${1}#${2}" + action_id_cip129=$(bech32 gov_action <<< "${1}$(printf '%02x' ${2})") +} + +# Command : parseGovActionId [gov_action_id] +parseGovActionId() { + unset action_tx_id action_idx + [[ -z $1 ]] && return 1 + action_hex=$(bech32 <<< "${1}") + action_tx_id=${action_hex:0:64} + action_idx=$(printf "%d" "0x${action_hex:64}") +} + # Command : getDRepStatus [type] [hash] # Description : query status of drep id -# Return : populates ${hash_type} ${drep_anchor_url} ${drep_anchor_hash} ${drep_deposit_amt} ${drep_expiry} +# Return : populates ${hash_type} ${drep_hash} ${drep_anchor_url} ${drep_anchor_hash} ${drep_deposit_amt} ${drep_expiry} ${drep_active} ${drep_vote_power} getDRepStatus() { - unset hash_type drep_anchor_url drep_anchor_hash drep_deposit_amt drep_expiry + unset hash_type drep_anchor_url drep_anchor_hash drep_deposit_amt drep_expiry drep_active drep_vote_power [[ -z $1 || -z $2 || ($1 != keyHash && $1 != scriptHash) ]] && return 1 hash_type="$1" - [[ ${hash_type} = keyHash ]] && hash_param="--drep-key-hash" || hash_param="--drep-script-hash" - println ACTION "${CCLI} conway query drep-state ${hash_param} ${2} ${NETWORK_IDENTIFIER} | jq -r .[0][1]" - drep_state=$(${CCLI} conway query drep-state ${hash_param} ${2} ${NETWORK_IDENTIFIER} | jq -r .[0][1]) - [[ ${drep_state} = null ]] && return 1 - IFS=',' read -r drep_anchor_url drep_anchor_hash drep_deposit_amt drep_expiry < <( jq -cr '"\(.anchor.url//""),\(.anchor.dataHash//""),\(.deposit),\(.expiry)"' <<< "${drep_state}" ) - return 0 + drep_hash="$2" + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + if [[ ${2} = alwaysAbstain ]]; then + _param="drep_always_abstain" + elif [[ ${2} = alwaysNoConfidence ]]; then + _param="drep_always_no_confidence" + else + [[ ${hash_type} = keyHash ]] && _param=$(bech32 drep <<< "22${2}") || _param=$(bech32 drep <<< "23${2}") + fi + HEADERS=("${KOIOS_API_HEADERS[@]}" -H "Content-Type: application/json" -H "accept: text/csv") + println ACTION "curl -sSL -f -X POST ${HEADERS[*]} -d '{\"_drep_ids\":[\"${_param}\"]}' ${KOIOS_API}/drep_info?select=registered,deposit,active,expires_epoch_no,amount,url,hash" + ! drep_info_list=$(curl -sSL -f -X POST "${HEADERS[@]}" -d '{"_drep_ids":["'${_param}'"]}' "${KOIOS_API}/drep_info?select=registered,deposit,active,expires_epoch_no,amount,url,hash" 2>&1) && println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${drep_info_list}\n" && return 1 # print error and return + [[ -z ${drep_info_list} ]] && return 1 + while IFS=',' read -r _registered _deposit _active _expires_epoch_no _amount _url _hash; do + [[ ${_registered} != t ]] && return 1 + drep_anchor_url="${_url}" + drep_anchor_hash="${_hash}" + drep_deposit_amt="${_deposit}" + drep_expiry="${_expires_epoch_no}" + drep_active="${_active}" + drep_vote_power="${_amount}" + return 0 + done <<< "$(tail -n +2 <<< ${drep_info_list})" + elif [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + [[ ${hash_type} = keyHash ]] && hash_param="--drep-key-hash" || hash_param="--drep-script-hash" + println ACTION "${CCLI} conway query drep-state ${hash_param} ${2} ${NETWORK_IDENTIFIER} | jq -r .[0][1]" + drep_state=$(${CCLI} conway query drep-state ${hash_param} ${2} ${NETWORK_IDENTIFIER} | jq -r .[0][1]) + [[ ${drep_state} = null ]] && return 1 + IFS=',' read -r drep_anchor_url drep_anchor_hash drep_deposit_amt drep_expiry < <( jq -cr '"\(.anchor.url//""),\(.anchor.dataHash//""),\(.deposit),\(.expiry)"' <<< "${drep_state}" ) + return 0 + fi + return 1 } # Command : getDRepAnchor [url] [hash] @@ -1142,16 +1262,38 @@ getDRepAnchor() { getDRepVotePower() { unset vote_power vote_power_total vote_power_pct [[ -z $1 ]] && return 1 - println ACTION "${CCLI} conway query drep-stake-distribution --all-dreps ${NETWORK_IDENTIFIER}" - all_drep_vote_power=$(${CCLI} conway query drep-stake-distribution --all-dreps ${NETWORK_IDENTIFIER}) - vote_power_total=$(jq -r '[.[][1]] | add' <<< "${all_drep_vote_power}") - search_string="drep-${1}" - [[ -n $2 ]] && search_string+="-${2}" - vote_power=$(jq -r --arg v "${search_string}" 'first(.[] | select(.[0] == $v)) | .[1]' <<< "${all_drep_vote_power}") - [[ -z ${vote_power} ]] && return 1 - vote_power_pct=$(fractionToPCT "$(bc -l <<< "${vote_power}/${vote_power_total}")") - [[ -z ${vote_power_pct%.*} || ${vote_power_pct%.*} = 0 ]] && pct_precision=4 || pct_precision=2 - vote_power_pct=$(printf "%.${pct_precision}f" ${vote_power_pct}) + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + if [[ $1 = always* ]]; then + getDRepStatus "-" "$1" || return 1 + elif [[ ${drep_hash} != "$2" ]] || ! isNumber ${drep_vote_power}; then + getDRepStatus "$1" "$2" || return 1 + fi + current_epoch=$(getEpoch) + HEADERS=("${KOIOS_API_HEADERS[@]}" -H "accept: text/csv") + println ACTION "curl -sSL -f -X GET ${HEADERS[*]} ${KOIOS_API}/drep_epoch_summary?_epoch_no=${current_epoch}&select=amount" + vote_power_total=$(curl -sSL -f -X GET "${HEADERS[@]}" "${KOIOS_API}/drep_epoch_summary?_epoch_no=${current_epoch}&select=amount") || return 1 + vote_power_total=$(tail -n +2 <<< ${vote_power_total}) + vote_power="${drep_vote_power}" + elif [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + println ACTION "${CCLI} conway query drep-stake-distribution --all-dreps ${NETWORK_IDENTIFIER}" + all_drep_vote_power=$(${CCLI} conway query drep-stake-distribution --all-dreps ${NETWORK_IDENTIFIER}) + vote_power_total=$(jq -r '[.[][1]] | add' <<< "${all_drep_vote_power}") + search_string="drep-${1}" + [[ -n $2 ]] && search_string+="-${2}" + vote_power=$(jq -r --arg v "${search_string}" 'first(.[] | select(.[0] == $v)) | .[1]' <<< "${all_drep_vote_power}") + [[ -z ${vote_power} ]] && return 1 + else + return 1 + fi + [[ -z ${vote_power_total} ]] && vote_power_total=0 + [[ -z ${vote_power} ]] && vote_power=0 + if [[ ${vote_power_total} -eq 0 || ${vote_power} -eq 0 ]]; then + vote_power_pct="0.00" + else + vote_power_pct=$(fractionToPCT "$(bc -l <<< "${vote_power}/${vote_power_total}")") + [[ -z ${vote_power_pct%.*} || ${vote_power_pct%.*} = 0 ]] && pct_precision=4 || pct_precision=2 + vote_power_pct=$(printf "%.${pct_precision}f" ${vote_power_pct}) + fi return 0 } @@ -1160,55 +1302,95 @@ getDRepVotePower() { # Parameters : wallet name > the name of the wallet # Return : populates ${vote_delegation} getWalletVoteDelegation() { + unset vote_delegation if isWalletRegistered $1; then - if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then - [[ -n ${vote_delegation} ]] && return 0 + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + vote_delegation=${vote_delegations[${reward_addr}]} fi + [[ -n ${vote_delegation} ]] && return 0 fi return 1 } # Command : getGovAction [action id] -# Description : fetch governance action information by id +# Description : fetch governance action information by id (cli format) # Return : populates ${vote_action} ${proposal_url} ${proposal_hash} # : 0 = ok, 1 = action not found, 2 = invalid url or content, 3 = hash doesn't match getGovAction() { unset vote_action proposal_url proposal_hash proposal_meta_file [[ -z $1 ]] && return 1 - println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r --arg govActionId \"$1\" 'first(.proposals | to_entries[] | select(.value.actionId.txId | contains(\$govActionId))) | .value'" - vote_action=$(${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r --arg govActionId "$1" 'first(.proposals | to_entries[] | select(.value.actionId.txId | contains($govActionId))) | .value') - [[ -z ${vote_action} ]] && return 1 - IFS=',' read -r proposal_url proposal_hash < <( jq -cr '"\(.proposalProcedure.anchor.url//""),\(.proposalProcedure.anchor.dataHash//"")"' <<< "${vote_action}" ) - [[ ! "${proposal_url}" =~ https?://.* ]] && return 2 - proposal_meta_file="${TMP_DIR}/metadata_$(date '+%Y%m%d%H%M%S').json" - if ! curl -sL -m ${CURL_TIMEOUT} -o "${proposal_meta_file}" ${proposal_url}; then - [[ -f "${proposal_meta_file}" ]] && rm -f "${proposal_meta_file}" - return 2 + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + HEADERS=("${KOIOS_API_HEADERS[@]}" -H "accept: application/json") + println ACTION "curl -sSL -f -X GET ${HEADERS[*]} ${KOIOS_API}/proposal_list?proposal_tx_hash=eq.${1}&proposal_index=eq.${2}" + ! vote_action=$(curl -sSL -f -X GET "${HEADERS[@]}" "${KOIOS_API}/proposal_list?proposal_tx_hash=eq.${1}&proposal_index=eq.${2}" 2>&1) && println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${vote_action}\n" && return 1 # print error and return + [[ ${vote_action} = '[]' ]] && return 1 + vote_action=$(jq -er .[0] <<< "${vote_action}") + IFS=',' read -r proposal_url proposal_hash < <( jq -cr '"\(.meta_url//""),\(.meta_hash//"")"' <<< "${vote_action}" ) + elif [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r --arg govActionId \"$1\" 'first(.proposals | to_entries[] | select(.value.actionId.txId | contains(\$govActionId))) | .value'" + vote_action=$(${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r --arg govActionId "$1" 'first(.proposals | to_entries[] | select(.value.actionId.txId | contains($govActionId))) | .value') + [[ -z ${vote_action} ]] && return 1 + IFS=',' read -r proposal_url proposal_hash < <( jq -cr '"\(.proposalProcedure.anchor.url//""),\(.proposalProcedure.anchor.dataHash//"")"' <<< "${vote_action}" ) + else + return 1 + fi + if [[ -n ${proposal_url} ]]; then + proposal_meta_file="${TMP_DIR}/metadata_$(date '+%Y%m%d%H%M%S').json" + [[ "${proposal_url}" = ipfs://* ]] && _proposal_url="https://ipfs.io/ipfs/${proposal_url:7}" || _proposal_url="${proposal_url}" + [[ ! "${_proposal_url}" =~ https?://.* ]] && return 2 + if ! curl -sL -m ${CURL_TIMEOUT} -o "${proposal_meta_file}" ${_proposal_url}; then + [[ -f "${proposal_meta_file}" ]] && rm -f "${proposal_meta_file}" + return 2 + fi + println ACTION "${CCLI} hash anchor-data --file-text ${proposal_meta_file}" + proposal_meta_hash="$(${CCLI} hash anchor-data --file-text "${proposal_meta_file}")" + [[ ${proposal_meta_hash} != "${proposal_hash}" ]] && return 3 fi - println ACTION "${CCLI} hash anchor-data --file-text ${proposal_meta_file}" - proposal_meta_hash="$(${CCLI} hash anchor-data --file-text "${proposal_meta_file}")" - [[ ${proposal_meta_hash} != "${proposal_hash}" ]] && return 3 return 0 } getAllGovActions() { - vote_action_list=(); _vote_action_list=() - println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -er '.proposals[] | @base64'" - for vote_action in $(${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -er '.proposals[] | @base64' 2>/dev/null); do - _vote_action_list+=( "$(jq -cr '"\((.actionId.txId//"") + "#" + (.actionId.govActionIx//0|tostring)),\(.proposalProcedure.govAction.tag//""),\(.proposedIn//0),\(.expiresAfter//0),\(.proposalProcedure.anchor.url//""),\(.dRepVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.dRepVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.dRepVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1))"' <<< "$(base64 -d <<< "${vote_action}")")" ) - done - # reverse order - for ((i=${#_vote_action_list[@]}-1; i>=0; i--)); do - vote_action_list+=( "${_vote_action_list[$i]}" ) - done + unset vote_action_list _vote_action_list _vote_action_votes + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + HEADERS=("${KOIOS_API_HEADERS[@]}" -H "accept: text/csv") + println ACTION "curl -sSL -f -X GET ${HEADERS[*]} ${KOIOS_API}/proposal_list?select=block_time,ratified_epoch,enacted_epoch,dropped_epoch,expired_epoch,proposal_id,proposal_tx_hash,proposal_index,proposal_type,proposed_epoch,expiration,meta_url&ratified_epoch=is.null&enacted_epoch=is.null&dropped_epoch=is.null&expired_epoch=is.null&order=block_time.desc" + _vote_action_list=$(curl -sSL -f -X GET "${HEADERS[@]}" "${KOIOS_API}/proposal_list?select=block_time,ratified_epoch,enacted_epoch,dropped_epoch,expired_epoch,proposal_id,proposal_tx_hash,proposal_index,proposal_type,proposed_epoch,expiration,meta_url&ratified_epoch=is.null&enacted_epoch=is.null&dropped_epoch=is.null&expired_epoch=is.null&order=block_time.desc" 2>&1) || return 1 + vote_action_list=() + while IFS=',' read -r _block_time _ratified_epoch _enacted_epoch _dropped_epoch _expired_epoch _proposal_id _proposal_tx_hash _proposal_index _proposal_type _proposed_epoch _expiration _meta_url; do + println ACTION "curl -sSL -f -X GET ${HEADERS[*]} ${KOIOS_API}/proposal_voting_summary?_proposal_id=${_proposal_id}&select=drep_yes_votes_cast,drep_yes_vote_power,drep_yes_pct,drep_no_votes_cast,drep_no_vote_power,drep_no_pct,pool_yes_votes_cast,pool_yes_vote_power,pool_yes_pct,pool_no_votes_cast,pool_no_vote_power,pool_no_pct,committee_yes_votes_cast,committee_yes_pct,committee_no_votes_cast,committee_no_pct" + _vote_action_votes=$(curl -sSL -f -X GET "${HEADERS[@]}" "${KOIOS_API}/proposal_voting_summary?_proposal_id=${_proposal_id}&select=drep_yes_votes_cast,drep_yes_vote_power,drep_yes_pct,drep_no_votes_cast,drep_no_vote_power,drep_no_pct,pool_yes_votes_cast,pool_yes_vote_power,pool_yes_pct,pool_no_votes_cast,pool_no_vote_power,pool_no_pct,committee_yes_votes_cast,committee_yes_pct,committee_no_votes_cast,committee_no_pct" 2>&1) || continue + IFS=',' read -r drep_yes_votes_cast drep_yes_vote_power drep_yes_pct drep_no_votes_cast drep_no_vote_power drep_no_pct pool_yes_votes_cast pool_yes_vote_power pool_yes_pct pool_no_votes_cast pool_no_vote_power pool_no_pct committee_yes_votes_cast committee_yes_pct committee_no_votes_cast committee_no_pct <<< "$(tail -n +2 <<< ${_vote_action_votes})" + vote_action_list+=( "${_proposal_tx_hash}#${_proposal_index},${_proposal_type},${_proposed_epoch},${_expiration:=0},${_meta_url},${drep_yes_votes_cast},${drep_yes_vote_power},${drep_yes_pct},${drep_no_votes_cast},${drep_no_vote_power},${drep_no_pct},${pool_yes_votes_cast},${pool_yes_vote_power},${pool_yes_pct},${pool_no_votes_cast},${pool_no_vote_power},${pool_no_pct},${committee_yes_votes_cast},${committee_yes_pct},${committee_no_votes_cast},${committee_no_pct}" ) + done <<< "$(tail -n +2 <<< ${_vote_action_list})" + elif [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + vote_action_list=(); _vote_action_list=() + println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -er '.proposals[] | @base64'" + for vote_action in $(${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -er '.proposals[] | @base64' 2>/dev/null); do + _vote_action_list+=( "$(jq -cr '"\((.actionId.txId//"") + "#" + (.actionId.govActionIx//0|tostring)),\(.proposalProcedure.govAction.tag//""),\(.proposedIn//0),\(.expiresAfter//0),\(.proposalProcedure.anchor.url//""),\(.dRepVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.dRepVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.dRepVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1))"' <<< "$(base64 -d <<< "${vote_action}")")" ) + done + # reverse order + for ((i=${#_vote_action_list[@]}-1; i>=0; i--)); do + vote_action_list+=( "${_vote_action_list[$i]}" ) + done + fi } -# Command : isCommitteeMember [hash] +# Command : isCommitteeMember [cold hash] [hot hash] # Description : check if supplied hash is part of current committee list +# Return : 0 = valid member, 1 = not a member, 2 = not authorized isCommitteeMember() { - [[ -z $1 ]] && return 1 - println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r '.committee.members | keys[]' | grep -q $1" - ${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r '.committee.members | keys[]' | grep -q $1 + [[ -z $1 || -z $2 ]] && return 1 + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + HEADERS=("${KOIOS_API_HEADERS[@]}" -H "accept: application/json") + println ACTION "curl -sSL -f -X GET ${HEADERS[*]} ${KOIOS_API}/committee_info?select=members | tail -n +2" + members=$(curl -sSL -f -X GET "${HEADERS[@]}" "${KOIOS_API}/committee_info?select=members" | tail -n +2) + elif [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r '.committee.members | keys[]' | grep -q $1" + members=$(${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r '.committee.members | keys[]') + fi + grep -q $1 <<< "${members}" || return 1 + grep -q $2 <<< "${members}" || return 2 + return 0 } # Command : getBaseAddress [wallet name] | [payment.vkey] [stake.vkey] @@ -1510,7 +1692,6 @@ getBalanceKoios() { ! address_utxo_list=$(curl -sSL -f -X POST "${HEADERS[@]}" -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} @@ -1529,7 +1710,7 @@ getBalanceKoios() { 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}" + done <<< "$(tail -n +2 <<< ${address_utxo_list})" fi } @@ -1658,14 +1839,14 @@ getMinUTxO() { getWalletRewards() { reward_lovelace=-1 if [[ $2 = true ]]; then - declare -gA rewards_available=(); declare -gA reward_status=(); declare -gA reward_pool=(); + declare -gA rewards_available=(); declare -gA reward_status=(); declare -gA pool_delegations=(); fi if isWalletRegistered $1; then if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then : # do nothing, variables populated through isWalletRegistered else reward_lovelace=${rewards_available[${reward_addr}]:-0} - stake_deposit=${reward_stake_deposit[${reward_addr}]} + stake_deposit=${stake_deposits[${reward_addr}]} fi fi } @@ -1676,28 +1857,37 @@ 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=(); declare -gA reward_stake_deposit=(); + declare -gA rewards_available=(); declare -gA reward_status=(); declare -gA pool_delegations=(); declare -gA vote_delegations=(); declare -gA stake_deposits=(); # 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}]' + unset 'pool_delegations[${_reward_addr}]' + unset 'vote_delegations[${_reward_addr}]' done if [[ -n ${KOIOS_API} && -n ${reward_addr_list+x} ]]; then printf -v addr_list_joined '\"%s\",' "${reward_addr_list[@]}" HEADERS=("${KOIOS_API_HEADERS[@]}" -H "Content-Type: application/json" -H "accept: text/csv") - println ACTION "curl -sSL -f -X POST ${HEADERS[*]} -d '{\"_stake_addresses\":[${addr_list_joined%,}]}' ${KOIOS_API}/account_info?select=stake_address,status,delegated_pool,rewards_available,deposit" - ! account_info_list=$(curl -sSL -f -X POST "${HEADERS[@]}" -d '{"_stake_addresses":['${addr_list_joined%,}']}' "${KOIOS_API}/account_info?select=stake_address,status,delegated_pool,rewards_available,deposit" 2>&1) && println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${account_info_list}\n" && return 1 # print error and return + println ACTION "curl -sSL -f -X POST ${HEADERS[*]} -d '{\"_stake_addresses\":[${addr_list_joined%,}]}' ${KOIOS_API}/account_info?select=stake_address,status,delegated_pool,delegated_drep,rewards_available,deposit" + ! account_info_list=$(curl -sSL -f -X POST "${HEADERS[@]}" -d '{"_stake_addresses":['${addr_list_joined%,}']}' "${KOIOS_API}/account_info?select=stake_address,status,delegated_pool,delegated_drep,rewards_available,deposit" 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 deposit; do - [[ ${stake_address} = stake_address ]] && continue # header line + while IFS=',' read -r stake_address status delegated_pool delegated_drep rewards_available deposit; do reward_status["${stake_address}"]="${status}" rewards_available["${stake_address}"]="${rewards_available}" - [[ -n ${delegated_pool} ]] && reward_pool["${stake_address}"]="${delegated_pool}" - reward_stake_deposit["${stake_address}"]="${deposit}" - done <<< "${account_info_list}" + [[ -n ${delegated_pool} ]] && pool_delegations["${stake_address}"]="${delegated_pool}" + if [[ -n ${delegated_drep} ]]; then + # convert to cli format - + vote_delegation_raw=$(bech32 <<< "${delegated_drep}") + if [[ ${vote_delegation_raw:0:2} = '22' ]]; then + vote_delegations["${stake_address}"]="keyHash-${vote_delegation_raw:2}" + else + vote_delegations["${stake_address}"]="scriptHash-${vote_delegation_raw:2}" + fi + fi + stake_deposits["${stake_address}"]="${deposit}" + done <<< "$(tail -n +2 <<< ${account_info_list})" fi } @@ -1743,7 +1933,7 @@ getWalletType() { ms_payment_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_SK_FILENAME}" ms_stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" ms_stake_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_SK_FILENAME}" - if [[ -f "${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" && -f "${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" ]]; then # CNTools wallet + if [[ -f "${payment_vk_file}" && -f "${stake_vk_file}" ]]; then # CNTools wallet wallet_desc=$(jq -r '.description' "${payment_vk_file}") if [[ ${wallet_desc} = *"Hardware"* ]]; then payment_sk_file="${WALLET_FOLDER}/${1}/${WALLET_HW_PAY_SK_FILENAME}" @@ -2292,7 +2482,7 @@ registerStakeWallet() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet Registration"; then return 1; fi @@ -2426,7 +2616,7 @@ deregisterStakeWallet() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet De-Registration"; then return 1; fi @@ -2577,7 +2767,7 @@ sendAssets() { fi fi - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Payment"; then return 1; fi @@ -2706,7 +2896,7 @@ delegate() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet Delegation"; then return 1; fi @@ -2825,7 +3015,7 @@ withdrawRewards() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet Rewards Withdrawal"; then return 1; fi @@ -2953,17 +3143,7 @@ registerPool() { ) [[ -n ${owner_delegation_cert} ]] && build_args+=( --certificate-file "${owner_delegation_cert}" ) - if ! buildTx; then return 1; fi - - needHWCLI=false - for index in "${!owner_wallets[@]}"; do - stake_vk_file="${WALLET_FOLDER}/${owner_wallets[${index}]}/${WALLET_STAKE_VK_FILENAME}" - [[ $(jq .description "${stake_vk_file}") = *Hardware* ]] && needHWCLI=true && break - done - if [[ ${needHWCLI} = true ]]; then - if ! HWCLIversionCheck; then return 1; fi - if ! transformRawTx "${TMP_DIR}"/tx.raw; then return 1; fi - fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Pool Registration"; then return 1; fi @@ -3104,17 +3284,7 @@ modifyPool() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi - - needHWCLI=false - for index in "${!owner_wallets[@]}"; do - stake_vk_file="${WALLET_FOLDER}/${owner_wallets[${index}]}/${WALLET_STAKE_VK_FILENAME}" - [[ $(jq .description "${stake_vk_file}") = *Hardware* ]] && needHWCLI=true && break - done - if [[ ${needHWCLI} = true ]]; then - if ! HWCLIversionCheck; then return 1; fi - if ! transformRawTx "${TMP_DIR}"/tx.raw; then return 1; fi - fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Pool Update"; then return 1; fi @@ -3253,15 +3423,7 @@ deRegisterPool() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi - - needHWCLI=false - pay_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" - [[ $(jq .description "${pay_vk_file}") = *Hardware* ]] && needHWCLI=true - if [[ ${needHWCLI} = true ]]; then - if ! HWCLIversionCheck; then return 1; fi - if ! transformRawTx "${TMP_DIR}"/tx.raw; then return 1; fi - fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Pool De-Registration"; then return 1; fi @@ -3491,7 +3653,7 @@ sendMetadata() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Metadata"; then return 1; fi @@ -3617,7 +3779,7 @@ mintAsset() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Asset Minting"; then return 1; fi @@ -3750,7 +3912,7 @@ burnAsset() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Asset Burning"; then return 1; fi @@ -3886,12 +4048,14 @@ voteDelegation() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet Vote Delegation"; then return 1; fi if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"drep-id\": \"${drep_id}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"drep-hash\": \"${drep_hash}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"drep-id-cip105\": \"${drep_id}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"drep-id-cip129\": \"${drep_id_cip129}\" }" <<< ${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 [[ ${wallet_type} -eq 5 ]]; then @@ -4011,7 +4175,7 @@ registerDRep() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet DRep Registration"; then return 1; fi @@ -4124,7 +4288,7 @@ retireDRep() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet DRep Retire"; then return 1; fi @@ -4234,12 +4398,13 @@ governanceVote() { --out-file "${TMP_DIR}"/tx.raw ) - if ! buildTx; then return 1; fi + if ! buildTx "${TMP_DIR}/tx.raw"; then return 1; fi if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet Governance Vote"; then return 1; fi if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"action-id\": \"${action_id}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"action-id-cip129\": \"${action_id_cip129}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { vote: \"${vote_param//-}\" }" <<< ${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 @@ -4290,15 +4455,19 @@ governanceVote() { if ! submitTx "${tx_signed}"; then return 1; fi } -# Command : buildTx [build_args] +# Command : buildTx [out_file] # Description : Helper function to build a raw transaction # : populate an array variable called 'build_args' with all data -# Parameters : build_args > an array with all the arguments to assemble the transaction +# Parameters : out_file > (optional) output file of tx build command needed for HW transform buildTx() { println ACTION "${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 + if [[ -n $1 ]] && command -v "cardano-hw-cli" &>/dev/null; then + HWCLIversionCheck || return 1 + transformRawTx "$1" || return 1 + fi } # Command : calcMinFee [rax tx file] @@ -4549,8 +4718,8 @@ HWCLIversionCheck() { println ACTION "cardano-hw-cli version" HWCLI_version="$(cardano-hw-cli version 2>/dev/null | head -n 1 | cut -d' ' -f6)" println LOG "cardano-hw-cli version: ${HWCLI_version}" - if ! versionCheck "1.14.0" "${HWCLI_version}"; then - println ERROR "${FG_RED}ERROR${NC}: Vacuumlabs cardano-hw-cli ${FG_LGRAY}v${HWCLI_version}${NC} installed on system, minimum required version is ${FG_GREEN}v1.14.0${NC} !!" + if ! versionCheck "1.16.0" "${HWCLI_version}"; then + println ERROR "${FG_RED}ERROR${NC}: Vacuumlabs cardano-hw-cli ${FG_LGRAY}v${HWCLI_version}${NC} installed on system, minimum required version is ${FG_GREEN}v1.16.0${NC} !!" println ERROR "Please run ${FG_LGRAY}guild-deploy.sh -s w${NC} to upgrade to the latest version." return 1 fi diff --git a/scripts/cnode-helper-scripts/cntools.sh b/scripts/cnode-helper-scripts/cntools.sh index 9d9532196..64a2089a3 100755 --- a/scripts/cnode-helper-scripts/cntools.sh +++ b/scripts/cnode-helper-scripts/cntools.sh @@ -541,13 +541,7 @@ function main { println " >> WALLET >> IMPORT >> HARDWARE WALLET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - println DEBUG "Supported HW wallets: Ledger S, Ledger X, Trezor Model T" - println "Is your hardware wallet one of these models?" - select_opt "[y] Yes" "[n] No" - case $? in - 0) : ;; # do nothing - 1) waitToProceed "Unsupported hardware wallet, press any key to return home" && continue ;; - esac + println DEBUG "${FG_BLUE}NOTE${NC}: Make sure your hardware wallet supported by Cardano and cardano-hw-cli utility" echo if ! cmdAvailable "cardano-hw-cli" &>/dev/null; then println ERROR "${FG_RED}ERROR${NC}: cardano-hw-cli not found in path or executable permission not set." @@ -606,7 +600,6 @@ function main { --path 1852H/1815H/${acct_idx}H/5/${key_idx} --path 1854H/1815H/${acct_idx}H/0/${key_idx} --path 1854H/1815H/${acct_idx}H/2/${key_idx} - --path 1854H/1815H/${acct_idx}H/3/${key_idx} --verification-key-file "${payment_vk_file}" --verification-key-file "${stake_vk_file}" --verification-key-file "${drep_vk_file}" @@ -614,7 +607,6 @@ function main { --verification-key-file "${cc_hot_sk_file}" --verification-key-file "${ms_payment_vk_file}" --verification-key-file "${ms_stake_vk_file}" - --verification-key-file "${ms_drep_vk_file}" --hw-signing-file "${payment_sk_file}" --hw-signing-file "${stake_sk_file}" --hw-signing-file "${drep_sk_file}" @@ -622,7 +614,6 @@ function main { --hw-signing-file "${cc_hot_sk_file}" --hw-signing-file "${ms_payment_sk_file}" --hw-signing-file "${ms_stake_sk_file}" - --hw-signing-file "${ms_drep_sk_file}" ) ;; esac @@ -630,6 +621,9 @@ function main { if ! stdout=$("${HW_DERIVATION_CMD[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: failure during key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi + # make a copy of 1852 DRep keys to 1854 multisig due to lacking HW support + cp "${drep_sk_file}" "${ms_drep_sk_file}" + cp "${drep_vk_file}" "${ms_drep_vk_file}" 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}" 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}" jq '.description = "Delegate Representative Hardware Verification Key"' "${drep_vk_file}" > "${TMP_DIR}/$(basename "${drep_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${drep_vk_file}").tmp" "${drep_vk_file}" @@ -870,7 +864,7 @@ function main { fi if [[ -n ${KOIOS_API} ]]; then [[ -v rewards_available[${reward_addr}] ]] && reward_lovelace=${rewards_available[${reward_addr}]} || reward_lovelace=0 - pool_delegation=${reward_pool[${reward_addr}]} + pool_delegation=${pool_delegations[${reward_addr}]} else getWalletRewards ${wallet_name} fi @@ -1092,7 +1086,7 @@ function main { if [[ -n ${reward_addr} ]]; then if [[ -n ${KOIOS_API} ]]; then [[ -v rewards_available[${reward_addr}] ]] && reward_lovelace=${rewards_available[${reward_addr}]} || reward_lovelace=0 - pool_delegation=${reward_pool[${reward_addr}]} + pool_delegation=${pool_delegations[${reward_addr}]} else getRewardsFromAddr ${reward_addr} fi @@ -1129,15 +1123,19 @@ function main { vote_delegation_type="${vote_delegation%-*}" if [[ ${vote_delegation} = *-* ]]; then vote_delegation_hash="${vote_delegation#*-}" - vote_delegation=$(bech32 drep <<< ${vote_delegation_hash}) while IFS= read -r -d '' _wallet; do getGovKeyInfo "$(basename ${_wallet})" - if [[ "${drep_id}" = "${vote_delegation}" ]]; then - walletName=" ${FG_GREEN}$(basename ${_wallet})${NC}" && break + if [[ "${drep_hash}" = "${vote_delegation_hash}" ]]; then + walletName="$(basename ${_wallet})" && break fi done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) fi - println "Delegation : ${FG_LGRAY}${vote_delegation}${NC}${walletName}" + getDRepIds ${vote_delegation_type} ${vote_delegation_hash} + println "Delegation : CIP-105 => ${FG_LGRAY}${drep_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${drep_id_cip129}${NC}" + if [[ -n ${walletName} ]]; then + println " : Wallet => ${FG_GREEN}${walletName}${NC}" + fi if [[ ${vote_delegation} = always* ]]; then : # do nothing elif getDRepStatus ${vote_delegation_type} ${vote_delegation_hash}; then @@ -3756,8 +3754,11 @@ function main { [[ ${otx_type} = "Asset Burning" ]] && println DEBUG "Assets Left : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-minted"' <<< ${offlineJSON})")${NC}" if [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && otx_metadata=$(jq -er '.metadata' <<< ${offlineJSON}); then println DEBUG "Metadata : \n${otx_metadata}\n"; fi jq -er '."drep-wallet-name"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep Wallet : ${FG_GREEN}$(jq -r '."drep-wallet-name"' <<< ${offlineJSON})${NC}" - jq -er '."drep-id"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep ID : ${FG_LGRAY}$(jq -r '."drep-id"' <<< ${offlineJSON})${NC}" + jq -er '."drep-hash"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep Hash : ${FG_LGRAY}$(jq -r '."drep-hash"' <<< ${offlineJSON})${NC}" + jq -er '."drep-id-cip105"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep ID CIP-105 : ${FG_LGRAY}$(jq -r '."drep-id-cip105"' <<< ${offlineJSON})${NC}" + jq -er '."drep-id-cip129"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep ID CIP-129 : ${FG_LGRAY}$(jq -r '."drep-id-cip129"' <<< ${offlineJSON})${NC}" jq -er '."action-id"' <<< ${offlineJSON} &>/dev/null && println DEBUG "Action ID : ${FG_LGRAY}$(jq -r '."action-id"' <<< ${offlineJSON})${NC}" + jq -er '."action-id-cip129"' <<< ${offlineJSON} &>/dev/null && println DEBUG "Action ID CIP-129: ${FG_LGRAY}$(jq -r '."action-id-cip129"' <<< ${offlineJSON})${NC}" jq -er '.vote' <<< ${offlineJSON} &>/dev/null && println DEBUG "Vote : ${FG_LGRAY}$(jq -r '.vote' <<< ${offlineJSON})${NC}" if [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]]; then @@ -3871,15 +3872,19 @@ function main { vote_delegation_type="${vote_delegation%-*}" if [[ ${vote_delegation} = *-* ]]; then vote_delegation_hash="${vote_delegation#*-}" - vote_delegation=$(bech32 drep <<< ${vote_delegation_hash}) while IFS= read -r -d '' _wallet; do getGovKeyInfo "$(basename ${_wallet})" - if [[ "${drep_id}" = "${vote_delegation}" ]]; then - walletName=" ${FG_GREEN}$(basename ${_wallet})${NC}" && break + if [[ ${drep_hash} = "${vote_delegation_hash}" ]]; then + walletName="$(basename ${_wallet})" && break fi done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) fi - println "Delegation : ${FG_LGRAY}${vote_delegation}${NC}${walletName}" + getDRepIds ${vote_delegation_type} ${vote_delegation_hash} + println "Delegation : CIP-105 => ${FG_LGRAY}${drep_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${drep_id_cip129}${NC}" + if [[ -n ${walletName} ]]; then + println " : Wallet => ${FG_GREEN}${walletName}${NC}" + fi if [[ ${vote_delegation} = always* ]]; then : # do nothing elif getDRepStatus ${vote_delegation_type} ${vote_delegation_hash}; then @@ -3917,7 +3922,8 @@ function main { println "Status : ${FG_YELLOW}Governance keys missing, please derive them if needed${NC}" waitToProceed && continue fi - println "DRep ID : ${FG_LGRAY}${drep_id}${NC}" + println "DRep ID : CIP-105 => ${FG_LGRAY}${drep_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${drep_id_cip129}${NC}" println "DRep Hash : ${FG_LGRAY}${drep_hash}${NC}" if [[ ${hash_type} = keyHash ]]; then println "DRep Type : ${FG_LGRAY}Key${NC}" @@ -3951,9 +3957,13 @@ function main { println "Status : ${FG_YELLOW}DRep key not registered${NC}" fi fi - echo - println "Committee Cold ID : ${FG_LGRAY}${cc_cold_id}${NC}" - println "Committee Hot ID : ${FG_LGRAY}${cc_hot_id}${NC}" + if [[ -n ${cc_cold_id} ]]; then + echo + println "Committee Cold ID : CIP-105 => ${FG_LGRAY}${cc_cold_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${cc_cold_id_cip129}${NC}" + println "Committee Hot ID : CIP-105 => ${FG_LGRAY}${cc_hot_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${cc_hot_id_cip129}${NC}" + fi waitToProceed && continue ;; ################################################################### delegate) @@ -3961,6 +3971,7 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println " >> VOTE >> GOVERNANCE >> DELEGATE" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + unset drep_id_cip129 if ! versionCheck "9.0" "${PROT_VERSION}"; then println INFO "\n${FG_YELLOW}Not yet in Conway era, please revisit once network has crossed into Cardano governance era!${NC}"; waitToProceed && continue fi @@ -3996,7 +4007,7 @@ function main { fi unset drep_wallet drep_hash println DEBUG "\nDo you want to delegate to a local CNTools DRep registered wallet, pre-defined type or specify the DRep?" - select_opt "[w] CNTools DRep Wallet" "[i] DRep (ID or hash)" "[a] Always Abstain" "[c] Always No Confidence" "[Esc] Cancel" + select_opt "[w] CNTools DRep Wallet" "[i] DRep ID" "[a] Always Abstain" "[c] Always No Confidence" "[Esc] Cancel" case $? in 0) selectWallet "none" case $? in @@ -4011,10 +4022,10 @@ function main { waitToProceed && continue fi ;; - 1) getAnswerAnyCust drep_id "DRep (blank to cancel)" + 1) getAnswerAnyCust drep_id "DRep ID [CIP-105 or CIP-129] (blank to cancel)" [[ -z "${drep_id}" ]] && continue - [[ ${drep_id} != drep* ]] && drep_id=$(bech32 drep <<< "${drep_id}" 2>/dev/null) - [[ ${#drep_id} -ne 56 || ${drep_id} != drep* ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid DRep ID entered!" && waitToProceed && continue + parseDRepId "${drep_id}" + [[ -z ${drep_id} ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid DRep ID entered!" && waitToProceed && continue ;; 2) drep_id="alwaysAbstain"; vote_param=("--always-abstain") ;; 3) drep_id="alwaysNoConfidence"; vote_param=("--always-no-confidence") ;; @@ -4022,9 +4033,7 @@ function main { esac unset drep_expiry if [[ ${drep_id} != always* ]]; then - [[ -z ${drep_hash} ]] && drep_hash=$(bech32 <<< "${drep_id}") - getDRepStatus keyHash ${drep_hash} - [[ -z ${drep_expiry} ]] && getDRepStatus scriptHash ${drep_hash} + getDRepStatus ${hash_type} ${drep_hash} if [[ -z ${drep_expiry} ]]; then println ERROR "\n${FG_RED}ERROR${NC}: selected DRep not registered" waitToProceed && continue @@ -4065,7 +4074,11 @@ function main { if ! verifyTx ${base_addr}; then waitToProceed && continue; fi echo println "${FG_GREEN}${wallet_name}${NC} successfully delegated to DRep!" - println "\nDRep ID : ${FG_LGRAY}${drep_id}${NC}" + echo + println "DRep ID : CIP-105 => ${FG_LGRAY}${drep_id}${NC}" + if [[ -n ${drep_id_cip129} ]]; then + println " : CIP-129 => ${FG_LGRAY}${drep_id_cip129}${NC}" + fi if [[ -n ${drep_expiry} ]]; then [[ $(getEpoch) -lt ${drep_expiry} ]] && expire_status="${FG_GREEN}active${NC}" || expire_status="${FG_RED}inactive${NC} (vote power does not count)" println "DRep expiry : epoch ${FG_LBLUE}${drep_expiry}${NC} - ${expire_status}" @@ -4088,28 +4101,51 @@ function main { waitToProceed && continue fi if [[ ${action_cnt} -gt 5 ]]; then - getAnswerAnyCust page_entries "${action_cnt} proposals found. Enter number of actions to display per page (enter for 5)" + getAnswerAnyCust page_entries "${action_cnt} proposals found. Enter number of actions to display per page (enter for 4)" fi - page_entries=${page_entries:=5} - if ! isNumber ${page_entries} || [[ ${page_entries} -eq 0 ]]; then + page_entries=${page_entries:=4} + if ! isNumber ${page_entries} || [[ ${page_entries} -lt 1 ]]; then println ERROR "${FG_RED}ERROR${NC}: invalid number" waitToProceed && continue fi curr_epoch=$(getEpoch) page=1 pages=$(( (action_cnt + (page_entries - 1)) / page_entries )) - echo - tput sc while true; do - tput rc && tput ed + clear + if [[ ${show_details} = Y ]]; then + tput sc && println DEBUG "\nFetching proposal details and metadata...\n" + getGovAction "${action_tx_id}" "${action_idx}" + res=$? + tput rc && tput ed + case ${res} in + 1) println ERROR "\n${FG_RED}ERROR${NC}: governance action id not found!" + waitToProceed && continue ;; + 2) println ERROR "\n${FG_YELLOW}WARN${NC}: invalid governance action proposal anchor url or content" + println DEBUG "URL : ${FG_LGRAY}${proposal_url}${NC}" + waitToProceed ;; + 3) println ERROR "\n${FG_YELLOW}WARN${NC}: invalid governance action proposal anchor hash" + println DEBUG "Action hash : ${FG_LGRAY}${proposal_hash}${NC}" + println DEBUG "Real hash : ${FG_LGRAY}${proposal_meta_hash}${NC}" + waitToProceed ;; + esac + println DEBUG "\nGovernance Action Details${FG_LGRAY}" + jq -er <<< "${vote_action}" 2>/dev/null || echo "${vote_action}" + println DEBUG "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + if [[ -f "${proposal_meta_file}" ]]; then + println DEBUG "\nGovernance Action Anchor Content${FG_LGRAY}" + jq -er "${proposal_meta_file}" 2>/dev/null || cat "${proposal_meta_file}" + fi + unset show_details + waitToProceed && continue + fi start_idx=$(( (page * page_entries) - page_entries )) # loop current page to find max length of entries - max_len=66 # assume action id (66) + max_len=70 # assume action id in CIP-129 format (70) for vote_action in "${vote_action_list[@]:${start_idx}:${page_entries}}"; do - IFS=',' read -r action_id action_type proposed_in expires_after anchor_url <<< "${vote_action}" - [[ ${#action_id} -gt ${max_len} ]] && max_len=${#action_id} - [[ ${#action_type} -gt ${max_len} ]] && max_len=${#action_type} - [[ ${#anchor_url} -gt ${max_len} ]] && max_len=${#anchor_url} + IFS=',' read -r -a vote_action_arr <<< "${vote_action}" + [[ ${#vote_action_arr[0]} -gt ${max_len} ]] && max_len=${#vote_action_arr[0]} + [[ ${#vote_action_arr[4]} -gt ${max_len} ]] && max_len=${#vote_action_arr[4]} done total_len=$(( max_len + 13 + 5 )) border_line="|$(printf "%${total_len}s" | tr " " "=")|" # max value length + longest title (13) + spacing (5) @@ -4119,8 +4155,40 @@ function main { idx=1 for vote_action in "${vote_action_list[@]:${start_idx}:${page_entries}}"; do [[ $idx -ne 1 ]] && printf "|$(printf "%${total_len}s" | tr " " "-")|\n" - IFS=',' read -r action_id action_type proposed_in expires_after anchor_url drep_yes drep_no drep_abstain spo_yes spo_no spo_abstain c_yes c_no c_abstain <<< "${vote_action}" + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + IFS=',' read -r action_id action_type proposed_in expires_after anchor_url drep_yes drep_yes_power drep_yes_pct drep_no drep_no_power drep_no_pct spo_yes spo_yes_power spo_yes_pct spo_no spo_no_power spo_no_pct cc_yes cc_yes_pct cc_no cc_no_pct <<< "${vote_action}" + max_yes_len=${#drep_yes} + max_no_len=${#drep_no} + [[ ${#spo_yes} -gt ${max_yes_len} ]] && max_yes_len=${#spo_yes} + [[ ${#spo_no} -gt ${max_no_len} ]] && max_no_len=${#spo_no} + [[ ${#cc_yes} -gt ${max_yes_len} ]] && max_yes_len=${#cc_yes} + [[ ${#cc_no} -gt ${max_no_len} ]] && max_no_len=${#cc_no} + drep_yes_power="$(formatLovelaceHuman ${drep_yes_power})"; max_yes_power_len=${#drep_yes_power} + drep_no_power="$(formatLovelaceHuman ${drep_no_power})"; max_no_power_len=${#drep_no_power} + spo_yes_power="$(formatLovelaceHuman ${spo_yes_power})"; [[ ${#spo_yes_power} -gt ${max_yes_power_len} ]] && max_yes_power_len=${#spo_yes_power} + spo_no_power="$(formatLovelaceHuman ${spo_no_power})"; [[ ${#spo_no_power} -gt ${max_no_power_len} ]] && max_no_power_len=${#spo_no_power} + max_yes_pct_len=${#drep_yes_pct} + max_no_pct_len=${#drep_no_pct} + [[ ${#spo_yes_pct} -gt ${max_yes_pct_len} ]] && max_yes_pct_len=${#spo_yes_pct} + [[ ${#spo_no_pct} -gt ${max_no_pct_len} ]] && max_no_pct_len=${#spo_no_pct} + [[ ${#cc_yes_pct} -gt ${max_yes_pct_len} ]] && max_yes_pct_len=${#cc_yes_pct} + [[ ${#cc_no_pct} -gt ${max_no_pct_len} ]] && max_no_pct_len=${#cc_no_pct} + else + IFS=',' read -r action_id action_type proposed_in expires_after anchor_url drep_yes drep_no drep_abstain spo_yes spo_no spo_abstain cc_yes cc_no cc_abstain <<< "${vote_action}" + max_yes_len=${#drep_yes} + max_no_len=${#drep_no} + max_abstain_len=${#drep_abstain} + [[ ${#spo_yes} -gt ${max_yes_len} ]] && max_yes_len=${#spo_yes} + [[ ${#spo_no} -gt ${max_no_len} ]] && max_no_len=${#spo_no} + [[ ${#spo_abstain} -gt ${max_abstain_len} ]] && max_abstain_len=${#spo_abstain} + [[ ${#cc_yes} -gt ${max_yes_len} ]] && max_yes_len=${#cc_yes} + [[ ${#cc_no} -gt ${max_no_len} ]] && max_no_len=${#cc_no} + [[ ${#cc_abstain} -gt ${max_abstain_len} ]] && max_abstain_len=${#cc_abstain} + fi + IFS='#' read -r proposal_tx_id proposal_index <<< "${action_id}" + getGovActionId ${proposal_tx_id} ${proposal_index} printf "| %-13s : ${FG_LGRAY}%-${max_len}s${NC} |\n" "Action ID" "${action_id}" + printf "| %-13s : ${FG_LGRAY}%-${max_len}s${NC} |\n" " CIP-129" "${action_id_cip129}" printf "| %-13s : ${FG_LGRAY}%-${max_len}s${NC} |\n" "Type" "${action_type}" printf "| %-13s : epoch ${FG_LBLUE}%-$(( max_len - 6 ))s${NC} |\n" "Proposed In" "${proposed_in}" if [[ ${expires_after} -lt ${curr_epoch} ]]; then @@ -4129,9 +4197,17 @@ function main { printf "| %-13s : epoch ${FG_LBLUE}%-$(( max_len - 6 ))s${NC} |\n" "Expires After" "${expires_after}" fi printf "| %-13s : ${FG_LGRAY}%-${max_len}s${NC} |\n" "Anchor URL" "${anchor_url}" - printf "| %-13s : Yes=${FG_LBLUE}%s${NC} No=${FG_LBLUE}%s${NC} Abstain=${FG_LBLUE}%-$((max_len-4-${#drep_yes}-4-${#drep_no}-9))s${NC} |\n" "DRep" "${drep_yes}" "${drep_no}" "${drep_abstain}" - printf "| %-13s : Yes=${FG_LBLUE}%s${NC} No=${FG_LBLUE}%s${NC} Abstain=${FG_LBLUE}%-$((max_len-4-${#spo_yes}-4-${#spo_no}-9))s${NC} |\n" "SPO" "${spo_yes}" "${spo_no}" "${spo_abstain}" - printf "| %-13s : Yes=${FG_LBLUE}%s${NC} No=${FG_LBLUE}%s${NC} Abstain=${FG_LBLUE}%-$((max_len-4-${#c_yes}-4-${#c_no}-9))s${NC} |\n" "Committee" "${c_yes}" "${c_no}" "${c_abstain}" + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + chars_left=$((max_len-max_yes_len-max_yes_pct_len-max_yes_power_len-max_no_len-max_no_pct_len-max_no_power_len-31)) + printf "| %-13s : Yes=${FG_LBLUE}%-${max_yes_len}s${NC} -> ${FG_LBLUE}%${max_yes_pct_len}s${NC}%% @ ${FG_LBLUE}%${max_yes_power_len}s${NC} VP | No=${FG_LBLUE}%-${max_no_len}s${NC} -> ${FG_LBLUE}%${max_no_pct_len}s${NC}%% @ ${FG_LBLUE}%${max_no_power_len}s${NC} VP %${chars_left}s\n" "DRep" "${drep_yes}" "${drep_yes_pct}" "${drep_yes_power}" "${drep_no}" "${drep_no_pct}" "${drep_no_power}" "|" + printf "| %-13s : Yes=${FG_LBLUE}%-${max_yes_len}s${NC} -> ${FG_LBLUE}%${max_yes_pct_len}s${NC}%% @ ${FG_LBLUE}%${max_yes_power_len}s${NC} VP | No=${FG_LBLUE}%-${max_no_len}s${NC} -> ${FG_LBLUE}%${max_no_pct_len}s${NC}%% @ ${FG_LBLUE}%${max_no_power_len}s${NC} VP %${chars_left}s\n" "SPO" "${spo_yes}" "${spo_yes_pct}" "${spo_yes_power}" "${spo_no}" "${spo_no_pct}" "${spo_no_power}" "|" + printf "| %-13s : Yes=${FG_LBLUE}%-${max_yes_len}s${NC} -> ${FG_LBLUE}%${max_yes_pct_len}s${NC}%%%$((max_yes_power_len+6))s | No=${FG_LBLUE}%-${max_no_len}s${NC} -> ${FG_LBLUE}%${max_no_pct_len}s${NC}%%%$((max_no_power_len+6))s %${chars_left}s\n" "Committee" "${cc_yes}" "${cc_yes_pct}" " " "${cc_no}" "${cc_no_pct}" " " "|" + else + chars_left=$((max_len-max_yes_len-max_no_len-max_abstain_len-16)) + printf "| %-13s : Yes=${FG_LBLUE}%-${max_yes_len}s${NC} No=${FG_LBLUE}%-${max_no_len}s${NC} Abstain=${FG_LBLUE}%-${max_abstain_len}s${NC} %${chars_left}s\n" "DRep" "${drep_yes}" "${drep_no}" "${drep_abstain}" "|" + printf "| %-13s : Yes=${FG_LBLUE}%-${max_yes_len}s${NC} No=${FG_LBLUE}%-${max_no_len}s${NC} Abstain=${FG_LBLUE}%-${max_abstain_len}s${NC} %${chars_left}s\n" "SPO" "${spo_yes}" "${spo_no}" "${spo_abstain}" "|" + printf "| %-13s : Yes=${FG_LBLUE}%-${max_yes_len}s${NC} No=${FG_LBLUE}%-${max_no_len}s${NC} Abstain=${FG_LBLUE}%-${max_abstain_len}s${NC} %${chars_left}s\n" "Committee" "${cc_yes}" "${cc_no}" "${cc_abstain}" "|" + fi ((idx++)) done println DEBUG "${border_line}" @@ -4140,19 +4216,25 @@ function main { println OFF "\nPage ${FG_LBLUE}${page}${NC} of ${FG_LGRAY}${pages}${NC}\n" if [[ ${page} -gt 1 && ${page} -lt ${pages} ]]; then hasPrev=Y; hasNext=Y - println OFF "[p] Previous Page | [n] Next Page | [r] Return" + println OFF "[p] Previous Page | [n] Next Page | [r] Return | [d] Details" elif [[ ${page} -eq 1 && ${page} -lt ${pages} ]]; then hasNext=Y - println OFF "${FG_DGRAY}[p] Previous Page${NC} | [n] Next Page | [r] Return" + println OFF "${FG_DGRAY}[p] Previous Page${NC} | [n] Next Page | [r] Return | [d] Details" else hasPrev=Y - println OFF "[p] Previous Page | ${FG_DGRAY}[n] Next Page${NC} | [r] Return" + println OFF "[p] Previous Page | ${FG_DGRAY}[n] Next Page${NC} | [r] Return | [d] Details" fi read -rsn1 key case ${key} in r ) continue 2 ;; p ) [[ -n ${hasPrev} ]] && ((page--)) ;; n ) [[ -n ${hasNext} ]] && ((page++)) ;; + d ) getAnswerAnyCust action_id "\nGovernance Action ID [# | CIP-129] (blank to cancel)" + [[ -z "${action_id}" ]] && continue + [[ ${action_id} = gov_action* ]] && parseGovActionId ${action_id} || IFS='#' read -r action_tx_id action_idx <<< "${action_id}" + ! isNumber "${action_idx}" && println ERROR "\n${FG_RED}ERROR${NC}: invalid action id!" && waitToProceed && continue + show_details=Y + ;; esac done ;; ################################################################### @@ -4225,10 +4307,14 @@ function main { 4) continue ;; esac if [[ ${vote_mode} = "committee" ]]; then - if ! isCommitteeMember $(bech32 <<< ${cc_cold_id}); then - println ERROR "\n${FG_RED}ERROR${NC}: selected wallet is not an active committee member!" - waitToProceed && continue - fi + isCommitteeMember $(bech32 <<< ${cc_cold_id}) $(bech32 <<< ${cc_hot_id}) + case $? in + 0) : ;; # ok + 1) println ERROR "\n${FG_RED}ERROR${NC}: selected wallet is not an active committee member!" + waitToProceed && continue ;; + 2) println ERROR "\n${FG_RED}ERROR${NC}: selected wallet is an active committee member but have not authorized hot credential for voting!" + waitToProceed && continue ;; + esac hash_type="keyHash" elif [[ ${vote_mode} = "drep" ]]; then if ! getDRepStatus ${hash_type} ${drep_hash}; then @@ -4241,11 +4327,12 @@ function main { fi fi echo - getAnswerAnyCust action_id "Governance Action ID [#] (blank to cancel)" + getAnswerAnyCust action_id "Governance Action ID [# | CIP-129] (blank to cancel)" [[ -z "${action_id}" ]] && continue - IFS='#' read -r action_tx_id action_idx <<< "${action_id}" - ! isNumber "${action_idx}" && println ERROR "\n${FG_RED}ERROR${NC}: invalid action id! #" && waitToProceed && continue - getGovAction "${action_tx_id}" + [[ ${action_id} = gov_action* ]] && parseGovActionId ${action_id} || IFS='#' read -r action_tx_id action_idx <<< "${action_id}" + ! isNumber "${action_idx}" && println ERROR "\n${FG_RED}ERROR${NC}: invalid action id!" && waitToProceed && continue + getGovActionId "${action_tx_id}" "${action_idx}" + getGovAction "${action_tx_id}" "${action_idx}" case $? in 1) println ERROR "\n${FG_RED}ERROR${NC}: governance action id not found!"; waitToProceed && continue ;; 2) println ERROR "\n${FG_YELLOW}WARN${NC}: invalid governance action proposal anchor url or content" @@ -4268,9 +4355,23 @@ function main { esac ;; esac + println DEBUG "\nPrint governance action details?" + select_opt "[y] Yes" "[n] No" + case $? in + 0) println DEBUG "\nGovernance Action Details${FG_LGRAY}" + jq -er <<< "${vote_action}" 2>/dev/null || echo "${vote_action}" + ;; + 1) : ;; # do nothing + esac if [[ -f "${proposal_meta_file}" ]]; then - println DEBUG "\nGovernance Action Anchor Content${FG_LGRAY}" - jq -er "${proposal_meta_file}" 2>/dev/null || cat "${proposal_meta_file}" + println DEBUG "\nPrint anchor content?" + select_opt "[y] Yes" "[n] No" + case $? in + 0) println DEBUG "\nGovernance Action Anchor Content${FG_LGRAY}" + jq -er "${proposal_meta_file}" 2>/dev/null || cat "${proposal_meta_file}" + ;; + 1) : ;; # do nothing + esac fi println DEBUG "${NC}\nHow do you want to vote?" select_opt "[y] Yes" "[n] No" "[a] Abstain" "[Esc] Cancel" @@ -4282,7 +4383,7 @@ function main { esac vote_file="${TMP_DIR}/${action_tx_id}_${action_idx}_$(date '+%Y%m%d%H%M%S').vote" VOTE_CMD=( - ${CCLI} ${NETWORK_ERA} governance vote create + ${CCLI} conway governance vote create ${vote_param} --governance-action-tx-id "${action_tx_id}" --governance-action-index "${action_idx}" @@ -4397,7 +4498,7 @@ function main { if [[ ${is_update} = N ]]; then # registration DREP_REG_CMD=( - ${CCLI} ${NETWORK_ERA} governance drep registration-certificate + ${CCLI} conway governance drep registration-certificate "${drep_reg_param[@]}" --key-reg-deposit-amt ${DREP_DEPOSIT} --out-file "${drep_cert_file}" @@ -4405,7 +4506,7 @@ function main { else # update DREP_REG_CMD=( - ${CCLI} ${NETWORK_ERA} governance drep update-certificate + ${CCLI} conway governance drep update-certificate "${drep_reg_param[@]}" --out-file "${drep_cert_file}" ) @@ -4473,7 +4574,7 @@ function main { drep_ret_param=(--drep-verification-key-file "${drep_vk_file}") fi DREP_RET_CMD=( - ${CCLI} ${NETWORK_ERA} governance drep retirement-certificate + ${CCLI} conway governance drep retirement-certificate "${drep_ret_param[@]}" --deposit-amt ${drep_deposit_amt} --out-file "${drep_cert_file}" @@ -4538,15 +4639,20 @@ function main { 1) waitToProceed; continue ;; 2) continue ;; esac + getWalletType ${wallet_name} + if [[ $? -eq 0 ]]; then + println ERROR "\n${FG_YELLOW}HW wallets currently not supported in a MultiSig DRep, please select only normal mnemonic or cli wallets${NC}" && waitToProceed && continue + fi getGovKeyInfo ${wallet_name} [[ -z ${ms_drep_id} || ${ms_drep_id} != drep* ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid wallet, MultiSig DRep keys not found!" && waitToProceed && continue key_hashes["${ms_drep_hash}"]=1 selected_wallets+=("${wallet_name}") ;; - 1) getAnswerAnyCust drep_id "MultiSig DRep ID (bech32)" - [[ ${drep_id} != drep* ]] && drep_id=$(bech32 drep <<< "${drep_id}" 2>/dev/null) - [[ ${#drep_id} -ne 56 || ${drep_id} != drep* ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid DRep ID entered!" && waitToProceed && continue - key_hashes[$(bech32 <<< "${drep_id}")]=1 + 1) getAnswerAnyCust drep_id "MultiSig DRep ID [CIP-105 or CIP-129] (blank to cancel)" + [[ -z "${drep_id}" ]] && continue + parseDRepId "${drep_id}" + [[ -z ${drep_id} ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid DRep ID entered!" && waitToProceed && continue + key_hashes[${drep_hash})]=1 ;; 2) break ;; 3) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; @@ -4577,10 +4683,12 @@ function main { fi chmod 600 "${WALLET_FOLDER}/${ms_wallet_name}/"* getGovKeyInfo ${ms_wallet_name} + getDRepIds scriptHash ${ms_drep_hash} echo println "New MultiSig DRep : ${FG_GREEN}${ms_wallet_name}${NC}" - println "DRep ID : ${FG_LGRAY}$(bech32 drep <<< ${drep_id} 2>/dev/null)${NC}" - println "DRep Script Hash : ${FG_LGRAY}${drep_id}${NC}" + println "DRep ID : CIP-105 => ${FG_LGRAY}${drep_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${drep_id_cip129}${NC}" + println "DRep Script Hash : ${FG_LGRAY}${ms_drep_hash}${NC}" println DEBUG "\nNote that this is not a normal wallet and can only be used to vote as a DRep coalition." waitToProceed && continue ;; ################################################################### @@ -4627,20 +4735,19 @@ function main { --path 1852H/1815H/${acct_idx}H/3/${key_idx} --path 1852H/1815H/${acct_idx}H/4/${key_idx} --path 1852H/1815H/${acct_idx}H/5/${key_idx} - --path 1854H/1815H/${acct_idx}H/3/${key_idx} --verification-key-file "${drep_vk_file}" --verification-key-file "${cc_cold_vk_file}" --verification-key-file "${cc_hot_vk_file}" - --verification-key-file "${ms_drep_vk_file}" --hw-signing-file "${drep_sk_file}" --hw-signing-file "${cc_cold_sk_file}" --hw-signing-file "${cc_hot_sk_file}" - --hw-signing-file "${ms_drep_sk_file}" ) println ACTION "${HW_CLI_CMD[*]}" if ! stdout=$("${HW_CLI_CMD[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: failure during governance key extraction!\n${stdout}"; waitToProceed && continue fi + cp "${drep_sk_file}" "${ms_drep_sk_file}" + cp "${drep_vk_file}" "${ms_drep_vk_file}" jq '.description = "Delegate Representative Hardware Verification Key"' "${drep_vk_file}" > "${TMP_DIR}/$(basename "${drep_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${drep_vk_file}").tmp" "${drep_vk_file}" jq '.description = "Constitutional Committee Cold Hardware Verification Key"' "${cc_cold_vk_file}" > "${TMP_DIR}/$(basename "${cc_cold_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${cc_cold_vk_file}").tmp" "${cc_cold_vk_file}" jq '.description = "Constitutional Committee Hot Hardware Verification Key"' "${cc_hot_sk_file}" > "${TMP_DIR}/$(basename "${cc_hot_sk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${cc_hot_sk_file}").tmp" "${cc_hot_sk_file}" @@ -4785,9 +4892,12 @@ function main { echo getGovKeyInfo ${wallet_name} println "Wallet : ${FG_GREEN}${wallet_name}${NC}" - println "DRep ID : ${FG_LGRAY}${drep_id}${NC}" - println "Committee Cold ID : ${FG_LGRAY}${cc_cold_id}${NC}" - println "Committee Hot ID : ${FG_LGRAY}${cc_hot_id}${NC}" + println "DRep ID : CIP-105 => ${FG_LGRAY}${drep_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${drep_id_cip129}${NC}" + println "Committee Cold ID : CIP-105 => ${FG_LGRAY}${cc_cold_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${cc_cold_id_cip129}${NC}" + println "Committee Hot ID : CIP-105 => ${FG_LGRAY}${cc_hot_id}${NC}" + println " : CIP-129 => ${FG_LGRAY}${cc_hot_id_cip129}${NC}" waitToProceed && continue ;; ################################################################### esac # vote sub OPERATION diff --git a/scripts/cnode-helper-scripts/env b/scripts/cnode-helper-scripts/env index 033e335ae..20346b617 100644 --- a/scripts/cnode-helper-scripts/env +++ b/scripts/cnode-helper-scripts/env @@ -398,6 +398,19 @@ formatLovelace() { fi } +# Description : Print lovelace/number in human readable format with suffix. +formatLovelaceHuman() { + if isNumber $1; then + if _ada=$(LovelaceToADA $1 0); then + [[ ${_ada} -lt 1000 ]] && printf ${_ada} && return 0 + [[ ${_ada} -lt 1000000 ]] && printf "%.1fK" "$(bc -l <<< "${_ada}/1000")" && return 0 + [[ ${_ada} -lt 1000000000 ]] && printf "%.1fM" "$(bc -l <<< "${_ada}/1000000")" && return 0 + printf "%.1fB" "$(bc -l <<< "${_ada}/1000000000")" && return 0 + fi + fi + return 1 +} + # Description : Pretty print Ada/Token value # : $1 = Amount as Integer formatAsset() { @@ -423,6 +436,20 @@ ADAToLovelace() { fi } +# Description : Convert number in Lovelace to Ada +# : $1 = Amount in Lovelace +# : $2 = number of decimal places (default 6) +LovelaceToADA() { + [[ -z $1 ]] && return 1 + if isNumber $1; then + decimals=${2} + printf "%.${decimals:=6}f" "$(bc -l <<< "${1}/1000000")" + else + printf "${FG_RED}ERROR${NC}: must be a valid integer number" 1>&2 + return 1 + fi +} + # Description : Convert number as percent to fraction # : $1 = number to be converted in range 0-100 pctToFraction() { @@ -1214,7 +1241,7 @@ case ${NWMAGIC} in esac NETWORK_ERA=$(${CCLI} latest query tip ${NETWORK_IDENTIFIER} 2>/dev/null | jq -r '.era //empty' | tr '[:upper:]' '[:lower:]') -[[ -z ${NETWORK_ERA} ]] && NETWORK_ERA=latest +[[ -z ${NETWORK_ERA} ]] && NETWORK_ERA=conway [[ ${OFFLINE_MODE} = "N" && ${SHELLEY_TRANS_EPOCH} -eq -1 ]] && getNodeMetrics && getShelleyTransitionEpoch