Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔒 Add Offline Hash Calculation Using JSON File #19

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ This Bash [script](./safe_hashes.sh) calculates the Safe transaction hashes by r
> For macOS users, please refer to the [macOS Users: Upgrading Bash](#macos-users-upgrading-bash) section.

```console
./safe_hashes.sh [--help] [--list-networks] --network <network> --address <address> --nonce <nonce> --message <file>
./safe_hashes.sh [--help] [--list-networks] --network <network> --address <address> --nonce <nonce> --message <file> --json <file>
```

**Options:**
Expand All @@ -67,6 +67,7 @@ This Bash [script](./safe_hashes.sh) calculates the Safe transaction hashes by r
- `--address <address>`: Specify the Safe multisig address.
- `--nonce <nonce>`: Specify the transaction nonce (required for transaction hashes).
- `--message <file>`: Specify the message file (required for off-chain message hashes).
- `--json <file>`: Specify a JSON file with transaction data (alternative to API-based retrieval).

Before you invoke the [script](./safe_hashes.sh), make it executable:

Expand Down Expand Up @@ -141,7 +142,11 @@ Make sure to replace `BASH_PATH` with the actual path you retrieved in step 1.

## Safe Transaction Hashes

To calculate the Safe transaction hashes for a specific transaction, you need to specify the `network`, `address`, and `nonce` parameters. An example:
To calculate the Safe transaction hashes for a specific transaction, you have two options:

### Option 1: Using the Safe Transaction Service API

To use the API, you need to specify the `network`, `address`, and `nonce` parameters. An example:

```console
./safe_hashes.sh --network arbitrum --address 0x111CEEee040739fD91D29C34C33E6B3E112F2177 --nonce 234
Expand Down Expand Up @@ -197,6 +202,36 @@ To list all supported networks:
./safe_hashes.sh --list-networks
```

### Option 2: Using a JSON Input File

If the Safe API is unavailable or you want to calculate transaction hashes offline, you can provide the transaction data in a JSON file:

```console
./safe_hashes.sh --json transaction.json
```

The JSON file should contain the following fields:

```json
{
"chainid": "1", // Chain ID (required)
"safe_address": "0x1234567890123456789012345678901234567890", // Safe multisig address (required)
"safe_version": "1.3.0", // Safe contract version (required)
"nonce": "42", // Transaction nonce (required)
"to_address": "0xabcdef0123456789abcdef0123456789abcdef01", // Destination address
"value": "1000000000000000000", // Value in wei
"data": "0x", // Transaction data
"operation": "0", // Operation type (0=Call, 1=DelegateCall)
"safe_transaction_gas": "21000", // SafeTxGas
"base_gas": "0", // BaseGas
"gas_price": "20000000000", // Gas price
"gas_token": "0x0000000000000000000000000000000000000000", // Gas token address
"refund_receiver": "0x0000000000000000000000000000000000000000" // Refund receiver address
}
```

A sample JSON file is provided in the repository as `sample_transaction.json`.

## Safe Message Hashes

This [script](./safe_hashes.sh) not only calculates Safe transaction hashes but also supports computing the corresponding hashes for off-chain messages following the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) standard. To calculate the Safe message hashes for a specific message, specify the `network`, `address`, and `message` parameters. The `message` parameter must specify a valid file containing the raw message. This can be either the file name or a relative path (e.g., `path/to/message.txt`). Note that the [script](./safe_hashes.sh) normalises line endings to `LF` (`\n`) in the message file.
Expand Down
104 changes: 98 additions & 6 deletions safe_hashes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,23 @@ usage() {
cat <<EOF
Usage: $0 [--help] [--list-networks]
--network <network> --address <address> --nonce <nonce>
--message <file>
--message <file> --json <file>

Options:
--help Display this help message
--list-networks List all supported networks and their chain IDs
--network <network> Specify the network (required)
--address <address> Specify the Safe multisig address (required)
--nonce <nonce> Specify the transaction nonce (required for transaction hashes)
--network <network> Specify the network (required for API-based retrieval)
--address <address> Specify the Safe multisig address (required for API-based retrieval)
--nonce <nonce> Specify the transaction nonce (required for transaction hashes via API)
--message <file> Specify the message file (required for off-chain message hashes)
--json <file> Specify a JSON file with transaction data (alternative to API-based retrieval)

Example for transaction hashes:
Example for transaction hashes via API:
$0 --network ethereum --address 0x1234...5678 --nonce 42

Example for transaction hashes via JSON file:
$0 --json transaction.json

Example for off-chain message hashes:
$0 --network ethereum --address 0x1234...5678 --message message.txt
EOF
Expand Down Expand Up @@ -508,7 +512,7 @@ calculate_safe_hashes() {
usage
fi

local network="" address="" nonce="" message_file=""
local network="" address="" nonce="" message_file="" json_file=""

# Parse the command line arguments.
while [[ $# -gt 0 ]]; do
Expand All @@ -518,11 +522,99 @@ calculate_safe_hashes() {
--address) address="$2"; shift 2 ;;
--nonce) nonce="$2"; shift 2 ;;
--message) message_file="$2"; shift 2 ;;
--json) json_file="$2"; shift 2 ;;
--list-networks) list_networks ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done

