#!/usr/bin/env bash # Operator (deployer) funds grid wallets from an exported JSON manifest. # # Modes: # ERC-20 (default): TOKEN + AMOUNT_WEI_PER_WALLET — transfer() from operator to each grid address. # Native (--native): NATIVE_AMOUNT_WEI (or AMOUNT_WEI_PER_WALLET) — simple value transfer for gas on Chain 138. # # Default: dry-run. Live: --apply # # Usage (ERC-20): # PMM_SOAK_GRID_JSON=config/pmm-soak-wallet-grid.json \ # TOKEN=0x93E66202A11B1772E55407B32B44e5Cd8eda7f22 AMOUNT_WEI_PER_WALLET=10000000000 \ # bash scripts/deployment/pmm-soak-operator-fund-grid.sh --apply --from-linear 0 --to-linear 99 # # Usage (native / gas): # PMM_SOAK_GRID_JSON=config/pmm-soak-wallet-grid.json \ # NATIVE_AMOUNT_WEI=20000000000000000 \ # bash scripts/deployment/pmm-soak-operator-fund-grid.sh --native --apply --from-linear 0 --to-linear 199 # # Env: # PRIVATE_KEY / DEPLOYER_PRIVATE_KEY — operator # RPC_URL_138 # PMM_SOAK_GRID_JSON — default: $PROJECT_ROOT/config/pmm-soak-wallet-grid.json # TOKEN — ERC-20 (not used with --native) # AMOUNT_WEI_PER_WALLET — per-recipient raw amount (ERC-20); also accepted for --native if NATIVE_AMOUNT_WEI unset # NATIVE_AMOUNT_WEI — preferred for --native (wei per wallet) # CHAIN138_DEPLOY_GAS_PRICE_WEI — default 1000 # CAST_SEND_TIMEOUT_SEC — passed to cast as ETH_TIMEOUT (seconds to wait for tx confirmation; default 900) # CAST_RPC_TIMEOUT_SEC — ETH_RPC_TIMEOUT for cast (default 120) # PMM_SOAK_RPC_URL_OVERRIDE — after dotenv load, force RPC_URL_138 for all cast calls # PMM_SOAK_FUND_PROGRESS_EVERY — during --apply, log ETA every N successful txs (0=off except final; default 50) # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" # shellcheck source=/dev/null source "${PROJECT_ROOT}/scripts/lib/pmm-soak-dotenv-override.sh" pmm_soak_snapshot_pool_env_for_restore # shellcheck source=/dev/null [[ -f "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" ]] && source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" pmm_soak_restore_pool_env_after_dotenv if [[ -n "${PMM_SOAK_RPC_URL_OVERRIDE:-}" ]]; then export RPC_URL_138="$PMM_SOAK_RPC_URL_OVERRIDE" export CHAIN138_RPC_URL="$RPC_URL_138" export CHAIN138_RPC="$RPC_URL_138" export ETH_RPC_URL="$RPC_URL_138" fi require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "[fund-grid] missing: $1" >&2 exit 1 } } require_cmd cast require_cmd python3 APPLY=0 FROM_L=0 TO_L=6533 MODE="erc20" while [[ $# -gt 0 ]]; do case "$1" in --apply) APPLY=1 ;; --native) MODE="native" ;; --erc20) MODE="erc20" ;; --from-linear) FROM_L="$2" shift ;; --to-linear) TO_L="$2" shift ;; -h | --help) sed -n '2,31p' "$0" exit 0 ;; *) echo "[fund-grid] unknown arg: $1" >&2 exit 2 ;; esac shift done PK="${DEPLOYER_PRIVATE_KEY:-${PRIVATE_KEY:-}}" RPC="${RPC_URL_138:-http://192.168.11.211:8545}" GRID_JSON="${PMM_SOAK_GRID_JSON:-${PROJECT_ROOT}/config/pmm-soak-wallet-grid.json}" TOKEN_ADDR="${TOKEN:-}" GAS_WEI="${CHAIN138_DEPLOY_GAS_PRICE_WEI:-1000}" NATIVE_AMOUNT="${NATIVE_AMOUNT_WEI:-}" ERC20_AMOUNT="${AMOUNT_WEI_PER_WALLET:-}" if [[ "$MODE" == "native" ]]; then AMOUNT="${NATIVE_AMOUNT:-${ERC20_AMOUNT:-}}" if [[ -z "$AMOUNT" ]]; then echo "[fund-grid] --native requires NATIVE_AMOUNT_WEI or AMOUNT_WEI_PER_WALLET (wei per wallet)" >&2 exit 1 fi else AMOUNT="${ERC20_AMOUNT:-}" if [[ -z "$TOKEN_ADDR" || -z "$AMOUNT" ]]; then echo "[fund-grid] ERC-20 mode: set TOKEN and AMOUNT_WEI_PER_WALLET" >&2 exit 1 fi fi if [[ ! -f "$GRID_JSON" ]]; then echo "[fund-grid] missing grid JSON: $GRID_JSON" >&2 exit 1 fi if [[ "$APPLY" -eq 1 && -z "$PK" ]]; then echo "[fund-grid] --apply requires PRIVATE_KEY / DEPLOYER_PRIVATE_KEY" >&2 exit 1 fi OPERATOR="$(cast wallet address --private-key "$PK" 2>/dev/null || true)" if [[ "$APPLY" -eq 1 && -z "$OPERATOR" ]]; then echo "[fund-grid] could not derive operator address" >&2 exit 1 fi mapfile -t RECIPIENTS < <( python3 - "$GRID_JSON" "$FROM_L" "$TO_L" <<'PY' import json, sys path, lo, hi = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]) with open(path, encoding="utf-8") as f: doc = json.load(f) for w in doc["wallets"]: li = int(w["linearIndex"]) if lo <= li <= hi: print(w["address"]) PY ) total="${#RECIPIENTS[@]}" if [[ "$MODE" == "native" ]]; then echo "[fund-grid] mode=native recipients=[$FROM_L,$TO_L] count=$total amount_each_wei=$AMOUNT apply=$APPLY" else echo "[fund-grid] mode=erc20 recipients=[$FROM_L,$TO_L] count=$total token=$TOKEN_ADDR amount_each=$AMOUNT apply=$APPLY" fi if [[ "$total" -eq 0 ]]; then exit 0 fi export ETH_TIMEOUT="${CAST_SEND_TIMEOUT_SEC:-900}" export ETH_RPC_TIMEOUT="${CAST_RPC_TIMEOUT_SEC:-120}" total_needed="$(python3 -c "print(int('$AMOUNT') * $total)")" if [[ "$APPLY" -eq 0 ]]; then echo "[fund-grid] dry-run sample (first 3):" for i in 0 1 2; do [[ "$i" -lt "$total" ]] || break if [[ "$MODE" == "native" ]]; then echo " cast send ${RECIPIENTS[$i]} --value $AMOUNT --rpc-url \"\$RPC_URL_138\" --private-key \"\$PRIVATE_KEY\" --legacy --gas-price $GAS_WEI" else echo " cast send $TOKEN_ADDR 'transfer(address,uint256)(bool)' ${RECIPIENTS[$i]} $AMOUNT --rpc-url \"\$RPC_URL_138\" --private-key \"\$PRIVATE_KEY\" --legacy --gas-price $GAS_WEI" fi done if [[ "$MODE" == "native" ]]; then echo "[fund-grid] aggregate native to recipients: $total_needed wei (+ operator gas for each funding tx)" else echo "[fund-grid] aggregate ERC-20 to recipients: $total_needed raw units (+ operator gas for each funding tx)" fi echo "[fund-grid] rerun with --apply to broadcast $total transactions" exit 0 fi if [[ "$MODE" == "native" && -n "$OPERATOR" ]]; then op_bal="$(cast balance "$OPERATOR" --rpc-url "$RPC" 2>/dev/null | head -1 || echo "0")" echo "[fund-grid] operator native balance (approx): $op_bal wei - need $total_needed wei to recipients + gas for $total txs" fi _pmm_fund_grid_err() { echo "[fund-grid] ERR line=${BASH_LINENO[0]} n=${n:-0}/${total} mode=${MODE} recipients=[$FROM_L,$TO_L]" >&2 } trap '_pmm_fund_grid_err' ERR _chunk_wall_start="$SECONDS" _prog_every="${PMM_SOAK_FUND_PROGRESS_EVERY:-50}" [[ "$_prog_every" =~ ^[0-9]+$ ]] || _prog_every=50 n=0 for r in "${RECIPIENTS[@]}"; do n=$((n + 1)) echo "[fund-grid] $n/$total -> $r" if [[ "$MODE" == "native" ]]; then cast send "$r" --value "$AMOUNT" --rpc-url "$RPC" --private-key "$PK" --legacy --gas-price "$GAS_WEI" else cast send "$TOKEN_ADDR" 'transfer(address,uint256)(bool)' "$r" "$AMOUNT" \ --rpc-url "$RPC" --private-key "$PK" --legacy --gas-price "$GAS_WEI" fi if [[ "$_prog_every" -gt 0 ]] && (( n % _prog_every == 0 || n == total )); then _el=$((SECONDS - _chunk_wall_start)) python3 -c "n=$n;total=$total;el=$_el;avg=el/n if n else 0.0;rem=total-n;eta=int(rem*avg) if rem>0 else 0;print(f'[fund-grid] progress {n}/{total} chunk_elapsed_s={el} avg_s_per_tx={avg:.2f} est_remaining_this_chunk_s={eta}')" 2>/dev/null || true elif [[ "$_prog_every" -eq 0 ]] && [[ "$n" -eq "$total" ]]; then echo "[fund-grid] progress ${n}/${total} chunk_elapsed_s=$((SECONDS - _chunk_wall_start)) (final)" fi done trap - ERR echo "[fund-grid] done"