#!/usr/bin/env bash # Cancel a queued nonce range for a Chain 138 EOA by sending 0-value self-transfers # at each nonce with a bumped gas price. Default mode is dry-run. # # Example: # PRIVATE_KEY=0xabc... bash scripts/deployment/cancel-chain138-eoa-nonce-range.sh \ # --rpc http://192.168.11.217:8545 \ # --from-nonce 1 # # PRIVATE_KEY=0xabc... bash scripts/deployment/cancel-chain138-eoa-nonce-range.sh \ # --rpc http://192.168.11.217:8545 \ # --from-nonce 1 \ # --to-nonce 8 \ # --apply set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" if [[ -f "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" ]]; then # shellcheck source=/dev/null source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" 2>/dev/null || true fi RPC_URL="${RPC_URL_138:-http://192.168.11.211:8545}" PRIVATE_KEY_INPUT="${PRIVATE_KEY:-${DEPLOYER_PRIVATE_KEY:-}}" FROM_NONCE="" TO_NONCE="" APPLY=0 GAS_LIMIT=21000 GAS_BUMP_MULTIPLIER=2 MIN_GAS_WEI=2000 usage() { cat <<'EOF' Usage: cancel-chain138-eoa-nonce-range.sh [options] Options: --private-key Private key for the EOA owner. --rpc Chain 138 RPC URL. Default: RPC_URL_138 or http://192.168.11.211:8545 --from-nonce First nonce to cancel. Required. --to-nonce Last nonce to cancel. Optional; defaults to highest pending owner nonce seen on the RPC. --apply Actually send cancel/self-transfer txs. --min-gas-wei Floor gas price in wei. Default: 2000 --gas-multiplier Multiply current eth_gasPrice by this factor. Default: 2 --help Show this help. Behavior: 1. Derives the owner address from the provided private key. 2. Reads latest nonce and owner pending txs from txpool_besuPendingTransactions. 3. Plans a cancel range from --from-nonce to --to-nonce. 4. In --apply mode, sends 0-value self-transfers at each nonce in that range. Notes: - Dry-run is the default and is recommended first. - Use this when the owner wants to invalidate old deployment attempts instead of completing them. - Requires: cast, curl, jq EOF } while [[ $# -gt 0 ]]; do case "$1" in --private-key) PRIVATE_KEY_INPUT="${2:-}" shift 2 ;; --rpc) RPC_URL="${2:-}" shift 2 ;; --from-nonce) FROM_NONCE="${2:-}" shift 2 ;; --to-nonce) TO_NONCE="${2:-}" shift 2 ;; --apply) APPLY=1 shift ;; --min-gas-wei) MIN_GAS_WEI="${2:-}" shift 2 ;; --gas-multiplier) GAS_BUMP_MULTIPLIER="${2:-}" shift 2 ;; --help|-h) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage exit 1 ;; esac done for cmd in cast curl jq; do command -v "$cmd" >/dev/null 2>&1 || { echo "Missing required command: $cmd" >&2 exit 1 } done if [[ -z "$PRIVATE_KEY_INPUT" ]]; then echo "Missing private key. Pass --private-key or set PRIVATE_KEY." >&2 exit 1 fi if [[ -z "$FROM_NONCE" ]]; then echo "Missing required --from-nonce." >&2 exit 1 fi case "$FROM_NONCE" in ''|*[!0-9]*) echo "--from-nonce must be a non-negative integer." >&2 exit 1 ;; esac if [[ -n "$TO_NONCE" ]]; then case "$TO_NONCE" in ''|*[!0-9]*) echo "--to-nonce must be a non-negative integer." >&2 exit 1 ;; esac fi OWNER_ADDRESS="$(cast wallet address --private-key "$PRIVATE_KEY_INPUT" 2>/dev/null || true)" if [[ -z "$OWNER_ADDRESS" ]]; then echo "Could not derive owner address from private key." >&2 exit 1 fi OWNER_ADDRESS_LC="$(printf '%s' "$OWNER_ADDRESS" | tr '[:upper:]' '[:lower:]')" rpc_call() { local method="$1" local params_json="$2" curl -fsS "$RPC_URL" \ -H 'content-type: application/json' \ --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"${method}\",\"params\":${params_json}}" } hex_to_dec() { cast --to-dec "$1" } latest_hex="$(rpc_call eth_getTransactionCount "[\"${OWNER_ADDRESS}\",\"latest\"]" | jq -r '.result // "0x0"')" pending_hex="$(rpc_call eth_getTransactionCount "[\"${OWNER_ADDRESS}\",\"pending\"]" | jq -r '.result // "0x0"')" gas_hex="$(rpc_call eth_gasPrice "[]" | jq -r '.result // "0x0"')" latest_nonce="$(hex_to_dec "$latest_hex")" pending_nonce="$(hex_to_dec "$pending_hex")" network_gas_wei="$(hex_to_dec "$gas_hex")" pending_json="$(rpc_call txpool_besuPendingTransactions "[]")" owner_pending_json="$( printf '%s' "$pending_json" | jq --arg owner "$OWNER_ADDRESS_LC" ' [ .result[]? | select(((.from // .sender // "") | ascii_downcase) == $owner) | { hash, nonce_hex: .nonce, to, gas, gasPrice, maxFeePerGas, maxPriorityFeePerGas, type } ] ' )" owner_pending_count="$(printf '%s' "$owner_pending_json" | jq 'length')" highest_pending_nonce="$latest_nonce" if [[ "$owner_pending_count" -gt 0 ]]; then highest_pending_nonce="$( printf '%s' "$owner_pending_json" \ | jq -r '.[].nonce_hex' \ | while read -r nonce_hex; do cast --to-dec "$nonce_hex"; done \ | sort -n \ | tail -n1 )" fi if [[ -z "$TO_NONCE" ]]; then TO_NONCE="$highest_pending_nonce" fi if (( TO_NONCE < FROM_NONCE )); then echo "--to-nonce must be greater than or equal to --from-nonce." >&2 exit 1 fi effective_gas_wei="$(( network_gas_wei * GAS_BUMP_MULTIPLIER ))" if (( effective_gas_wei < MIN_GAS_WEI )); then effective_gas_wei="$MIN_GAS_WEI" fi echo "=== Chain 138 EOA nonce-range cancel ===" echo "Owner address: $OWNER_ADDRESS" echo "RPC URL: $RPC_URL" echo "Latest nonce: $latest_nonce" echo "Pending nonce view: $pending_nonce" echo "Pending tx count: $owner_pending_count" echo "Highest pending nonce:${highest_pending_nonce}" echo "Cancel from nonce: $FROM_NONCE" echo "Cancel to nonce: $TO_NONCE" echo "Network gas price: $network_gas_wei wei" echo "Planned gas price: $effective_gas_wei wei" echo "" if [[ "$owner_pending_count" -gt 0 ]]; then echo "--- Pending txs for owner in txpool_besuPendingTransactions ---" printf '%s\n' "$owner_pending_json" | jq '.' echo "" else echo "No owner txs found in txpool_besuPendingTransactions." echo "" fi echo "--- Planned cancel range ---" for ((nonce = FROM_NONCE; nonce <= TO_NONCE; nonce++)); do echo "nonce $nonce -> self-transfer cancel" done echo "" if [[ "$APPLY" -ne 1 ]]; then echo "Dry-run only. Re-run with --apply to submit cancels for the full nonce range above." exit 0 fi echo "--- Applying cancels ---" for ((nonce = FROM_NONCE; nonce <= TO_NONCE; nonce++)); do echo "Sending cancel tx for nonce $nonce ..." cast send "$OWNER_ADDRESS" \ --private-key "$PRIVATE_KEY_INPUT" \ --rpc-url "$RPC_URL" \ --nonce "$nonce" \ --value 0 \ --gas-limit "$GAS_LIMIT" \ --gas-price "$effective_gas_wei" \ >/tmp/cancel-chain138-eoa-nonce-range.out 2>/tmp/cancel-chain138-eoa-nonce-range.err || { echo "Failed to send cancel for nonce $nonce" >&2 cat /tmp/cancel-chain138-eoa-nonce-range.err >&2 || true exit 1 } cat /tmp/cancel-chain138-eoa-nonce-range.out done echo "" echo "Submitted cancel transactions. Wait for inclusion, then rerun in dry-run mode to verify the queue is gone."