Files
proxmox/scripts/deployment/send-cwusdc-ei-matrix-targeted.sh
defiQUG 4ebf2d7902
Some checks failed
Deploy to Phoenix / validate (push) Failing after 1s
Deploy to Phoenix / deploy (push) Has been skipped
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Has been skipped
Deploy to Phoenix / cloudflare (push) Has been skipped
chore(repo): sync operator workspace (config, scripts, docs, multi-chain)
Add optional Cosmos/Engine-X/act-runner templates, CWUSDC/EI-matrix tooling,
non-EVM route planner in multi-chain-execution (tests passing), token list and
extraction updates, and documentation (MetaMask matrix, GRU/CWUSDC packets).

Ignore institutional evidence tarballs/sha256 under reports/status.

Validated with: bash scripts/verify/run-all-validation.sh --skip-genesis

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:25:08 -07:00

298 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# Targeted mainnet cWUSDC transfers to a subset of EI matrix wallets by linear index.
# Intended for triage: feed indices from run-ei-matrix-full-readiness-audit.sh gap files.
#
# Usage:
# ./scripts/deployment/send-cwusdc-ei-matrix-targeted.sh [--dry-run] --send-raw R \
# [--indices-file PATH] [--amounts-tsv PATH] [--resume-next] [--quiet-dry-run] [--legacy]
#
# --indices-file Newline-separated linear indices (default: reports/status/ei-matrix-readiness-gaps-mainnet-indices.txt).
# Empty lines and # comments ignored.
# --send-raw R Amount (raw, 6 decimals) per wallet when not using --amounts-tsv.
# --amounts-tsv F Tab-separated: linearIndex <TAB> amountRaw (must cover every index in indices file).
# --resume-next Continue from reports/status/ei-matrix-cwusdc-targeted-last-idx.txt + 1
# (skips indices at or below last completed).
#
# Env: same as send-cwusdc-ei-matrix-wallets.sh (PRIVATE_KEY, ETHEREUM_MAINNET_RPC, CWUSDC_MAINNET, …)
# Lock: reports/status/ei-matrix-cwusdc-targeted-send.lock
# Progress: reports/status/ei-matrix-cwusdc-targeted-last-idx.txt
# Failures: reports/status/ei-matrix-cwusdc-targeted-failures.log
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$PROJECT_ROOT"
DRY_RUN=false
QUIET_DRY_RUN=false
CAST_LEGACY=false
RESUME_NEXT=false
INDICES_FILE="${EI_MATRIX_TARGETED_INDICES_FILE:-${PROJECT_ROOT}/reports/status/ei-matrix-readiness-gaps-mainnet-indices.txt}"
AMOUNTS_TSV=""
SEND_RAW=""
LAST_IDX_FILE="${PROJECT_ROOT}/reports/status/ei-matrix-cwusdc-targeted-last-idx.txt"
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--quiet-dry-run) QUIET_DRY_RUN=true; shift ;;
--legacy) CAST_LEGACY=true; shift ;;
--resume-next) RESUME_NEXT=true; shift ;;
--indices-file) INDICES_FILE="${2:?}"; shift 2 ;;
--amounts-tsv) AMOUNTS_TSV="${2:?}"; shift 2 ;;
--send-raw) SEND_RAW="${2:?}"; shift 2 ;;
*) echo "Unknown arg: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$SEND_RAW" && -z "$AMOUNTS_TSV" ]]; then
echo "Set --send-raw R or --amounts-tsv PATH." >&2
exit 1
fi
if [[ -n "$SEND_RAW" && -n "$AMOUNTS_TSV" ]]; then
echo "Use only one of --send-raw or --amounts-tsv." >&2
exit 1
fi
# shellcheck disable=SC1091
source "$PROJECT_ROOT/scripts/lib/load-project-env.sh"
CWUSDC="${CWUSDC_MAINNET:-0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a}"
PUBLIC_ETH_RPC="${ETHEREUM_MAINNET_PUBLIC_RPC:-https://ethereum-rpc.publicnode.com}"
RPC="${ETHEREUM_MAINNET_RPC:-${RPC_URL_1:-${ETH_MAINNET_RPC_URL:-$PUBLIC_ETH_RPC}}}"
BALANCE_RPC="${EI_MATRIX_BALANCE_RPC:-$RPC}"
GRID="$PROJECT_ROOT/config/pmm-soak-wallet-grid.json"
[[ -f "$INDICES_FILE" ]] || { echo "Missing indices file: $INDICES_FILE" >&2; exit 1; }
[[ -f "$GRID" ]] || { echo "Missing $GRID" >&2; exit 1; }
command -v cast &>/dev/null || { echo "cast required" >&2; exit 1; }
command -v jq &>/dev/null || { echo "jq required" >&2; exit 1; }
[[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY not set" >&2; exit 1; }
LOCK_FILE="${PROJECT_ROOT}/reports/status/ei-matrix-cwusdc-targeted-send.lock"
mkdir -p "${PROJECT_ROOT}/reports/status"
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
echo "Another send-cwusdc-ei-matrix-targeted.sh is already running (lock: $LOCK_FILE)." >&2
exit 1
fi
FROM_ADDR=$(cast wallet address --private-key "$PRIVATE_KEY")
CHAIN_ID=$(cast chain-id --rpc-url "$RPC" 2>/dev/null | tr -d '[:space:]' || true)
[[ -n "$CHAIN_ID" ]] || CHAIN_ID="1"
if [[ "$CHAIN_ID" != "1" ]]; then
echo "[WARN] chain-id=$CHAIN_ID (expected 1)." >&2
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"
}
token_decimals() {
cast call "$CWUSDC" 'decimals()(uint8)' --rpc-url "$BALANCE_RPC" 2>/dev/null | awk '{print $1}'
}
token_balance_raw() {
cast call "$CWUSDC" "balanceOf(address)(uint256)" "$FROM_ADDR" --rpc-url "$BALANCE_RPC" 2>/dev/null | awk '{print $1}'
}
ERR_LOG="${PROJECT_ROOT}/reports/status/ei-matrix-cwusdc-targeted-failures.log"
# Sorted unique indices from file (one index per line; # comments; whitespace tolerated)
IND_TMP=$(mktemp)
grep -v '^[[:space:]]*$' "$INDICES_FILE" | sed 's/#.*//' \
| awk '{ gsub(/[[:space:]]/, "", $0); if ($0 ~ /^[0-9]+$/) print $0 }' \
| sort -n -u >"$IND_TMP" || true
if [[ ! -s "$IND_TMP" ]]; then
echo "No numeric indices in $INDICES_FILE — nothing to do."
rm -f "$IND_TMP"
exit 0
fi
if $RESUME_NEXT; then
[[ -f "$LAST_IDX_FILE" ]] || { echo "Missing $LAST_IDX_FILE for --resume-next" >&2; rm -f "$IND_TMP"; exit 1; }
_last="$(tr -d '[:space:]' < "$LAST_IDX_FILE" || echo "")"
[[ -n "$_last" ]] || { echo "Empty $LAST_IDX_FILE" >&2; rm -f "$IND_TMP"; exit 1; }
_filtered=$(mktemp)
while read -r line; do
[[ "$line" =~ ^[0-9]+$ ]] || continue
if [[ "$line" -gt "$_last" ]]; then
echo "$line"
fi
done <"$IND_TMP" >"$_filtered"
mv "$_filtered" "$IND_TMP"
echo "Resume-next: last completed idx=$_last; remaining indices: $(wc -l <"$IND_TMP" | tr -d ' ')"
fi
N_IND=$(wc -l <"$IND_TMP" | tr -d ' ')
if [[ "$N_IND" -eq 0 ]]; then
echo "No indices to process after filters."
rm -f "$IND_TMP"
exit 0
fi
PAIR_TMP=$(mktemp)
cleanup() {
[[ -f "$IND_TMP" ]] && rm -f "$IND_TMP"
[[ -f "$PAIR_TMP" ]] && rm -f "$PAIR_TMP"
}
trap cleanup EXIT
if [[ -n "$AMOUNTS_TSV" ]]; then
[[ -f "$AMOUNTS_TSV" ]] || { echo "Missing amounts TSV: $AMOUNTS_TSV" >&2; exit 1; }
# Build map idx->amount in Python for validation + join
python3 - "$IND_TMP" "$AMOUNTS_TSV" "$PAIR_TMP" <<'PY'
import sys
from pathlib import Path
ind_path = Path(sys.argv[1])
amt_path = Path(sys.argv[2])
out_path = Path(sys.argv[3])
wanted = [int(x) for x in ind_path.read_text().split() if x.strip().isdigit()]
wanted_set = set(wanted)
amap: dict[int, int] = {}
for line in amt_path.read_text().splitlines():
line = line.split("#", 1)[0].strip()
if not line:
continue
parts = line.split("\t")
if len(parts) < 2:
parts = line.split()
if len(parts) < 2:
print(f"Bad amounts line: {line!r}", file=sys.stderr)
sys.exit(1)
idx = int(parts[0].strip())
raw = int(parts[1].strip())
amap[idx] = raw
missing = sorted(wanted_set - set(amap))
if missing:
print(f"Amounts TSV missing {len(missing)} indices (first 20): {missing[:20]}", file=sys.stderr)
sys.exit(1)
lines = []
for idx in sorted(wanted_set):
lines.append(f"{idx}\t{amap[idx]}")
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
PY
# PAIR_TMP now: idx \t raw per line sorted by idx
BUDGET_RAW=$(awk '{s+=$2} END {print s}' "$PAIR_TMP")
else
while read -r idx; do
echo -e "${idx}\t${SEND_RAW}"
done <"$IND_TMP" | sort -n -t $'\t' -k1,1 >"$PAIR_TMP"
BUDGET_RAW=$(python3 -c "print(int('$SEND_RAW') * int('$N_IND'))")
fi
ADDR_AMT_TMP=$(mktemp)
cleanup() {
[[ -f "$IND_TMP" ]] && rm -f "$IND_TMP"
[[ -f "$PAIR_TMP" ]] && rm -f "$PAIR_TMP"
[[ -f "$ADDR_AMT_TMP" ]] && rm -f "$ADDR_AMT_TMP"
}
trap cleanup EXIT
while IFS=$'\t' read -r idx raw_amt; do
addr=$(jq -r --argjson i "$idx" '.wallets[$i].address // empty' "$GRID")
[[ -n "$addr" && "$addr" != null ]] || { echo "No address for index $idx in grid" >&2; exit 1; }
echo -e "$addr\t$raw_amt\t$idx"
done <"$PAIR_TMP" >"$ADDR_AMT_TMP"
DECIMALS=$(token_decimals || echo "6")
GAS_EST="${EI_MATRIX_SEND_GAS_EST:-70000}"
HEADROOM_BPS="${EI_MATRIX_GAS_HEADROOM_BPS:-10500}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "EI matrix cWUSDC targeted transfer (mainnet)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "RPC: $RPC"
echo "Token: $CWUSDC"
echo "Signer: $FROM_ADDR"
echo "Indices: $N_IND (from $INDICES_FILE)"
echo "Budget: $BUDGET_RAW raw total"
echo "Dry-run: $DRY_RUN"
echo ""
ETH_WEI=$(cast balance "$FROM_ADDR" --rpc-url "$BALANCE_RPC" 2>/dev/null | awk '{print $1}' || echo "0")
TOKEN_BAL=$(token_balance_raw || echo "0")
echo "Signer ETH: $ETH_WEI wei"
echo "Signer cWUSDC (raw): $TOKEN_BAL"
if ! $DRY_RUN && [[ "${EI_MATRIX_SKIP_BALANCE_CHECK:-}" != "1" ]]; then
if ! python3 -c "import sys; sys.exit(0 if int('$TOKEN_BAL') >= int('$BUDGET_RAW') else 1)"; then
echo "Insufficient cWUSDC for budget $BUDGET_RAW raw." >&2
exit 1
fi
fi
if ! $DRY_RUN && [[ "${EI_MATRIX_SKIP_GAS_CHECK:-}" != "1" ]]; then
GAS_PRICE_WEI=$(cast gas-price --rpc-url "$RPC" 2>/dev/null | awk '{print $1}' | head -1)
[[ -n "$GAS_PRICE_WEI" ]] || GAS_PRICE_WEI=0
MIN_WEI=$(python3 -c "c=int('$N_IND'); g=int('$GAS_EST'); p=int('$GAS_PRICE_WEI'); b=int('$HEADROOM_BPS'); print(c*g*p*b//10000)")
if ! python3 -c "import sys; sys.exit(0 if int('$ETH_WEI') >= int('$MIN_WEI') else 1)"; then
echo "Insufficient ETH for gas (need ≈ $MIN_WEI wei)." >&2
exit 1
fi
fi
matrix_try_transfer() {
local addr="$1" raw_amt="$2" idx="$3"
local dec human out tx attempt=1
dec="${DECIMALS:-6}"
[[ "$raw_amt" != "0" ]] || { echo "[skip] idx=$idx zero"; return 0; }
human=$(python3 -c "d=int('$dec'); a=int('$raw_amt'); print(f'{a / (10**d):.6f}')" 2>/dev/null || echo "$raw_amt")
if $DRY_RUN; then
if ! $QUIET_DRY_RUN; then
echo "[dry-run] idx=$idx $addr raw=$raw_amt (~$human)"
fi
return 0
fi
local cast_extra=()
$CAST_LEGACY && cast_extra+=(--legacy)
while [[ "$attempt" -le 2 ]]; do
if out=$(cast send "$CWUSDC" "transfer(address,uint256)" "$addr" "$raw_amt" \
--rpc-url "$RPC" --private-key "$PRIVATE_KEY" \
--nonce "$NONCE" "${cast_extra[@]}" 2>&1); then
tx=$(echo "$out" | tail -n1)
echo "[ok] idx=$idx nonce=$NONCE $addr raw=$raw_amt tx=$tx"
sent=$((sent + 1))
NONCE=$((NONCE + 1))
echo "$idx" >"$LAST_IDX_FILE"
return 0
fi
if [[ "$attempt" -eq 1 ]] && echo "$out" | grep -qi 'nonce too low'; then
NONCE=$(pending_nonce) || true
attempt=$((attempt + 1))
continue
fi
echo "[fail] idx=$idx $out" | tee -a "$ERR_LOG" >&2
failed=$((failed + 1))
NONCE=$(pending_nonce) || true
return 0
done
}
sent=0
failed=0
NONCE=$(pending_nonce) || { echo "Could not read nonce" >&2; exit 1; }
echo "Starting nonce: $NONCE"
echo ""
while IFS=$'\t' read -r addr raw_amt idx; do
matrix_try_transfer "$addr" "$raw_amt" "$idx"
done <"$ADDR_AMT_TMP"
if $DRY_RUN; then
echo "Dry-run complete ($N_IND wallets)."
else
echo "Done. sent=$sent failed=$failed"
fi