diff --git a/scripts/action-create-info.sh b/scripts/action-create-info.sh index 5e8dc93..82120fc 100755 --- a/scripts/action-create-info.sh +++ b/scripts/action-create-info.sh @@ -216,7 +216,11 @@ is_stake_address_script() { is_stake_address_registered(){ local address="$1" - stake_address_deposit=$(cardano-cli conway query stake-address-info --address "$address" | jq -r '.[0].delegationDeposit') + echo -e "Checking if stake address $address is registered on-chain..." + which cardano-cli + cardano-cli conway query stake-address-info --address "$address" + stake_address_deposit=$(cardano-cli conway query stake-address-info --address "$address" | jq -r '.[0].stakeRegistrationDeposit') + if [ "$stake_address_deposit" != "null" ]; then return 0 else diff --git a/scripts/action-create-uc.sh b/scripts/action-create-uc.sh new file mode 100755 index 0000000..987a231 --- /dev/null +++ b/scripts/action-create-uc.sh @@ -0,0 +1,290 @@ +#!/bin/bash + +################################################## + +# Default configuration values + +################################################## + +# Exit immediately if a command exits with a non-zero status, +# treat unset variables as an error, and fail if any command in a pipeline fails +set -euo pipefail + +# Colors +#BLACK='\033[0;30m' +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BRIGHTWHITE='\033[0;37;1m' +NC='\033[0m' + +# Check if cardano-cli is installed +if ! command -v cardano-cli >/dev/null 2>&1; then + echo "Error: cardano-cli is not installed or not in your PATH." >&2 + exit 1 +fi + +# Check if ipfs cli is installed +if ! command -v ipfs >/dev/null 2>&1; then + echo "Error: ipfs cli is not installed or not in your PATH." >&2 + exit 1 +fi + +# Usage message + +usage() { + echo "Usage: $0 [--deposit-return-addr ]" + echo "Options:" + echo " Path to the JSON-LD metadata file" + echo " --deposit-return-addr Check that metadata deposit return address matches provided one (Bech32)" + echo " -h, --help Show this help message and exit" + exit 1 +} + +# Initialize variables with defaults +input_file="" + +# Optional variables +deposit_return_address_input="" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --deposit-return-addr) + if [ -n "${2:-}" ]; then + deposit_return_address_input="$2" + shift 2 + else + echo -e "${RED}Error: --deposit-return-addr requires a value${NC}" >&2 + usage + fi + ;; + -h|--help) + usage + ;; + *) + if [ -z "$input_file" ]; then + input_file="$1" + else + echo -e "${RED}Error: Input file already specified. Unexpected argument: $1${NC}" >&2 + usage + fi + shift + ;; + esac +done + +# If no input file provided, show usage +if [ -z "$input_file" ]; then + echo -e "${RED}Error: No input file specified${NC}" >&2 + usage +fi + +# Check if input file exists +if [ ! -f "$input_file" ]; then + echo -e "${RED}Error: Input file not found: $input_file${NC}" >&2 + exit 1 +fi + +echo -e " " +echo -e "${YELLOW}Creating an Info governance action from a given metadata file${NC}" +echo -e "${CYAN}This script assumes compliance Intersect's Info action schema${NC}" +echo -e "${CYAN}This script assumes that CARDANO_NODE_SOCKET_PATH, CARDANO_NODE_NETWORK_ID and IPFS_GATEWAY_URI are set${NC}" + +# Exit if socket path is not set +if [ -z "$CARDANO_NODE_SOCKET_PATH" ]; then + echo "Error: Cardano node $CARDANO_NODE_SOCKET_PATH environment variable is not set." >&2 + exit 1 +fi + +# Exit if network id is not set +if [ -z "$CARDANO_NODE_NETWORK_ID" ]; then + echo "Error: Cardano node $CARDANO_NODE_NETWORK_ID environment variable is not set." >&2 +fi + +# Get if mainnet or testnet +if [ "$CARDANO_NODE_NETWORK_ID" = "764824073" ] || [ "$CARDANO_NODE_NETWORK_ID" = "mainnet" ]; then + echo -e "${YELLOW}Local node is using mainnet${NC}" + protocol_magic="mainnet" +else + echo -e "${YELLOW}Local node is using a testnet${NC}" + protocol_magic="testnet" +fi + +# Open the provided metadata file + +# Do some basic validation checks on metadata +echo -e " " +echo -e "${CYAN}Doing some basic validation and checks on metadata${NC}" + +# Function to check if jq query returned null or empty +check_field() { + local field_name="$1" + local field_value="$2" + + if [ -z "$field_value" ] || [ "$field_value" = "null" ]; then + echo -e "${RED}Error: Required field '$field_name' not found in metadata${NC}" >&2 + exit 1 + fi +} + +# Extract and validate required fields +title=$(jq -r '.body.title' "$input_file") +check_field "title" "$title" + +ga_type=$(jq -r '.body.onChain.governanceActionType' "$input_file") +check_field "governanceActionType" "$ga_type" + +deposit_return=$(jq -r '.body.onChain.depositReturnAddress' "$input_file") +check_field "depositReturnAddress" "$deposit_return" + +authors=$(jq -r '.authors' "$input_file") +check_field "authors" "$authors" +witness=$(jq -r '.authors[0].witness' "$input_file") +check_field "witness" "$witness" + +if [ "$ga_type" = "info" ]; then + echo "Metadata has correct governanceActionType" +else + echo "Metadata does not have the correct governanceActionType" + echo "Expected: info found: $ga_type" + exit 1 +fi + +# if return address passed in check against metadata +if [ ! -z "$deposit_return_address_input" ]; then + echo "Deposit return address provided" + echo "Comparing provided address to metadata" + if [ "$deposit_return_address_input" = "$deposit_return" ]; then + echo -e "${GREEN}Metadata has expected deposit return address${NC}" + else + echo -e "${RED}Metadata does not have expected deposit return address${NC}" + exit 1 + fi +fi + +# use bech32 prefix to determine if addresses are mainnet or testnet +is_stake_address_mainnet() { + local address="$1" + # Check if address starts with stake1 (mainnet) + if [[ "$address" =~ ^stake1 ]]; then + return 0 + # Check if address starts with stake_test1 (testnet) + elif [[ "$address" =~ ^stake_test1 ]]; then + return 1 + else + echo -e "${RED}Error: Invalid stake address format: $address${NC}" >&2 + exit 1 + fi +} + +# if mainnet node then expect addresses to be mainnet +if [ "$protocol_magic" = "mainnet" ]; then + if is_stake_address_mainnet "$deposit_return"; then + echo -e "Deposit return address is a valid mainnet stake address" + else + echo -e "${RED}Deposit return address is not a valid mainnet stake address${NC}" + exit 1 + fi +else + if ! is_stake_address_mainnet "$deposit_return"; then + echo -e "Deposit return address is a valid testnet stake address" + else + echo -e "${RED}Deposit return address is not a valid testnet stake address${NC}" + exit 1 + fi +fi + +# use header byte to determine if stake address is script-based or key-based +is_stake_address_script() { + local address="$1" + + address_hex=$(cardano-cli address info --address "$address"| jq -r ".base16") + first_char="${address_hex:0:1}" + + if [ "$first_char" = "f" ]; then + return 0 # true + elif [ "$first_char" = "e" ]; then + return 1 # false + else + echo -e "${RED}Error: Invalid stake address header byte${NC}" >&2 + exit 1 + fi +} + +is_stake_address_registered(){ + local address="$1" + echo -e "Checking if stake address $address is registered on-chain..." + which cardano-cli + cardano-cli conway query stake-address-info --address "$address" + stake_address_deposit=$(cardano-cli conway query stake-address-info --address "$address" | jq -r '.[0].delegationDeposit') + + if [ "$stake_address_deposit" != "null" ]; then + return 0 + else + return 1 + fi +} + +# check if stake addresses are registered +if is_stake_address_registered "$deposit_return"; then + echo -e "Deposit return stake address is registered" +else + echo -e "${RED}Deposit return stake address is not registered, exiting.${NC}" + exit 1 +fi + + +echo -e "${GREEN}Automatic validations passed${NC}" +echo -e " " +echo -e "${CYAN}Computing details${NC}" + +# Compute the hash and IPFS URI +file_hash=$(b2sum -l 256 "$input_file" | awk '{print $1}') +echo -e "Metadata file hash: ${YELLOW}$file_hash${NC}" + +ipfs_cid=$(ipfs add -Q --cid-version 1 "$input_file") +echo -e "IPFS URI: ${YELLOW}ipfs://$ipfs_cid${NC}" + +# Make user manually confirm the choices +echo -e " " +echo -e "${CYAN}Creating info action${NC}" +echo -e "Title: ${YELLOW}$title${NC}" +echo -e " " +echo -e "Deposit return address: ${YELLOW}$deposit_return${NC}" +if is_stake_address_script "$deposit_return"; then + echo -e "(this is a script-based address)" +else + echo -e "(this is a key-based address)" +fi + +echo -e " " +read -p "Do you want to proceed with this deposit return address? (yes/no): " confirm_deposit + +if [ "$confirm_deposit" != "yes" ]; then + echo -e "${RED}Deposit address not confirmed by user, exiting.${NC}" + exit 1 +fi + +# Create the action +echo -e " " +echo -e "${CYAN}Creating action file...${NC}" + +cardano-cli conway governance action create-info \ + --$protocol_magic \ + --governance-action-deposit $(cardano-cli conway query gov-state | jq -r '.currentPParams.govActionDeposit') \ + --deposit-return-stake-address "$deposit_return" \ + --anchor-url "ipfs://$ipfs_cid" \ + --anchor-data-hash "$file_hash" \ + --check-anchor-data \ + --out-file "$input_file.action" + +echo -e "${GREEN}Action file created at "$input_file.action" ${NC}" + +echo -e " " +echo -e "${CYAN}Creating JSON representation of action file...${NC}" + +cardano-cli conway governance action view --action-file "$input_file.action" > "$input_file.action.json" +echo -e "${GREEN}JSON file created at "$input_file.action.json" ${NC}" \ No newline at end of file diff --git a/scripts/metadata-create.sh b/scripts/metadata-create.sh index 62d93ee..0495dc8 100755 --- a/scripts/metadata-create.sh +++ b/scripts/metadata-create.sh @@ -33,7 +33,7 @@ usage() { echo "Usage: $0 <.md-file> --governance-action-type --deposit-return-addr " echo "Options:" echo " <.md-file> Path to the .md file as input" - echo " --governance-action-type Type of governance action (info, treasury, protocol param update, etc.)" + echo " --governance-action-type Type of governance action (info, treasury, protocol param update, etc.)" echo " --deposit-return-addr Stake address for deposit return (bech32)" echo " -h, --help Show this help message and exit" exit 1 @@ -157,8 +157,8 @@ get_section() { get_section_last() { local label="$1" - awk "/^${label}\$/,/^### References\$/" "$TEMP_MD" | sed "1d" \ - | jq -Rs . + awk "/^${label}\$/ {found=1; next} /^## References$/ {found=0} found" "$TEMP_MD" | jq -Rs . + } # Extract references from References section @@ -323,6 +323,172 @@ generate_treasury_onchain() { EOF } +collect_remove_committee_credentials() { + echo " " >&2 + echo -e "${YELLOW}Collecting credentials to REMOVE from committee...${NC}" >&2 + + echo -n "How many credentials do you want to remove? (0 if none): " >&2 + IFS= read -r remove_count &2 + exit 1 + fi + + local remove_credentials="[]" + + if [ "$remove_count" -gt 0 ]; then + local creds="[" + for ((i=1; i<=remove_count; i++)); do + echo " " >&2 + echo -e "${CYAN}Credential $i to remove:${NC}" >&2 + + # Ask for credential type + echo -n " Type (script/key): " >&2 + IFS= read -r cred_type &2 + exit 1 + fi + + # Ask for credential hash + echo -n " Hash: " >&2 + IFS= read -r cred_hash &2 + exit 1 + fi + + # Add to credentials array + if [ $i -gt 1 ]; then + creds+="," + fi + creds+="{\"type\":\"$cred_type\",\"hash\":\"$cred_hash\"}" + done + creds+="]" + remove_credentials="$creds" + fi + + echo "$remove_credentials" +} + +collect_add_committee_credentials() { + echo " " >&2 + echo -e "${YELLOW}Collecting credentials to ADD to committee...${NC}" >&2 + + echo -n "How many credentials do you want to add? (0 if none): " >&2 + IFS= read -r add_count &2 + exit 1 + fi + + local add_credentials="[]" + + if [ "$add_count" -gt 0 ]; then + local creds="[" + for ((i=1; i<=add_count; i++)); do + echo " " >&2 + echo -e "${CYAN}Credential $i to add:${NC}" >&2 + + # Ask for credential type + echo -n " Type (script/key): " >&2 + IFS= read -r cred_type &2 + exit 1 + fi + + # Ask for credential hash + echo -n " Hash: " >&2 + IFS= read -r cred_hash &2 + exit 1 + fi + + # Ask for epoch expiry + echo -n " Epoch expiry: " >&2 + IFS= read -r epoch_expiry &2 + exit 1 + fi + + # Add to credentials array + if [ $i -gt 1 ]; then + creds+="," + fi + creds+="{\"type\":\"$cred_type\",\"hash\":\"$cred_hash\",\"epochExpiry\":$epoch_expiry}" + done + creds+="]" + add_credentials="$creds" + fi + + echo "$add_credentials" +} + +generate_update_committee_onchain() { + # Collect credentials to remove and add + local remove_creds=$(collect_remove_committee_credentials) + local add_creds=$(collect_add_committee_credentials) + + # Display summary + echo " " >&2 + echo -e "${GREEN}Committee changes summary:${NC}" >&2 + echo -e " Credentials to remove: $remove_creds" >&2 + echo -e " Credentials to add: $add_creds" >&2 + + # Confirm with user + echo -n "Is this correct? (y/n): " >&2 + IFS= read -r confirm &2 + exit 1 + fi + + # Build committeeChanges object dynamically, excluding empty arrays + local committee_changes="{" + + # Check if add_creds is not empty + if [ "$add_creds" != "[]" ]; then + committee_changes+="\"addCommitteeCredentials\": $add_creds" + fi + + # Check if remove_creds is not empty + if [ "$remove_creds" != "[]" ]; then + # Add comma if add_creds was added + if [ "$add_creds" != "[]" ]; then + committee_changes+="," + fi + committee_changes+="\"removeCommitteeCredentials\": $remove_creds" + fi + + committee_changes+="}" + + # Generate JSON output + cat <