# Handle JSON file input if provided
if [[ -n "$json_file" ]]; then
# Validate that the JSON file exists
if [[ ! -f "$json_file" ]]; then
echo -e "${BOLD}${RED}JSON file not found: \"${json_file}\"!${RESET}" >&2
exit 1
fi

# Read and parse the JSON file
local json_content=$(cat "$json_file")

# Extract values from the JSON file
local chain_id=$(echo "$json_content" | jq -r ".chainid // \"\"")
local safe_address=$(echo "$json_content" | jq -r ".safe_address // \"\"")
local safe_version=$(echo "$json_content" | jq -r ".safe_version // \"\"")
local json_nonce=$(echo "$json_content" | jq -r ".nonce // \"\"")
local to_address=$(echo "$json_content" | jq -r ".to_address // \"0x0000000000000000000000000000000000000000\"")
local value=$(echo "$json_content" | jq -r ".value // \"0\"")
local data=$(echo "$json_content" | jq -r ".data // \"0x\"")
local operation=$(echo "$json_content" | jq -r ".operation // \"0\"")
local safe_tx_gas=$(echo "$json_content" | jq -r ".safe_transaction_gas // \"0\"")
local base_gas=$(echo "$json_content" | jq -r ".base_gas // \"0\"")
local gas_price=$(echo "$json_content" | jq -r ".gas_price // \"0\"")
local gas_token=$(echo "$json_content" | jq -r ".gas_token // \"0x0000000000000000000000000000000000000000\"")
local refund_receiver=$(echo "$json_content" | jq -r ".refund_receiver // \"0x0000000000000000000000000000000000000000\"")

# Validate required fields
if [[ -z "$chain_id" ]]; then
echo -e "${BOLD}${RED}Missing required field 'chainid' in JSON file!${RESET}" >&2
exit 1
fi
if [[ -z "$safe_address" ]]; then
echo -e "${BOLD}${RED}Missing required field 'safe_address' in JSON file!${RESET}" >&2
exit 1
fi
if [[ -z "$safe_version" ]]; then
echo -e "${BOLD}${RED}Missing required field 'safe_version' in JSON file!${RESET}" >&2
exit 1
fi
if [[ -z "$json_nonce" ]]; then
echo -e "${BOLD}${RED}Missing required field 'nonce' in JSON file!${RESET}" >&2
exit 1
fi

# Validate address format
if [[ ! "$safe_address" =~ ^0x[a-fA-F0-9]{40}$ ]]; then
echo -e "${BOLD}${RED}Invalid Ethereum address format for 'safe_address': \"${safe_address}\"${RESET}" >&2
exit 1
fi

# Calculate and display the hashes
echo "==================================="
echo "= Selected Network Configurations ="
echo -e "===================================\n"
print_field "Chain ID" "$chain_id" true
echo "========================================"
echo "= Transaction Data and Computed Hashes ="
echo "========================================"

# Since we're using a JSON file, we don't have decoded data
local data_decoded="0x"

calculate_hashes "$chain_id" \
"$safe_address" \
"$to_address" \
"$value" \
"$data" \
"$operation" \
"$safe_tx_gas" \
"$base_gas" \
"$gas_price" \
"$gas_token" \
"$refund_receiver" \
"$json_nonce" \
"$data_decoded" \
"$safe_version"

exit 0
fi

# For API-based retrieval, validate required parameters
if [[ -z "$network" || -z "$address" ]]; then
echo -e "${BOLD}${RED}Missing required parameters for API-based retrieval!${RESET}" >&2
echo -e "${BOLD}${RED}Please provide --network and --address, or use --json for file-based input.${RESET}" >&2
exit 1
fi

# Validate if the required parameters have the correct format.
validate_network "$network"
validate_address "$address"
Expand Down
15 changes: 15 additions & 0 deletions sample_transaction.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"chainid": "1",
"safe_address": "0x1234567890123456789012345678901234567890",
"safe_version": "1.3.0",
"nonce": "42",
"to_address": "0xabcdef0123456789abcdef0123456789abcdef01",
"value": "1000000000000000000",
"data": "0x",
"operation": "0",
"safe_transaction_gas": "21000",
"base_gas": "0",
"gas_price": "20000000000",
"gas_token": "0x0000000000000000000000000000000000000000",
"refund_receiver": "0x0000000000000000000000000000000000000000"
}