Files
proxmox/scripts/deployment/send-eth-ei-matrix-wallets.sh
defiQUG b8613905bd
Some checks failed
Deploy to Phoenix / validate (push) Failing after 15s
Deploy to Phoenix / deploy (push) Has been skipped
chore: sync workspace — configs, docs, scripts, CI, pnpm, submodules
- Submodule pins: dbis_core, cross-chain-pmm-lps, mcp-proxmox (local, push may be pending), metamask-integration, smom-dbis-138
- Atomic swap + cross-chain-pmm-lops-publish, deploy-portal workflow, phoenix deploy-targets, routing/aggregator matrices
- Docs, token-lists, forge proxy, phoenix API, runbooks, verify scripts

Made-with: Cursor
2026-04-21 22:01:33 -07:00

296 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Send native ETH to each address in config/pmm-soak-wallet-grid.json (Elemental
# Imperium 33×33×6 matrix).
#
# Default mode (spread): take BALANCE_PCT of the signer's native balance (minus
# optional reserve), split across the selected wallet slice with independent
# uniform random multipliers in [1 - SPREAD_PCT/100, 1 + SPREAD_PCT/100] around
# equal weight, then renormalize so the total sent equals that budget exactly.
#
# Legacy mode: random 59 ETH per wallet (--uniform-eth).
#
# 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).
# --balance-pct P Percent of (balance minus reserve) to distribute (default: 80).
# --spread-pct S Per-wallet multiplier range ±S%% around fair share (default: 15).
# --reserve-wei W Wei to leave untouchable before applying balance-pct (default: 0).
# --uniform-eth Legacy: random 59 ETH per wallet (ignores balance-pct/spread).
#
# 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
#
# Spread mode defaults (same as --balance-pct / --spread-pct / --reserve-wei):
# EI_MATRIX_BALANCE_PCT=80 EI_MATRIX_SPREAD_PCT=15 EI_MATRIX_RESERVE_WEI=0
# Balance query uses public RPC by default; override with EI_MATRIX_BALANCE_RPC. Tx RPC: RPC_URL_138 or public.
#
# Nonces: each send uses an explicit --nonce from eth_getTransactionCount(..., "pending")
# and increments locally so --async does not race duplicate nonces.
#
# RPC: defaults to Chain 138 public HTTP (reachable without LAN). Override with RPC_URL_138
# (e.g. core or Besu on 192.168.x.x). Public URL env: CHAIN138_PUBLIC_RPC_URL or RPC_URL_138_PUBLIC.
#
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"
BALANCE_PCT="${EI_MATRIX_BALANCE_PCT:-80}"
SPREAD_PCT="${EI_MATRIX_SPREAD_PCT:-15}"
RESERVE_WEI="${EI_MATRIX_RESERVE_WEI:-0}"
UNIFORM_ETH_LEGACY=false
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--limit) LIMIT="${2:?}"; shift 2 ;;
--offset) OFFSET="${2:?}"; shift 2 ;;
--balance-pct) BALANCE_PCT="${2:?}"; shift 2 ;;
--spread-pct) SPREAD_PCT="${2:?}"; shift 2 ;;
--reserve-wei) RESERVE_WEI="${2:?}"; shift 2 ;;
--uniform-eth) UNIFORM_ETH_LEGACY=true; shift ;;
*) echo "Unknown arg: $1" >&2; exit 1 ;;
esac
done
# shellcheck disable=SC1091
source "$PROJECT_ROOT/scripts/lib/load-project-env.sh"
CHAIN138_PUBLIC_RPC_DEFAULT="https://rpc-http-pub.d-bis.org"
RPC_PUBLIC="${CHAIN138_PUBLIC_RPC_URL:-${RPC_URL_138_PUBLIC:-$CHAIN138_PUBLIC_RPC_DEFAULT}}"
# Balance read for spread budget: public RPC first (aligns with deployer checks on public endpoints).
# After that, nonce + sends use RPC (same as RPC_URL_138 when set, else public).
BALANCE_RPC="${EI_MATRIX_BALANCE_RPC:-$RPC_PUBLIC}"
RPC="${RPC_URL_138:-$RPC_PUBLIC}"
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
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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if $UNIFORM_ETH_LEGACY; then
echo "EI matrix ETH distribution (legacy: random 59 ETH per wallet)"
else
echo "EI matrix ETH distribution (${BALANCE_PCT}% of balance, ±${SPREAD_PCT}% jitter, normalized)"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Balance RPC (public): $BALANCE_RPC"
echo "Tx / nonce RPC: $RPC"
echo "Signer: $FROM_ADDR"
echo "Grid: $GRID"
echo "Dry-run: $DRY_RUN"
echo "Offset: $OFFSET Limit: ${LIMIT:-all}"
if ! $UNIFORM_ETH_LEGACY; then
echo "Reserve: ${RESERVE_WEI} wei excluded before balance-pct"
fi
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_uniform_legacy() {
python3 -c "import random; from decimal import Decimal; print(int(Decimal(str(random.uniform(5.0, 9.0))) * 10**18))"
}
# Args: count budget_wei spread_pct — prints one wei amount per line; sum(lines) == budget_wei
generate_spread_amounts_wei() {
local count="$1" budget="$2" spread="$3"
python3 - "$count" "$budget" "$spread" <<'PY'
import random
import sys
n = int(sys.argv[1])
budget = int(sys.argv[2])
spread = float(sys.argv[3])
if n <= 0:
sys.exit("count must be positive")
if budget < 0:
sys.exit("budget must be non-negative")
if spread < 0 or spread > 100:
sys.exit("spread-pct must be in [0, 100]")
# Integer-only weights (float on ~10^27 wei rounds to 0 → huge remainder loop and hang)
base = 10000
low_w = max(1, (100 * base - int(spread * base)) // 100)
high_w = (100 * base + int(spread * base)) // 100
w = [random.randint(low_w, high_w) for _ in range(n)]
s = sum(w)
raw = [(budget * wi) // s for wi in w]
rem = budget - sum(raw)
for i in range(rem):
raw[i % n] += 1
for w in raw:
print(w)
PY
}
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
# Args: addr wei idx — uses global NONCE, sent, failed, DRY_RUN, RPC, PRIVATE_KEY, EI_MATRIX_* , ERR_LOG
matrix_try_send() {
local addr="$1" wei="$2" idx="$3"
local eth_approx out tx GP PP attempt
if [[ "$wei" == "0" ]]; then
echo "[skip] idx=$idx $addr zero wei"
return 0
fi
eth_approx=$(python3 -c "w=int('$wei'); print(f'{w / 1e18:.6f}')")
if $DRY_RUN; then
echo "[dry-run] idx=$idx $addr ${wei} wei (~${eth_approx} ETH)"
return 0
fi
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
}
stream_addresses() {
# Slice in jq only — jq|head breaks with SIGPIPE under pipefail when LIMIT is set.
if [[ -n "${LIMIT:-}" ]]; then
jq -r --argjson o "$OFFSET" --argjson l "$LIMIT" '.wallets[$o:$o+$l][] | .address' "$GRID"
else
jq -r --argjson o "$OFFSET" '.wallets[$o:][] | .address' "$GRID"
fi
}
compute_budget_wei() {
python3 - "$1" "$2" "$3" <<'PY'
import sys
bal = int(sys.argv[1])
reserve = int(sys.argv[2])
pct = float(sys.argv[3])
avail = max(0, bal - reserve)
print(int(avail * pct / 100.0))
PY
}
ADDR_TMP=""
AMOUNTS_TMP=""
cleanup_tmp() {
[[ -n "$ADDR_TMP" && -f "$ADDR_TMP" ]] && rm -f "$ADDR_TMP"
[[ -n "$AMOUNTS_TMP" && -f "$AMOUNTS_TMP" ]] && rm -f "$AMOUNTS_TMP"
}
trap cleanup_tmp EXIT
if $UNIFORM_ETH_LEGACY; then
:
else
ADDR_TMP=$(mktemp)
AMOUNTS_TMP=$(mktemp)
stream_addresses > "$ADDR_TMP"
WALLET_COUNT=$(wc -l < "$ADDR_TMP" | tr -d '[:space:]')
if [[ -z "$WALLET_COUNT" || "$WALLET_COUNT" -eq 0 ]]; then
echo "No wallets in range (offset=$OFFSET limit=${LIMIT:-all})." >&2
exit 1
fi
BAL_WEI=$(cast balance "$FROM_ADDR" --rpc-url "$BALANCE_RPC" 2>/dev/null | awk '{print $1}' || echo "0")
BUDGET_WEI=$(compute_budget_wei "$BAL_WEI" "$RESERVE_WEI" "$BALANCE_PCT")
eth_bal=$(python3 -c "print(f'{int(\"$BAL_WEI\") / 1e18:.8f}')" 2>/dev/null || echo "?")
eth_bud=$(python3 -c "print(f'{int(\"$BUDGET_WEI\") / 1e18:.8f}')" 2>/dev/null || echo "?")
echo "Balance: $BAL_WEI wei (~$eth_bal ETH)"
echo "Budget: $BUDGET_WEI wei (~$eth_bud ETH) (${BALANCE_PCT}% of max(0, balance ${RESERVE_WEI} reserve))"
echo "Wallets: $WALLET_COUNT spread: ±${SPREAD_PCT}% then normalized to budget"
echo ""
if [[ "$BUDGET_WEI" == "0" ]]; then
echo "Budget is zero (balance below reserve or pct=0). Nothing to send." >&2
exit 1
fi
generate_spread_amounts_wei "$WALLET_COUNT" "$BUDGET_WEI" "$SPREAD_PCT" > "$AMOUNTS_TMP"
fi
sent=0
failed=0
idx=$OFFSET
if $UNIFORM_ETH_LEGACY; then
while read -r addr; do
wei=$(random_wei_uniform_legacy)
matrix_try_send "$addr" "$wei" "$idx"
idx=$((idx + 1))
done < <(stream_addresses)
else
while IFS=$'\t' read -r addr wei; do
matrix_try_send "$addr" "$wei" "$idx"
idx=$((idx + 1))
done < <(paste -d $'\t' "$ADDR_TMP" "$AMOUNTS_TMP")
fi
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