From dc8abb06e2bbd33e30803ca59d284ead8ea970c6 Mon Sep 17 00:00:00 2001 From: defiQUG Date: Sat, 11 Apr 2026 20:56:40 -0700 Subject: [PATCH] PMM soak grid: fund-grid progress/ERR trap, complete-grid START_LEG and timing, tranche RPC override, grid funding doc - pmm-soak-operator-fund-grid: PMM_SOAK_FUND_PROGRESS_EVERY, ERR trap, help text comments only - pmm-soak-complete-grid-funding-operator: PMM_SOAK_START_LEG resume, per-leg and total wall time - pmm-soak-operator-fund-full-grid-tranches: PMM_SOAK_RPC_URL_OVERRIDE, bash --noprofile --norc, manual hint - pmm-soak-mint-mirror-usdc-deployer-shortfall: ASCII-only operator messages - CHAIN138_GRID_6534_WALLET_FUNDING_PLAN: full-grid orchestrator, env vars, log markers Made-with: Cursor --- .../CHAIN138_GRID_6534_WALLET_FUNDING_PLAN.md | 158 ++++++++++ ...pmm-soak-complete-grid-funding-operator.sh | 291 ++++++++++++++++++ ...oak-mint-mirror-usdc-deployer-shortfall.sh | 90 ++++++ ...m-soak-operator-fund-full-grid-tranches.sh | 195 ++++++++++++ .../deployment/pmm-soak-operator-fund-grid.sh | 209 +++++++++++++ 5 files changed, 943 insertions(+) create mode 100644 docs/11-references/CHAIN138_GRID_6534_WALLET_FUNDING_PLAN.md create mode 100755 scripts/deployment/pmm-soak-complete-grid-funding-operator.sh create mode 100755 scripts/deployment/pmm-soak-mint-mirror-usdc-deployer-shortfall.sh create mode 100755 scripts/deployment/pmm-soak-operator-fund-full-grid-tranches.sh create mode 100755 scripts/deployment/pmm-soak-operator-fund-grid.sh diff --git a/docs/11-references/CHAIN138_GRID_6534_WALLET_FUNDING_PLAN.md b/docs/11-references/CHAIN138_GRID_6534_WALLET_FUNDING_PLAN.md new file mode 100644 index 00000000..c7671965 --- /dev/null +++ b/docs/11-references/CHAIN138_GRID_6534_WALLET_FUNDING_PLAN.md @@ -0,0 +1,158 @@ +# Chain 138 — Funding plan for 33×33×6 (6,534) PMM soak grid wallets + +**Purpose:** Separate the **operator (deployer)** role from the **6,534 grid EOAs**, summarize how to fund native gas and ERC-20 inventory for soak activity, and give **order-of-magnitude budgets** with formulas. +**Companion:** [DEPLOYER_WALLET_FUNDING_PLAN_PMM_POOLS.md](DEPLOYER_WALLET_FUNDING_PLAN_PMM_POOLS.md) (deployer liquidity / reserve split). +**Automation:** `scripts/deployment/pmm-soak-operator-fund-grid.sh` (`--native` or ERC-20), `scripts/deployment/pmm-soak-export-wallet-grid.py`, `scripts/deployment/chain138-pmm-soak-grid-bot.sh`, smoke `scripts/deployment/pmm-soak-grid-smoke-check.sh`, **one-shot** `scripts/deployment/pmm-soak-complete-operator-bootstrap.sh` (creates `~/.secure-secrets/chain138-pmm-soak-grid.mnemonic` when `PMM_SOAK_AUTO_INIT_GRID_MNEMONIC=1`, exports 6,534 addresses to `config/pmm-soak-wallet-grid.json`, optional `--apply-funds --to-linear N`), **full-grid resume** `scripts/deployment/pmm-soak-complete-grid-funding-operator.sh` (waits chain + deployer nonce, mirror-USDC mint shortfall, native + four ERC-20 legs in chunks; env `PMM_SOAK_START_LEG`, `PMM_SOAK_RESUME_NATIVE_FROM_LINEAR`, `PMM_SOAK_RPC_URL_OVERRIDE`), and **`scripts/deployment/pmm-soak-operator-fund-full-grid-tranches.sh`** (same legs as tranche loops; same RPC override pattern). **CI:** `.github/workflows/pmm-soak-grid-smoke.yml` runs the smoke script on `CI=true` with public read-only RPC when `RPC_URL_138` is unset. +**Pool narrowing:** `scripts/lib/pmm-soak-pools.sh` (`PMM_SOAK_POOL_PRESET`, `PMM_SOAK_POOLS_FILE`). +**Swap execution:** `scripts/lib/pmm-soak-chain138-tick.sh` defaults to **`CHAIN138_PMM_SOAK_SWAP_VIA=pool`** (EOA `transfer` into the DVM + `sellBase` / `sellQuote`). On this chain, `DODOPMMIntegration.swapExactIn` reverts inside the pool when the integration contract is `msg.sender`; set `CHAIN138_PMM_SOAK_SWAP_VIA=integration` only if you have confirmed that path on your RPC. +**Dotenv vs shell:** Root `.env` is loaded inside soak scripts; `scripts/lib/pmm-soak-dotenv-override.sh` restores **`PMM_SOAK_POOL_PRESET`**, **`PMM_SOAK_POOLS`**, **`PMM_SOAK_POOLS_FILE`**, **`CHAIN138_PMM_SOAK_SWAP_VIA`**, and **`RPC_URL_138`** (plus `CHAIN138_RPC_URL` / `CHAIN138_RPC` after restore) from the parent shell when set before launch — used by `chain138-pmm-soak-grid-bot.sh`, `chain138-pmm-random-soak-swaps.sh`, `pmm-soak-operator-fund-grid.sh`, `pmm-soak-complete-grid-funding-operator.sh`, `pmm-soak-operator-fund-full-grid-tranches.sh`, and `pmm-soak-complete-operator-bootstrap.sh`. Alternatively pass **`--pool-preset `** or **`--swap-via pool|integration`** on the bot CLI (CLI wins over dotenv for preset and clears `PMM_SOAK_POOLS*` so the preset applies). + +--- + +## 1. Two different key systems (do not confuse them) + +| Role | Keys | Address source | Typical use | +|------|------|----------------|-------------| +| **Operator / deployer** | `PRIVATE_KEY` / `DEPLOYER_PRIVATE_KEY` in `.env` | Single EOA | Funds grid wallets (native + ERC-20), operations, liquidity (see deployer funding plan doc) | +| **Grid (6,534 wallets)** | **`PMM_SOAK_GRID_MNEMONIC`** (BIP39), separate from deployer | HD path `m/44'/60'/0'/0/{linearIndex}` with `linearIndex = lpbca×198 + branch×6 + class` | Sign PMM swaps (and optional inter-wallet transfers) in the soak bot | + +- The **deployer** is **not** derived from the grid mnemonic unless you intentionally use the same seed (not recommended). +- Export **public** addresses only: `PMM_SOAK_GRID_MNEMONIC='…' python3 scripts/deployment/pmm-soak-export-wallet-grid.py --out config/pmm-soak-wallet-grid.json` — keep that JSON **local** (gitignored pattern in `.gitignore`). + +--- + +## 2. Deployer (operator) — review and snapshot + +**Canonical address:** `0x4A666F96fC8764181194447A7dFdb7d471b301C8` +**Explorer:** https://explorer.d-bis.org/address/0x4A666F96fC8764181194447A7dFdb7d471b301C8 + +**Point-in-time read (LAN Core RPC `http://192.168.11.211:8545`, 2026-04-10):** + +| Asset | Raw (integer) | Human (approx.) | +|-------|-----------------|-----------------| +| Native | `989399835388702020921911788` wei | ~989,400 ETH nominal units (18 decimals) — private chain genesis style | +| cUSDT `0x93E66202A11B1772E55407B32B44e5Cd8eda7f22` | `688280049090000` | ~688.28M units (6 decimals) | +| cUSDC `0xf22258f57794CC8E06237084b353Ab30fFfa640b` | `689514099298585` | ~689.51M units (6 decimals) | + +Re-check before funding: + +```bash +source scripts/lib/load-project-env.sh +D=0x4A666F96fC8764181194447A7dFdb7d471b301C8 +cast balance "$D" --rpc-url "${RPC_URL_138:-http://192.168.11.211:8545}" +cast call 0x93E66202A11B1772E55407B32B44e5Cd8eda7f22 'balanceOf(address)(uint256)' "$D" --rpc-url "$RPC_URL_138" +cast call 0xf22258f57794CC8E06237084b353Ab30fFfa640b 'balanceOf(address)(uint256)' "$D" --rpc-url "$RPC_URL_138" +``` + +The deployer holds **large** stable inventory relative to typical per-wallet soak seeds; still treat **grid funding** as a **budgeted** draw (tranches), not an implicit unlimited tap. + +--- + +## 3. Grid wallets (6,534) — what each needs + +### 3.1 Native (gas) + +Grid wallets must pay **legacy** gas for: + +- `approve` (when allowance < swap amount) on the sold token +- `DODOPMMIntegration.swapExactIn` +- Optional `ERC20.transfer` if you enable inter-wallet flow (`PMM_SOAK_TRANSFER_*`) + +On Chain 138, `CHAIN138_DEPLOY_GAS_PRICE_WEI` is often **1000 wei** per gas unit (see deployment scripts). Cost per swap is **small in wei terms**, but **6,534 wallets × many swaps** adds up if native per wallet is set too high. + +**Suggested starting native per wallet (order of magnitude):** + +| Scenario | `NATIVE_AMOUNT_WEI` (per wallet) | Notes | +|----------|----------------------------------|--------| +| Smoke (few ticks) | `5e15` – `2e16` wei (0.005 – 0.02 ETH units) | Enough for multiple approve+swap paths at low gas price | +| Sustained soak | `2e16` – `5e16` wei | Raise if you see OOG or heavy revert churn | +| **Do not** blindly max-fund all 6,534 without a tranche plan | — | Operator native spend + recipient inventory must stay bounded | + +**Operator overhead (native):** Each funding tx is a native transfer from the deployer; budget **deployer native** for: + +- **Sum to recipients:** `6,534 × NATIVE_AMOUNT_WEI` +- **Funding transaction gas:** roughly `21_000 × gasPrice × 6,534` (plus buffer for retries). With `gasPrice = 1000` wei, this term is on the order of **1.4×10^11 wei** total — negligible next to recipient amounts unless `NATIVE_AMOUNT_WEI` is tiny. + +Use **slices** to limit blast radius: + +```bash +PMM_SOAK_GRID_JSON=config/pmm-soak-wallet-grid.json NATIVE_AMOUNT_WEI=20000000000000000 \ + bash scripts/deployment/pmm-soak-operator-fund-grid.sh --native --from-linear 0 --to-linear 199 +# dry-run first; then --apply +``` + +### 3.2 ERC-20 (swap inventory) + +Soak sizes default to **random USD face** between `PMM_SOAK_USD_MIN` / `PMM_SOAK_USD_MAX` (default 1,000–100,000 USD) expressed as **6-decimal** units for stables (`amount ≈ USD × 10^6`). A wallet must hold **at least** the token it sells each tick (often **cUSDT** or **cUSDC** on stable presets), plus gas. + +**Pool preset drives which tokens matter** (`PMM_SOAK_POOL_PRESET`): + +| Preset | Pools (count) | Primary inventory to fund | +|--------|----------------|---------------------------| +| `cusdt-cusdc` | 1 | cUSDT and/or cUSDC (both sides of pool) | +| `stable` / `stable-mirrors` | 3 | cUSDT, cUSDC, mirror **USDT** / **USDC** as needed for sell side | +| `xau-*` | XAU pairs | cUSDT/cUSDC/cEURT + cXAUC/cXAUT per pool (see [ADDRESS_MATRIX_AND_STATUS](ADDRESS_MATRIX_AND_STATUS.md)) | + +**Conservative per-wallet ERC-20 seed (example):** +`AMOUNT_WEI_PER_WALLET=500000000` (**500 USDT** face at 6 decimals: `500 × 10^6`) for **stable-only** testing — increase only after observing quote success rates. + +**Total ERC-20 to grid (single token, uniform):** +`6,534 × AMOUNT_WEI_PER_WALLET` raw — must be **≤ deployer balance** of that token (minus reserves for other ops). Example: 500 USDT per wallet → **~3.27M USDT** face total if all 6,534 wallets are funded equally. + +--- + +## 4. Automated smoke check (no secrets on disk) + +From repo root (uses a **public test mnemonic** and **dry-run only**; removes temp JSON after): + +```bash +bash scripts/deployment/pmm-soak-grid-smoke-check.sh +``` + +Override: `PMM_SOAK_GRID_SMOKE_MNEMONIC='twelve words…'` (still dry-run). For a **partial export** of real addresses: +`PMM_SOAK_GRID_MNEMONIC='…' python3 scripts/deployment/pmm-soak-export-wallet-grid.py --out config/pmm-soak-wallet-grid.json --count 100` + +--- + +## 5. Recommended phased funding (6,534 wallets) + +1. **Export addresses** to `config/pmm-soak-wallet-grid.json` (local only). +2. **Narrow pools:** `PMM_SOAK_POOL_PRESET=stable` or `cusdt-cusdc` until healthy quotes. +3. **Phase A — native:** Fund **linear ranges** in batches (e.g. 0–199, 200–399, …); dry-run then `--apply`. +4. **Phase B — ERC-20:** Same batching; start with **one** token (e.g. cUSDT) aligned with your preset’s sell-side testing. +5. **Phase C — bot:** `chain138-pmm-soak-grid-bot.sh --dry-run`, then `--apply` with `PMM_SOAK_LINEAR_MIN` / `MAX` restricted before full 0–6533. +6. **Monitor:** deployer native + cUSDT/cUSDC balances; grid wallet balances via explorer or scripted `cast balance` / `balanceOf` samples. +7. **Full-grid resume (optional):** `bash scripts/deployment/pmm-soak-complete-grid-funding-operator.sh --dry-run` then `--apply`. For long runs from a host where LAN Core RPC may flap, set `PMM_SOAK_RPC_URL_OVERRIDE=https://rpc-http-pub.d-bis.org` (or another stable endpoint). To skip native when every wallet is already funded, set `PMM_SOAK_RESUME_NATIVE_FROM_LINEAR=6534` (above max linear `6533`). To resume after a partial leg, set `PMM_SOAK_START_LEG` to one of `native`, `mint`, `cusdt`, `cusdc`, `mirr_usdt`, `mirr_usdc` — earlier legs are skipped; you must already satisfy deployer balances and on-chain state for those legs. Chunk progress and ETA during apply: `PMM_SOAK_FUND_PROGRESS_EVERY` on `pmm-soak-operator-fund-grid.sh` (default `50`, use `0` for quiet except the last line per chunk). Example background log: `nohup env PMM_SOAK_RPC_URL_OVERRIDE=… bash scripts/deployment/pmm-soak-complete-grid-funding-operator.sh --apply >> /tmp/pmm-soak-complete-grid-lan.log 2>&1 &` — completion lines: `[complete] total apply wall_s=… (after waits)` then `[complete] all legs finished (apply=1)`. + +--- + +## 6. Budget table (illustrative totals) + +Let `N = 6,534`, `g = NATIVE_AMOUNT_WEI`, `a = AMOUNT_WEI_PER_WALLET` (one token). + +| Item | Formula | +|------|---------| +| Native to grid | `N × g` | +| One-token ERC-20 to grid | `N × a` | +| Deployer funding txs gas (native, order of mag.) | `≈ N × 21_000 × gasPrice` | + +Example: `g = 2e16` wei (0.02 ETH per wallet) → native to grid ≈ `6,534 × 0.02 ≈ 131` ETH nominal (plus deployer gas for funding txs). +Example: `a = 500_000_000` raw (**500 USDT** face per wallet) → **~3.27×10^12** raw units total for that token if every wallet gets the same `a`. + +--- + +## 7. References + +| Doc / script | Use | +|--------------|-----| +| [DEPLOYER_WALLET_FUNDING_PLAN_PMM_POOLS.md](DEPLOYER_WALLET_FUNDING_PLAN_PMM_POOLS.md) | Deployer liquidity / 50% rule for pools | +| [ADDRESS_MATRIX_AND_STATUS.md](ADDRESS_MATRIX_AND_STATUS.md) | PMM pool + token addresses | +| `scripts/deployment/pmm-soak-operator-fund-grid.sh` | Batch fund native or ERC-20 | +| `scripts/deployment/pmm-soak-complete-grid-funding-operator.sh` | One-shot resume: wait chain + nonce, mint mirror-USDC shortfall, native + four ERC-20 legs (`PMM_SOAK_START_LEG`, `PMM_SOAK_RESUME_NATIVE_FROM_LINEAR`, `PMM_SOAK_RPC_URL_OVERRIDE`) | +| `scripts/deployment/pmm-soak-operator-fund-full-grid-tranches.sh` | Same funding legs as explicit tranche loops (RPC override + `bash --noprofile --norc` into fund-grid) | +| `scripts/lib/pmm-soak-pools.sh` | Narrow pool sets | +| `scripts/deployment/chain138-pmm-soak-grid-bot.sh` | Grid soak runner | + +--- + +**Disclaimer:** Soak bots spend **real gas and tokens** on chain. Cap tranches, use dry-run, and align `PMM_SOAK_USD_*` with funded balances. diff --git a/scripts/deployment/pmm-soak-complete-grid-funding-operator.sh b/scripts/deployment/pmm-soak-complete-grid-funding-operator.sh new file mode 100755 index 00000000..b44a8037 --- /dev/null +++ b/scripts/deployment/pmm-soak-complete-grid-funding-operator.sh @@ -0,0 +1,291 @@ +#!/usr/bin/env bash +# Finish PMM soak grid funding after stalls or partial runs: wait for blocks + mempool, optional +# owner-mint of mirror USDC shortfall, then fund native (resume) + cUSDT + cUSDC + mirror USDT + mirror USDC. +# +# Requires: cast, jq, python3, PRIVATE_KEY, RPC_URL_138 (via load-project-env) +# +# Usage: +# bash scripts/deployment/pmm-soak-complete-grid-funding-operator.sh --dry-run +# bash scripts/deployment/pmm-soak-complete-grid-funding-operator.sh --apply +# +# Env: +# PMM_SOAK_GRID_JSON, PMM_SOAK_FUND_CHUNK (default 250), NATIVE_AMOUNT_WEI, seed RAW vars (same as tranche script) +# PMM_SOAK_RESUME_NATIVE_FROM_LINEAR — if set, first linear index to receive native (skip lower). If unset, auto-detect. +# PMM_SOAK_WAIT_BLOCK_SEC — max seconds to wait for eth_blockNumber to increase (default 86400) +# PMM_SOAK_WAIT_NONCE_CLEAR_SEC — max wait for deployer latest==pending nonce (default 7200) +# CAST_SEND_TIMEOUT_SEC / CAST_RPC_TIMEOUT_SEC — forwarded via fund-grid +# PMM_SOAK_RPC_URL_OVERRIDE — after dotenv load, force RPC_URL_138 (e.g. https://rpc-http-pub.d-bis.org for long runs) +# PMM_SOAK_START_LEG — optional resume: run from this leg onward only (native | mint | cusdt | cusdc | mirr_usdt | mirr_usdc). +# Skipped legs are not re-run; ensure deployer balances and mempool are already suitable (e.g. start at cusdt only after native + mint done). +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +cd "$PROJECT_ROOT" + +# 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 + +GRID_JSON="${PMM_SOAK_GRID_JSON:-${PROJECT_ROOT}/config/pmm-soak-wallet-grid.json}" +CHUNK="${PMM_SOAK_FUND_CHUNK:-250}" +NATIVE_WEI="${NATIVE_AMOUNT_WEI:-20000000000000000}" +SEED_CUSDT="${PMM_SOAK_SEED_CUSDT_RAW:-100000000}" +SEED_CUSDC="${PMM_SOAK_SEED_CUSDC_RAW:-100000000}" +SEED_USDT="${PMM_SOAK_SEED_MIRROR_USDT_RAW:-100000000}" +SEED_USDC="${PMM_SOAK_SEED_MIRROR_USDC_RAW:-100000000}" + +CUSDT=0x93E66202A11B1772E55407B32B44e5Cd8eda7f22 +CUSDC=0xf22258f57794CC8E06237084b353Ab30fFfa640b +USDT_M=0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1 +USDC_M=0x71D6687F38b93CCad569Fa6352c876eea967201b + +RPC="${RPC_URL_138:-http://192.168.11.211:8545}" +D=0x4A666F96fC8764181194447A7dFdb7d471b301C8 +MAX_LI=6533 +WAIT_BLOCK="${PMM_SOAK_WAIT_BLOCK_SEC:-86400}" +WAIT_NONCE="${PMM_SOAK_WAIT_NONCE_CLEAR_SEC:-7200}" + +APPLY=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --apply) APPLY=1 ;; + --dry-run) APPLY=0 ;; + -h | --help) sed -n '2,21p' "$0"; exit 0 ;; + *) echo "unknown: $1" >&2; exit 2 ;; + esac + shift +done + +if [[ ! -f "$GRID_JSON" ]]; then + echo "[complete] missing $GRID_JSON" >&2 + exit 1 +fi + +if [[ -n "${PMM_SOAK_START_LEG:-}" ]]; then + case "${PMM_SOAK_START_LEG}" in + native | mint | cusdt | cusdc | mirr_usdt | mirr_usdc) ;; + *) + echo "[complete] FATAL: PMM_SOAK_START_LEG must be one of: native mint cusdt cusdc mirr_usdt mirr_usdc (got: ${PMM_SOAK_START_LEG})" >&2 + exit 2 + ;; + esac +fi + +_leg_idx() { + case "$1" in + native) echo 0 ;; + mint) echo 1 ;; + cusdt) echo 2 ;; + cusdc) echo 3 ;; + mirr_usdt) echo 4 ;; + mirr_usdc) echo 5 ;; + *) echo -1 ;; + esac +} + +_leg_should_run() { + local leg="$1" + local st="${PMM_SOAK_START_LEG:-}" + [[ -z "$st" ]] && return 0 + local a b + a="$(_leg_idx "$leg")" + b="$(_leg_idx "$st")" + (( a >= b )) +} + +wait_chain_block_progress() { + echo "[complete] waiting for block number to advance (max ${WAIT_BLOCK}s) ..." + local first stall=0 + first="$(cast block-number --rpc-url "$RPC")" + while true; do + local now + now="$(cast block-number --rpc-url "$RPC")" + if (( now > first )); then + echo "[complete] chain progressed blocks $first -> $now" + return 0 + fi + if (( stall >= WAIT_BLOCK )); then + echo "[complete] FATAL: no new blocks in ${WAIT_BLOCK}s (stuck at $first). Fix Chain 138 consensus / RPC, then re-run." >&2 + exit 1 + fi + sleep 5 + stall=$((stall + 5)) + done +} + +wait_deployer_nonce_mempool_clear() { + local start="$SECONDS" + while true; do + local nl np + nl="$(cast nonce "$D" --rpc-url "$RPC" -B latest)" + np="$(cast nonce "$D" --rpc-url "$RPC" -B pending)" + if [[ "$nl" == "$np" ]]; then + echo "[complete] deployer nonce clear (latest=pending=$nl)" + return 0 + fi + if (( SECONDS - start > WAIT_NONCE )); then + echo "[complete] FATAL: deployer mempool stuck (latest=$nl pending=$np) after ${WAIT_NONCE}s" >&2 + exit 1 + fi + echo "[complete] waiting deployer mempool latest=$nl pending=$np ..." + sleep 3 + done +} + +detect_native_resume_linear() { + local target="$NATIVE_WEI" + # 95% of target: allow tiny fee dust below nominal + local thresh + thresh="$(python3 -c "print(int(int('$target') * 95 // 100))")" + local li + for ((li = 0; li <= MAX_LI; li++)); do + local addr bal + addr="$(jq -r --argjson li "$li" '.wallets[] | select(.linearIndex==$li) | .address' "$GRID_JSON")" + bal="$(cast balance "$addr" --rpc-url "$RPC" | head -1 || echo 0)" + if (( bal < thresh )); then + echo "$li" + return 0 + fi + done + echo "$((MAX_LI + 1))" +} + +run_native_chunk() { + local from="$1" to="$2" + echo "[complete] === native $from..$to apply=$APPLY ===" + if [[ "$APPLY" -eq 1 ]]; then + PMM_SOAK_GRID_JSON="$GRID_JSON" NATIVE_AMOUNT_WEI="$NATIVE_WEI" \ + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-operator-fund-grid.sh" --apply --native --from-linear "$from" --to-linear "$to" + else + PMM_SOAK_GRID_JSON="$GRID_JSON" NATIVE_AMOUNT_WEI="$NATIVE_WEI" \ + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-operator-fund-grid.sh" --native --from-linear "$from" --to-linear "$to" + fi +} + +run_erc20_chunk() { + local from="$1" to="$2" tok="$3" amt="$4" label="$5" + echo "[complete] === $label $tok $from..$to apply=$APPLY ===" + if [[ "$APPLY" -eq 1 ]]; then + PMM_SOAK_GRID_JSON="$GRID_JSON" TOKEN="$tok" AMOUNT_WEI_PER_WALLET="$amt" \ + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-operator-fund-grid.sh" --apply --from-linear "$from" --to-linear "$to" + else + PMM_SOAK_GRID_JSON="$GRID_JSON" TOKEN="$tok" AMOUNT_WEI_PER_WALLET="$amt" \ + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-operator-fund-grid.sh" --from-linear "$from" --to-linear "$to" + fi +} + +if [[ "$APPLY" -eq 0 ]]; then + echo "[complete] dry-run: on --apply would: wait new block -> clear deployer mempool -> native from first under-funded linear (or PMM_SOAK_RESUME_NATIVE_FROM_LINEAR) -> mint mirror USDC shortfall -> cUSDT/cUSDC/mirror USDT/mirror USDC for 0..$MAX_LI in chunks of $CHUNK" + [[ -n "${PMM_SOAK_START_LEG:-}" ]] && echo "[complete] dry-run: PMM_SOAK_START_LEG=${PMM_SOAK_START_LEG} would skip earlier legs on --apply" + RESUME_LI="${PMM_SOAK_RESUME_NATIVE_FROM_LINEAR:-0}" + echo "[complete] dry-run sample native chunk from linear $RESUME_LI" + to=$((RESUME_LI + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_native_chunk "$RESUME_LI" "$to" + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-mint-mirror-usdc-deployer-shortfall.sh" --dry-run + _dto=$((CHUNK - 1)) + [[ "$_dto" -gt "$MAX_LI" ]] && _dto="$MAX_LI" + run_erc20_chunk 0 "$_dto" "$CUSDT" "$SEED_CUSDT" "cUSDT (sample chunk)" + echo "[complete] dry-run done (apply=0)" + exit 0 +fi + +wait_chain_block_progress +wait_deployer_nonce_mempool_clear + +_complete_apply_start="$SECONDS" +_phase_wall_start="$SECONDS" +if _leg_should_run native; then + RESUME_LI="${PMM_SOAK_RESUME_NATIVE_FROM_LINEAR:-}" + if [[ -z "$RESUME_LI" ]]; then + echo "[complete] auto-detecting first linear missing native (threshold 95% of $NATIVE_WEI wei) ..." + RESUME_LI="$(detect_native_resume_linear)" + echo "[complete] native resume linear index: $RESUME_LI" + else + echo "[complete] native resume linear from env: $RESUME_LI" + fi + + if (( RESUME_LI > MAX_LI )); then + echo "[complete] all wallets already at/above native threshold - skipping native legs" + else + for ((from = RESUME_LI; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_native_chunk "$from" "$to" + done + fi + echo "[complete] phase native wall_s=$((SECONDS - _phase_wall_start))" +else + echo "[complete] skipping native leg (PMM_SOAK_START_LEG=${PMM_SOAK_START_LEG:-unset})" +fi + +_phase_wall_start="$SECONDS" +if _leg_should_run mint; then + echo "[complete] owner-mint mirror USDC shortfall (if any) ..." + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-mint-mirror-usdc-deployer-shortfall.sh" --apply + echo "[complete] phase mint wall_s=$((SECONDS - _phase_wall_start))" +else + echo "[complete] skipping mint leg (PMM_SOAK_START_LEG=${PMM_SOAK_START_LEG:-unset})" +fi + +_phase_wall_start="$SECONDS" +if _leg_should_run cusdt; then + for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_erc20_chunk "$from" "$to" "$CUSDT" "$SEED_CUSDT" "cUSDT" + done + echo "[complete] phase cusdt wall_s=$((SECONDS - _phase_wall_start))" +else + echo "[complete] skipping cUSDT leg (PMM_SOAK_START_LEG=${PMM_SOAK_START_LEG:-unset})" +fi + +_phase_wall_start="$SECONDS" +if _leg_should_run cusdc; then + for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_erc20_chunk "$from" "$to" "$CUSDC" "$SEED_CUSDC" "cUSDC" + done + echo "[complete] phase cusdc wall_s=$((SECONDS - _phase_wall_start))" +else + echo "[complete] skipping cUSDC leg (PMM_SOAK_START_LEG=${PMM_SOAK_START_LEG:-unset})" +fi + +_phase_wall_start="$SECONDS" +if _leg_should_run mirr_usdt; then + for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_erc20_chunk "$from" "$to" "$USDT_M" "$SEED_USDT" "mirror USDT" + done + echo "[complete] phase mirr_usdt wall_s=$((SECONDS - _phase_wall_start))" +else + echo "[complete] skipping mirror USDT leg (PMM_SOAK_START_LEG=${PMM_SOAK_START_LEG:-unset})" +fi + +_phase_wall_start="$SECONDS" +if _leg_should_run mirr_usdc; then + for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_erc20_chunk "$from" "$to" "$USDC_M" "$SEED_USDC" "mirror USDC" + done + echo "[complete] phase mirr_usdc wall_s=$((SECONDS - _phase_wall_start))" +else + echo "[complete] skipping mirror USDC leg (PMM_SOAK_START_LEG=${PMM_SOAK_START_LEG:-unset})" +fi + +echo "[complete] total apply wall_s=$((SECONDS - _complete_apply_start)) (after waits)" +echo "[complete] all legs finished (apply=$APPLY)" diff --git a/scripts/deployment/pmm-soak-mint-mirror-usdc-deployer-shortfall.sh b/scripts/deployment/pmm-soak-mint-mirror-usdc-deployer-shortfall.sh new file mode 100755 index 00000000..a8edf583 --- /dev/null +++ b/scripts/deployment/pmm-soak-mint-mirror-usdc-deployer-shortfall.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# Mint Official USDC mirror (Chain 138) to deployer so PMM soak grid funding can transfer out. +# OfficialStableMirrorToken: onlyOwner mint(address,uint256). Deployer is owner. +# +# Waits until deployer latest nonce == pending (no in-flight txs from this EOA), then mints shortfall: +# (PMM_SOAK_SEED_MIRROR_USDC_RAW × (6534 wallets)) − deployer balance +# +# Usage: +# bash scripts/deployment/pmm-soak-mint-mirror-usdc-deployer-shortfall.sh --dry-run +# bash scripts/deployment/pmm-soak-mint-mirror-usdc-deployer-shortfall.sh --apply +# +# Env: RPC_URL_138, PMM_SOAK_RPC_URL_OVERRIDE, PRIVATE_KEY / DEPLOYER_PRIVATE_KEY, PMM_SOAK_GRID_JSON, PMM_SOAK_SEED_MIRROR_USDC_RAW, +# PMM_SOAK_WAIT_NONCE_CLEAR_SEC, CHAIN138_DEPLOY_GAS_PRICE_WEI +# +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +cd "$PROJECT_ROOT" +# 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 + +USDC_M=0x71D6687F38b93CCad569Fa6352c876eea967201b +D=0x4A666F96fC8764181194447A7dFdb7d471b301C8 +RPC="${RPC_URL_138:-http://192.168.11.211:8545}" +PK="${DEPLOYER_PRIVATE_KEY:-${PRIVATE_KEY:-}}" +SEED_USDC="${PMM_SOAK_SEED_MIRROR_USDC_RAW:-100000000}" +MAX_LI=6533 +GAS_WEI="${CHAIN138_DEPLOY_GAS_PRICE_WEI:-1000}" +MAX_WAIT="${PMM_SOAK_WAIT_NONCE_CLEAR_SEC:-7200}" + +APPLY=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --apply) APPLY=1 ;; + --dry-run) APPLY=0 ;; + *) echo "unknown: $1" >&2; exit 2 ;; + esac + shift +done + +if [[ "$APPLY" -eq 1 && -z "$PK" ]]; then + echo "[mint-usdc] FATAL: --apply needs PRIVATE_KEY" >&2 + exit 1 +fi + +USDC_NEEDED="$(python3 -c "print(int('$SEED_USDC') * (int('$MAX_LI') + 1))")" +USDC_BAL="$(cast call "$USDC_M" 'balanceOf(address)(uint256)' "$D" --rpc-url "$RPC" | awk '{print $1}')" +if (( USDC_BAL >= USDC_NEEDED )); then + echo "[mint-usdc] deployer already has $USDC_BAL >= need $USDC_NEEDED - nothing to do" + exit 0 +fi +SHORTFALL="$(python3 -c "print(int('$USDC_NEEDED') - int('$USDC_BAL'))")" +echo "[mint-usdc] need=$USDC_NEEDED bal=$USDC_BAL shortfall=$SHORTFALL" + +if [[ "$APPLY" -eq 0 ]]; then + echo "[mint-usdc] dry-run: after mempool clear, would run:" + echo " cast send $USDC_M 'mint(address,uint256)' $D $SHORTFALL --rpc-url \"\$RPC_URL_138\" --private-key \"\$PRIVATE_KEY\" --legacy --gas-price $GAS_WEI" + exit 0 +fi + +start="$SECONDS" +while true; do + nl="$(cast nonce "$D" --rpc-url "$RPC" -B latest)" + np="$(cast nonce "$D" --rpc-url "$RPC" -B pending)" + if [[ "$nl" == "$np" ]]; then + echo "[mint-usdc] nonce clear (latest=pending=$nl)" + break + fi + if (( SECONDS - start > MAX_WAIT )); then + echo "[mint-usdc] FATAL: pending nonce (latest=$nl pending=$np) after ${MAX_WAIT}s" >&2 + exit 1 + fi + echo "[mint-usdc] waiting mempool latest=$nl pending=$np ..." + sleep 3 +done + +cast send "$USDC_M" 'mint(address,uint256)' "$D" "$SHORTFALL" \ + --rpc-url "$RPC" --private-key "$PK" --legacy --gas-price "$GAS_WEI" +USDC_BAL="$(cast call "$USDC_M" 'balanceOf(address)(uint256)' "$D" --rpc-url "$RPC" | awk '{print $1}')" +echo "[mint-usdc] done deployer mirror USDC balance=$USDC_BAL" diff --git a/scripts/deployment/pmm-soak-operator-fund-full-grid-tranches.sh b/scripts/deployment/pmm-soak-operator-fund-full-grid-tranches.sh new file mode 100755 index 00000000..756811e1 --- /dev/null +++ b/scripts/deployment/pmm-soak-operator-fund-full-grid-tranches.sh @@ -0,0 +1,195 @@ +#!/usr/bin/env bash +# Fund all grid wallets (0..6533) in linear tranches — native + ERC-20 from deployer. +# Each tranche invokes pmm-soak-operator-fund-grid.sh (one tx per recipient per tranche). +# +# Usage (dry-run first): +# bash scripts/deployment/pmm-soak-operator-fund-full-grid-tranches.sh --dry-run +# bash scripts/deployment/pmm-soak-operator-fund-full-grid-tranches.sh --apply +# +# Env (optional overrides): +# PMM_SOAK_GRID_JSON — default config/pmm-soak-wallet-grid.json +# PMM_SOAK_FUND_CHUNK — wallets per tranche (default 250) +# NATIVE_AMOUNT_WEI — default 20000000000000000 (0.02) +# PMM_SOAK_SEED_CUSDT_RAW / PMM_SOAK_SEED_CUSDC_RAW / PMM_SOAK_SEED_MIRROR_USDT_RAW — 6-decimal raw per wallet (default 100000000 = 100 face) +# PMM_SOAK_SEED_MIRROR_USDC_RAW — per-wallet raw amount (default 100000000). +# PMM_SOAK_SKIP_MIRROR_USDC_OWNER_MINT=1 — do not auto-mint OfficialStableMirrorToken USDC to deployer when balance is short (default: mint shortfall via owner mint() before transfers). +# PMM_SOAK_WAIT_NONCE_CLEAR_SEC — before owner-mint, wait up to this many seconds for deployer latest==pending nonce (default 7200). Avoids "replacement transaction underpriced" when another process holds the next nonce. +# PMM_SOAK_RPC_URL_OVERRIDE — after dotenv load, force RPC_URL_138 (same as complete-grid / fund-grid long runs). +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +cd "$PROJECT_ROOT" + +# 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 + +GRID_JSON="${PMM_SOAK_GRID_JSON:-${PROJECT_ROOT}/config/pmm-soak-wallet-grid.json}" +CHUNK="${PMM_SOAK_FUND_CHUNK:-250}" +NATIVE_WEI="${NATIVE_AMOUNT_WEI:-20000000000000000}" +SEED_CUSDT="${PMM_SOAK_SEED_CUSDT_RAW:-100000000}" +SEED_CUSDC="${PMM_SOAK_SEED_CUSDC_RAW:-100000000}" +SEED_USDT="${PMM_SOAK_SEED_MIRROR_USDT_RAW:-100000000}" +SEED_USDC="${PMM_SOAK_SEED_MIRROR_USDC_RAW:-100000000}" + +CUSDT=0x93E66202A11B1772E55407B32B44e5Cd8eda7f22 +CUSDC=0xf22258f57794CC8E06237084b353Ab30fFfa640b +USDT_M=0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1 +USDC_M=0x71D6687F38b93CCad569Fa6352c876eea967201b + +RPC="${RPC_URL_138:-http://192.168.11.211:8545}" +PK="${DEPLOYER_PRIVATE_KEY:-${PRIVATE_KEY:-}}" +D=0x4A666F96fC8764181194447A7dFdb7d471b301C8 + +APPLY=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --apply) APPLY=1 ;; + --dry-run) APPLY=0 ;; + -h | --help) + sed -n '1,28p' "$0" | tail -n +2 + exit 0 + ;; + *) echo "unknown: $1" >&2; exit 2 ;; + esac + shift +done + +if [[ "$APPLY" -eq 1 && -z "$PK" ]]; then + echo "[fund-tranches] FATAL: --apply needs PRIVATE_KEY / DEPLOYER_PRIVATE_KEY" >&2 + exit 1 +fi + +if [[ ! -f "$GRID_JSON" ]]; then + echo "[fund-tranches] missing $GRID_JSON" >&2 + exit 1 +fi + +MAX_LI=6533 +GAS_WEI="${CHAIN138_DEPLOY_GAS_PRICE_WEI:-1000}" + +refresh_usdc_bal() { + USDC_BAL="$(cast call "$USDC_M" 'balanceOf(address)(uint256)' "$D" --rpc-url "$RPC" | awk '{print $1}')" +} + +# Wait until deployer has no pending txs (latest nonce tag == pending nonce tag). +wait_deployer_nonce_mempool_clear() { + local max_wait="${PMM_SOAK_WAIT_NONCE_CLEAR_SEC:-7200}" + local start="$SECONDS" + while true; do + local nl np + nl="$(cast nonce "$D" --rpc-url "$RPC" -B latest)" + np="$(cast nonce "$D" --rpc-url "$RPC" -B pending)" + if [[ "$nl" == "$np" ]]; then + echo "[fund-tranches] deployer nonce clear (latest=pending=$nl)" + return 0 + fi + if (( SECONDS - start > max_wait )); then + echo "[fund-tranches] FATAL: deployer still has pending nonce (latest=$nl pending=$np) after ${max_wait}s — stop other deployer senders or raise PMM_SOAK_WAIT_NONCE_CLEAR_SEC" >&2 + exit 1 + fi + echo "[fund-tranches] waiting for deployer mempool (latest=$nl pending=$np) …" + sleep 3 + done +} + +USDC_NEEDED="$(python3 -c "print(int('$SEED_USDC') * (int('$MAX_LI') + 1))")" +refresh_usdc_bal +DO_MIRROR_USDC=1 +if (( USDC_BAL < USDC_NEEDED )); then + SHORTFALL="$(python3 -c "print(int('$USDC_NEEDED') - int('$USDC_BAL'))")" + if [[ "${PMM_SOAK_SKIP_MIRROR_USDC_OWNER_MINT:-0}" == "1" ]]; then + DO_MIRROR_USDC=0 + echo "[fund-tranches] WARN: deployer mirror USDC balance $USDC_BAL < need $USDC_NEEDED; PMM_SOAK_SKIP_MIRROR_USDC_OWNER_MINT=1 — skipping mirror USDC tranches" + elif [[ "$APPLY" -eq 0 ]]; then + echo "[fund-tranches] deployer mirror USDC $USDC_BAL < need $USDC_NEEDED — dry-run: on --apply would owner-mint $SHORTFALL to deployer then fund mirror USDC" + echo " cast send $USDC_M 'mint(address,uint256)' $D $SHORTFALL --rpc-url \"\$RPC_URL_138\" --private-key \"\$PRIVATE_KEY\" --legacy --gas-price $GAS_WEI" + DO_MIRROR_USDC=1 + elif [[ -z "$PK" ]]; then + DO_MIRROR_USDC=0 + echo "[fund-tranches] WARN: shortfall $SHORTFALL but no PRIVATE_KEY — cannot mint; skipping mirror USDC tranches" >&2 + else + echo "[fund-tranches] owner-mint mirror USDC shortfall $SHORTFALL to deployer (need $USDC_NEEDED, have $USDC_BAL)" + wait_deployer_nonce_mempool_clear + cast send "$USDC_M" 'mint(address,uint256)' "$D" "$SHORTFALL" \ + --rpc-url "$RPC" --private-key "$PK" --legacy --gas-price "$GAS_WEI" + refresh_usdc_bal + if (( USDC_BAL < USDC_NEEDED )); then + echo "[fund-tranches] FATAL: after mint deployer USDC $USDC_BAL still < $USDC_NEEDED" >&2 + exit 1 + fi + echo "[fund-tranches] deployer mirror USDC balance now $USDC_BAL" + fi +fi + +run_native() { + local from="$1" to="$2" + echo "[fund-tranches] === native linear $from..$to (apply=$APPLY) ===" + if [[ "$APPLY" -eq 1 ]]; then + PMM_SOAK_GRID_JSON="$GRID_JSON" NATIVE_AMOUNT_WEI="$NATIVE_WEI" \ + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-operator-fund-grid.sh" --apply --native --from-linear "$from" --to-linear "$to" + else + PMM_SOAK_GRID_JSON="$GRID_JSON" NATIVE_AMOUNT_WEI="$NATIVE_WEI" \ + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-operator-fund-grid.sh" --native --from-linear "$from" --to-linear "$to" + fi +} + +run_erc20() { + local from="$1" to="$2" tok="$3" amt="$4" + echo "[fund-tranches] === ERC-20 $tok linear $from..$to amount=$amt (apply=$APPLY) ===" + if [[ "$APPLY" -eq 1 ]]; then + PMM_SOAK_GRID_JSON="$GRID_JSON" TOKEN="$tok" AMOUNT_WEI_PER_WALLET="$amt" \ + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-operator-fund-grid.sh" --apply --from-linear "$from" --to-linear "$to" + else + PMM_SOAK_GRID_JSON="$GRID_JSON" TOKEN="$tok" AMOUNT_WEI_PER_WALLET="$amt" \ + bash --noprofile --norc "${PROJECT_ROOT}/scripts/deployment/pmm-soak-operator-fund-grid.sh" --from-linear "$from" --to-linear "$to" + fi +} + +for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_native "$from" "$to" +done + +for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_erc20 "$from" "$to" "$CUSDT" "$SEED_CUSDT" +done + +for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_erc20 "$from" "$to" "$CUSDC" "$SEED_CUSDC" +done + +for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_erc20 "$from" "$to" "$USDT_M" "$SEED_USDT" +done + +if [[ "$DO_MIRROR_USDC" -eq 1 ]]; then + for ((from = 0; from <= MAX_LI; from += CHUNK)); do + to=$((from + CHUNK - 1)) + [[ "$to" -gt "$MAX_LI" ]] && to="$MAX_LI" + run_erc20 "$from" "$to" "$USDC_M" "$SEED_USDC" + done +else + echo "[fund-tranches] mirror USDC tranches skipped (see WARN above). Options: unset PMM_SOAK_SKIP_MIRROR_USDC_OWNER_MINT and re-run --apply (auto-mint), or manual mint then:" + echo " PMM_SOAK_GRID_JSON=$GRID_JSON TOKEN=$USDC_M AMOUNT_WEI_PER_WALLET=$SEED_USDC bash --noprofile --norc scripts/deployment/pmm-soak-operator-fund-grid.sh --apply --from-linear 0 --to-linear $MAX_LI" +fi + +echo "[fund-tranches] done (apply=$APPLY)" diff --git a/scripts/deployment/pmm-soak-operator-fund-grid.sh b/scripts/deployment/pmm-soak-operator-fund-grid.sh new file mode 100755 index 00000000..d4dfee9a --- /dev/null +++ b/scripts/deployment/pmm-soak-operator-fund-grid.sh @@ -0,0 +1,209 @@ +#!/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"