diff --git a/scripts/action-create-tw.sh b/scripts/action-create-tw.sh new file mode 100755 index 0000000..288bea2 --- /dev/null +++ b/scripts/action-create-tw.sh @@ -0,0 +1,350 @@ +#!/bin/bash + +################################################## + +# Default configuration values +WITHDRAW_TO_SCRIPT="false" + +################################################## + +# 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 [--withdraw-to-script] [--deposit-return-addr ] [--withdrawal-addr ]" + echo "Options:" + echo " Path to the JSON-LD metadata file or directory containing metadata files" + echo " --withdraw-to-script Check that the withdrawal address is a script-based address, exit otherwise" + echo " --deposit-return-addr Check that metadata deposit return address matches provided one (Bech32)" + echo " --withdrawal-addr Check that metadata withdrawal address matches provided one (Bech32)" + echo " -h, --help Show this help message and exit" + exit 1 +} + +# Initialize variables with defaults +input_file="" + +# Optional variables +withdraw_to_script="$WITHDRAW_TO_SCRIPT" +deposit_return_address_input="" +withdrawal_address_input="" + +# todo check all the inputs work + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --withdraw-to-script) + withdraw_to_script="true" + shift + ;; + --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 + ;; + --withdrawal-addr) + if [ -n "${2:-}" ]; then + withdrawal_address_input="$2" + shift 2 + else + echo -e "${RED}Error: --withdrawal-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 a treasury withdrawal governance action from a given metadata file${NC}" +echo -e "${CYAN}This script assumes compliance Intersect's treasury withdrawal 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="$CARDANO_NODE_NETWORK_ID" +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" + +# todo: support multiple withdrawals +withdrawal_address=$(jq -r '.body.onChain.withdrawals[0].withdrawalAddress' "$input_file") +check_field "withdrawalAddress" "$withdrawal_address" +withdrawal_amount=$(jq -r '.body.onChain.withdrawals[0].withdrawalAmount' "$input_file") +check_field "withdrawalAmount" "$withdrawal_amount" + +if [ "$ga_type" = "treasuryWithdrawals" ]; then + echo "Metadata has correct governanceActionType" +else + echo "Metadata does not have the correct governanceActionType" + echo "Expected: treasuryWithdrawals 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 + +# check if withdrawal address is provided +if [ ! -z "$withdrawal_address_input" ]; then + echo "Withdrawal address provided" + echo "Comparing provided address to metadata" + if [ "$withdrawal_address_input" = "$withdrawal_address" ]; then + echo -e "${GREEN}Metadata has expected withdrawal address${NC}" + else + echo -e "${RED}Metadata does not have expected withdrawal 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 + + if is_stake_address_mainnet "$withdrawal_address"; then + echo -e "Withdrawal address is a valid mainnet stake address" + else + echo -e "${RED}Withdrawal 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 + + if ! is_stake_address_mainnet "$withdrawal_address"; then + echo -e "Withdrawal address is a valid testnet stake address" + else + echo -e "${RED}Withdrawal 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 +} + +if [ "$withdraw_to_script" = "true" ]; then + if is_stake_address_script "$withdrawal_address"; then + echo -e "Withdrawal address is script-based" + else + echo -e "${RED}Withdrawal address is not script-based as requested, exiting.${NC}" + exit 1 + fi +fi + +echo -e "${GREEN}Automatic validations passed${NC}" +echo -e " " +echo -e "${CYAN}Computing details${NC}" + +# compute if provided addresses are script-based or key-based +# we should warn the user if they are key-based + +# 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 treasury withdrawal 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 + +echo -e " " +echo -e "Withdrawal address: ${YELLOW}$withdrawal_address${NC}" +if is_stake_address_script "$withdrawal_address"; then + echo -e "(this is a script-based address)" +else + echo -e "(this is a key-based address)" +fi + +ada_amount=$(echo "scale=6; $withdrawal_amount / 1000000" | bc) +ada_amount_formatted=$(printf "%'0.6f" "$ada_amount") +echo -e "Withdrawal amount (ada): ${YELLOW}$ada_amount_formatted${NC}" + +echo -e " " +read -p "Do you want to proceed with this withdrawal address and amount? (yes/no): " confirm_withdrawal + +if [ "$confirm_withdrawal" != "yes" ]; then + echo -e "${RED}Withdrawal amount or withdrawal 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-treasury-withdrawal \ + --mainnet \ + --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 \ + --funds-receiving-stake-address "$withdrawal_address" \ + --transfer "$withdrawal_amount" \ + --constitution-script-hash $(cardano-cli conway query constitution | jq -r '.script') \ + --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