#!/usr/bin/env bash # Send a random amount between 5 and 9 ETH (inclusive) to each address in # config/pmm-soak-wallet-grid.json (Elemental Imperium 33×33×6 matrix). # # Requires: cast (Foundry), jq, python3. Loads PRIVATE_KEY and RPC via load-project-env.sh. # # Usage: # ./scripts/deployment/send-eth-ei-matrix-wallets.sh [--dry-run] [--limit N] [--offset N] # # --dry-run Print planned sends only (no transactions). # --limit N Process at most N wallets (after offset). Default: all. # --offset N Skip the first N wallets (resume / partial run). # # Gas (Chain 138 / Besu): defaults avoid stuck pending txs from near-zero EIP-1559 caps. # Override if needed: # EI_MATRIX_GAS_PRICE=100000000000 # EI_MATRIX_PRIORITY_GAS_PRICE=20000000000 # # Nonces: each send uses an explicit --nonce from eth_getTransactionCount(..., "pending") # and increments locally so --async does not race duplicate nonces. # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" cd "$PROJECT_ROOT" DRY_RUN=false LIMIT="" OFFSET="0" while [[ $# -gt 0 ]]; do case "$1" in --dry-run) DRY_RUN=true; shift ;; --limit) LIMIT="${2:?}"; shift 2 ;; --offset) OFFSET="${2:?}"; shift 2 ;; *) echo "Unknown arg: $1" >&2; exit 1 ;; esac done # shellcheck disable=SC1091 source "$PROJECT_ROOT/scripts/lib/load-project-env.sh" LOCK_FILE="${PROJECT_ROOT}/reports/status/ei-matrix-eth-send.lock" mkdir -p "$(dirname "$LOCK_FILE")" exec 200>"$LOCK_FILE" if ! flock -n 200; then echo "Another send-eth-ei-matrix-wallets.sh is already running (lock: $LOCK_FILE)." >&2 exit 1 fi RPC="${RPC_URL_138:-http://192.168.11.211:8545}" GRID="$PROJECT_ROOT/config/pmm-soak-wallet-grid.json" DEPLOYER_CANONICAL="0x4A666F96fC8764181194447A7dFdb7d471b301C8" # Wei per gas — must exceed stuck-replacement threshold on busy pools (see script header). EI_MATRIX_GAS_PRICE="${EI_MATRIX_GAS_PRICE:-100000000000}" EI_MATRIX_PRIORITY_GAS_PRICE="${EI_MATRIX_PRIORITY_GAS_PRICE:-20000000000}" [[ -f "$GRID" ]] || { echo "Missing $GRID" >&2; exit 1; } command -v cast &>/dev/null || { echo "cast (Foundry) required" >&2; exit 1; } command -v jq &>/dev/null || { echo "jq required" >&2; exit 1; } [[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY not set (source smom-dbis-138/.env or root .env)" >&2; exit 1; } FROM_ADDR=$(cast wallet address --private-key "$PRIVATE_KEY") echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "EI matrix ETH distribution (random 5–9 ETH per wallet)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "RPC: $RPC" echo "Signer: $FROM_ADDR" echo "Grid: $GRID" echo "Dry-run: $DRY_RUN" echo "Offset: $OFFSET Limit: ${LIMIT:-all}" echo "Gas: maxFee=$EI_MATRIX_GAS_PRICE wei priorityFee=$EI_MATRIX_PRIORITY_GAS_PRICE wei" echo "" if [[ "${FROM_ADDR,,}" != "${DEPLOYER_CANONICAL,,}" ]]; then echo "[WARN] Signer is not canonical deployer $DEPLOYER_CANONICAL — continuing anyway." echo "" fi pending_nonce() { local resp hex resp=$(curl -sS -X POST "$RPC" -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"${FROM_ADDR}\",\"pending\"],\"id\":1}" 2>/dev/null) || return 1 hex=$(echo "$resp" | jq -r '.result // empty') [[ -n "$hex" ]] || return 1 cast to-dec "$hex" } random_wei() { python3 -c "import random; from decimal import Decimal; print(int(Decimal(str(random.uniform(5.0, 9.0))) * 10**18))" } ERR_LOG="${PROJECT_ROOT}/reports/status/ei-matrix-eth-send-failures.log" mkdir -p "$(dirname "$ERR_LOG")" if ! $DRY_RUN; then NONCE=$(pending_nonce) || { echo "Could not read pending nonce for $FROM_ADDR" >&2; exit 1; } echo "Starting nonce (pending): $NONCE" echo "" else NONCE=0 fi stream_addresses() { if [[ -n "${LIMIT:-}" ]]; then jq -r '.wallets[] | .address' "$GRID" | tail -n +$((OFFSET + 1)) | head -n "$LIMIT" else jq -r '.wallets[] | .address' "$GRID" | tail -n +$((OFFSET + 1)) fi } sent=0 failed=0 idx=$OFFSET while read -r addr; do wei=$(random_wei) eth_approx=$(python3 -c "print(f'{$wei / 1e18:.6f}')") if $DRY_RUN; then echo "[dry-run] idx=$idx $addr ${wei} wei (~${eth_approx} ETH)" else GP="$EI_MATRIX_GAS_PRICE" PP="$EI_MATRIX_PRIORITY_GAS_PRICE" attempt=1 while [[ "$attempt" -le 2 ]]; do if out=$(cast send "$addr" --value "$wei" --rpc-url "$RPC" --private-key "$PRIVATE_KEY" \ --nonce "$NONCE" \ --async \ --gas-price "$GP" \ --priority-gas-price "$PP" \ 2>&1); then tx=$(echo "$out" | tail -n1) echo "[ok] idx=$idx nonce=$NONCE $addr ${eth_approx} ETH tx=$tx" sent=$((sent + 1)) NONCE=$((NONCE + 1)) echo "$idx" > "${PROJECT_ROOT}/reports/status/ei-matrix-eth-send-last-idx.txt" break fi if echo "$out" | grep -q "Replacement transaction underpriced" && [[ "$attempt" -eq 1 ]]; then GP=$((GP * 2)) PP=$((PP * 2)) NONCE=$(pending_nonce) || true attempt=$((attempt + 1)) continue fi echo "[fail] idx=$idx nonce=$NONCE $addr $out" | tee -a "$ERR_LOG" >&2 failed=$((failed + 1)) NONCE=$(pending_nonce) || true break done fi idx=$((idx + 1)) done < <(stream_addresses) echo "" if $DRY_RUN; then echo "Dry-run complete (no transactions sent). Indices covered: $OFFSET..$((idx - 1))." else echo "Done. Sent: $sent Failed: $failed" fi