chore: sync workspace — configs, docs, scripts, CI, pnpm, submodules
Some checks failed
Deploy to Phoenix / validate (push) Failing after 15s
Deploy to Phoenix / deploy (push) Has been skipped

- 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
This commit is contained in:
defiQUG
2026-04-21 22:01:33 -07:00
parent e6bc7a6d7c
commit b8613905bd
231 changed files with 31657 additions and 2184 deletions

View File

@@ -1,21 +1,19 @@
#!/usr/bin/env bash
# Check deployer wallet balances on ChainID 138 (native ETH + ERC-20: WETH, WETH10, LINK, cUSDT, cUSDC).
# Output half of each balance as the funding plan for the three PMM liquidity pools.
# Check deployer wallet balances on ChainID 138 (native ETH + ERC-20: WETH, WETH10, LINK, cUSDT, cUSDC, cEURT).
# Output half of each balance as the funding plan for the canonical PMM liquidity pools.
#
# Usage:
# RPC_URL_138=https://rpc-core.d-bis.org ./scripts/deployment/check-deployer-balance-chain138-and-funding-plan.sh
# ./scripts/deployment/check-deployer-balance-chain138-and-funding-plan.sh
# RPC_URL_138=https://rpc-core.d-bis.org ./scripts/deployment/... # override
# # Or from smom-dbis-138: source .env then run from repo root with RPC_URL_138 set
#
# Requires: cast (foundry), jq (optional). RPC_URL_138 must be set and reachable.
# Requires: cast (foundry), jq (optional). Defaults to public Chain 138 HTTP RPC if RPC_URL_138 unset.
set -euo pipefail
DEPLOYER="${DEPLOYER_ADDRESS:-0x4A666F96fC8764181194447A7dFdb7d471b301C8}"
RPC="${RPC_URL_138:-}"
if [ -z "$RPC" ]; then
echo "ERROR: Set RPC_URL_138 (e.g. https://rpc-core.d-bis.org or http://192.168.11.211:8545)"
exit 1
fi
CHAIN138_PUBLIC_RPC_DEFAULT="https://rpc-http-pub.d-bis.org"
RPC="${RPC_URL_138:-${CHAIN138_PUBLIC_RPC_URL:-$CHAIN138_PUBLIC_RPC_DEFAULT}}"
CHAIN_ID=138
@@ -25,27 +23,48 @@ WETH10="0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f"
LINK="0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03"
CUSDT="0x93E66202A11B1772E55407B32B44e5Cd8eda7f22"
CUSDC="0xf22258f57794CC8E06237084b353Ab30fFfa640b"
CEURT="0xdf4b71c61E5912712C1Bdd451416B9aC26949d72"
USDT_OFFICIAL="0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1"
# PMM pool addresses (from LIQUIDITY_POOLS_MASTER_MAP / ADDRESS_MATRIX)
POOL_CUSDTCUSDC="0x9fcB06Aa1FD5215DC0E91Fd098aeff4B62fEa5C8"
POOL_CUSDTUSDT="0x6fc60DEDc92a2047062294488539992710b99D71"
POOL_CUSDCUSDC="0x90bd9Bf18Daa26Af3e814ea224032d015db58Ea5"
POOL_CUSDTCUSDC="0x9e89bAe009adf128782E19e8341996c596ac40dC"
POOL_CUSDTUSDT="0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66"
POOL_CUSDCUSDC="0xc39B7D0F40838cbFb54649d327f49a6DAC964062"
POOL_CUSDTWETH="0xaE38a008Ba4Dbf8D9F141D03e9dC8f7Dbe0ce17c"
POOL_CUSDCWETH="0xAAE68830a55767722618E869882c6Ed064Cc1eb2"
POOL_CEURTWETH="0x4a64c886cedF00db42ea5B946D6b304121ad9529"
get_balance() {
local addr="$1"
cast call "$addr" "balanceOf(address)(uint256)" "$DEPLOYER" --rpc-url "$RPC" 2>/dev/null || echo "0"
cast call "$addr" "balanceOf(address)(uint256)" "$DEPLOYER" --rpc-url "$RPC" 2>/dev/null | awk '{print $1}' || echo "0"
}
get_decimals() {
local addr="$1"
cast call "$addr" "decimals()(uint8)" --rpc-url "$RPC" 2>/dev/null | cast --to-dec 2>/dev/null || echo "18"
half_of() {
python3 - "$1" <<'PY'
import sys
print(int(sys.argv[1]) // 2)
PY
}
format_units() {
python3 - "$1" "$2" <<'PY'
from decimal import Decimal, getcontext
import sys
getcontext().prec = 80
raw = Decimal(sys.argv[1])
decimals = int(sys.argv[2])
value = raw / (Decimal(10) ** decimals)
if decimals == 18:
print(f"{value:.6f}")
else:
print(f"{value:.2f}")
PY
}
# Native balance
native_wei=$(cast balance "$DEPLOYER" --rpc-url "$RPC" 2>/dev/null || echo "0")
native_eth=$(awk "BEGIN { printf \"%.6f\", $native_wei / 1e18 }" 2>/dev/null || echo "0")
half_native_wei=$((native_wei / 2))
native_wei=$(cast balance "$DEPLOYER" --rpc-url "$RPC" 2>/dev/null | awk '{print $1}' || echo "0")
native_eth=$(format_units "$native_wei" 18)
half_native_wei=$(half_of "$native_wei")
echo "============================================"
echo "Deployer wallet — ChainID 138"
@@ -57,27 +76,23 @@ echo "--- Current balances ---"
echo " Native ETH: $native_eth ETH (raw: $native_wei wei)"
echo ""
RAW_WETH=0; RAW_WETH10=0; RAW_LINK=0; RAW_CUSDT=0; RAW_CUSDC=0
HALF_WETH=0; HALF_WETH10=0; HALF_LINK=0; HALF_CUSDT=0; HALF_CUSDC=0
RAW_WETH=0; RAW_WETH10=0; RAW_LINK=0; RAW_CUSDT=0; RAW_CUSDC=0; RAW_CEURT=0
HALF_WETH=0; HALF_WETH10=0; HALF_LINK=0; HALF_CUSDT=0; HALF_CUSDC=0; HALF_CEURT=0
for entry in "WETH:$WETH:18" "WETH10:$WETH10:18" "LINK:$LINK:18" "cUSDT:$CUSDT:6" "cUSDC:$CUSDC:6"; do
for entry in "WETH:$WETH:18" "WETH10:$WETH10:18" "LINK:$LINK:18" "cUSDT:$CUSDT:6" "cUSDC:$CUSDC:6" "cEURT:$CEURT:6"; do
sym="${entry%%:*}"; rest="${entry#*:}"; addr="${rest%%:*}"; dec="${rest#*:}"
raw=$(get_balance "$addr")
half=$((raw / 2))
half=$(half_of "$raw")
case "$sym" in
WETH) RAW_WETH=$raw; HALF_WETH=$half ;;
WETH10) RAW_WETH10=$raw; HALF_WETH10=$half ;;
LINK) RAW_LINK=$raw; HALF_LINK=$half ;;
cUSDT) RAW_CUSDT=$raw; HALF_CUSDT=$half ;;
cUSDC) RAW_CUSDC=$raw; HALF_CUSDC=$half ;;
cEURT) RAW_CEURT=$raw; HALF_CEURT=$half ;;
esac
if [ "$dec" = "18" ]; then
disp=$(awk "BEGIN { printf \"%.6f\", $raw / 1e18 }" 2>/dev/null || echo "0")
half_disp=$(awk "BEGIN { printf \"%.6f\", $half / 1e18 }" 2>/dev/null || echo "0")
else
disp=$(awk "BEGIN { printf \"%.2f\", $raw / 1e$dec }" 2>/dev/null || echo "0")
half_disp=$(awk "BEGIN { printf \"%.2f\", $half / 1e$dec }" 2>/dev/null || echo "0")
fi
disp=$(format_units "$raw" "$dec")
half_disp=$(format_units "$half" "$dec")
echo " $sym: $disp (raw: $raw) → half for LP: $half_disp (raw: $half)"
done
@@ -96,18 +111,36 @@ echo "Pool 3: cUSDC/USDC ($POOL_CUSDCUSDC)"
echo " Base (cUSDC): $HALF_CUSDC (decimals 6)"
echo " Quote (USDC): use same amount in USDC (official) — check deployer USDC balance separately if needed"
echo ""
echo "Pool 4: cUSDT/WETH ($POOL_CUSDTWETH)"
echo " Base (cUSDT): $HALF_CUSDT (decimals 6)"
echo " Quote (WETH): $HALF_WETH (decimals 18)"
echo ""
echo "Pool 5: cUSDC/WETH ($POOL_CUSDCWETH)"
echo " Base (cUSDC): $HALF_CUSDC (decimals 6)"
echo " Quote (WETH): $HALF_WETH (decimals 18)"
echo ""
echo "Pool 6: cEURT/WETH ($POOL_CEURTWETH)"
echo " Base (cEURT): $HALF_CEURT (decimals 6)"
echo " Quote (WETH): $HALF_WETH (decimals 18)"
echo ""
echo "--- Env vars for AddLiquidityPMMPoolsChain138 (half of cUSDT/cUSDC) ---"
echo "# Add to smom-dbis-138/.env and run: forge script script/dex/AddLiquidityPMMPoolsChain138.s.sol:AddLiquidityPMMPoolsChain138 --rpc-url \$RPC_URL_138 --broadcast --private-key \$PRIVATE_KEY"
echo "POOL_CUSDTCUSDC=$POOL_CUSDTCUSDC"
echo "POOL_CUSDTUSDT=$POOL_CUSDTUSDT"
echo "POOL_CUSDCUSDC=$POOL_CUSDCUSDC"
echo "POOL_CUSDTWETH=$POOL_CUSDTWETH"
echo "POOL_CUSDCWETH=$POOL_CUSDCWETH"
echo "POOL_CEURTWETH=$POOL_CEURTWETH"
echo "ADD_LIQUIDITY_BASE_AMOUNT=$HALF_CUSDT"
echo "ADD_LIQUIDITY_QUOTE_AMOUNT=$HALF_CUSDC"
echo "# For pool cUSDT/cUSDC only (base=cUSDT, quote=cUSDC). For cUSDT/USDT and cUSDC/USDC use per-pool vars:"
echo "# ADD_LIQUIDITY_CUSDTUSDT_BASE=$HALF_CUSDT ADD_LIQUIDITY_CUSDTUSDT_QUOTE=<deployer USDT balance / 2>"
echo "# ADD_LIQUIDITY_CUSDCUSDC_BASE=$HALF_CUSDC ADD_LIQUIDITY_CUSDCUSDC_QUOTE=<deployer USDC balance / 2>"
echo "# ADD_LIQUIDITY_CUSDTWETH_BASE=$HALF_CUSDT ADD_LIQUIDITY_CUSDTWETH_QUOTE=$HALF_WETH"
echo "# ADD_LIQUIDITY_CUSDCWETH_BASE=$HALF_CUSDC ADD_LIQUIDITY_CUSDCWETH_QUOTE=$HALF_WETH"
echo "# ADD_LIQUIDITY_CEURTWETH_BASE=$HALF_CEURT ADD_LIQUIDITY_CEURTWETH_QUOTE=$HALF_WETH"
echo ""
echo "--- Reserve ---"
echo " Keep half of native ETH for gas. Half for LP (if wrapping to WETH for a pool): $((half_native_wei / 2)) wei."
echo " Keep half of native ETH for gas. Half for LP (if wrapping to WETH for a pool): $(half_of "$half_native_wei") wei."
echo " WETH/LINK: half amounts above can be reserved for other use or future pools."
echo "============================================"

View File

@@ -0,0 +1,516 @@
#!/usr/bin/env python3
"""
Enumerate PMM pool addresses (deployment-status.json) and Uniswap V2 pair addresses
(pair-discovery JSON), then report deployer balances with **LP token resolution**.
**Uniswap V2:** The pair contract *is* the LP ERC-20 (`lpToken` = pair).
**DODO PMM (DVM / IDODOPMMPool):** Official DODO Vending Machine pools inherit ERC-20;
`balanceOf(pool)` is the LP share balance — the pool address **is** the LP token.
**DODO V1-style PMM:** Some pools expose ``_BASE_CAPITAL_TOKEN_`` / ``_QUOTE_CAPITAL_TOKEN_``;
LP exposure may be split across two capital ERC-20s (balances reported separately).
When ``balanceOf(pool)`` fails (RPC flake, proxy, or non-DVM), this script optionally
re-probes with DODO view calls and alternate public RPCs (see ``--resolve-dodo``).
Deployer: ``--deployer`` / ``DEPLOYER_ADDRESS`` / ``PRIVATE_KEY`` (see below).
Usage:
python3 scripts/deployment/check-deployer-lp-balances.py --summary-only
python3 scripts/deployment/check-deployer-lp-balances.py --resolve-dodo --json-out /tmp/lp.json
Requires: ``cast`` (Foundry). Environment: ``DEPLOYER_ADDRESS``, ``PRIVATE_KEY``, etc.
"""
from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
DEFAULT_STATUS = ROOT / "cross-chain-pmm-lps" / "config" / "deployment-status.json"
DEFAULT_DISCOVERY = ROOT / "reports" / "extraction" / "promod-uniswap-v2-live-pair-discovery-latest.json"
DEFAULT_ENV = ROOT / "smom-dbis-138" / ".env"
ZERO = "0x0000000000000000000000000000000000000000"
DEFAULT_RPC: dict[str, str] = {
"1": "https://eth.llamarpc.com",
"10": "https://mainnet.optimism.io",
"25": "https://evm.cronos.org",
"56": "https://bsc-dataseed.binance.org",
"100": "https://rpc.gnosischain.com",
"137": "https://polygon-rpc.com",
"8453": "https://mainnet.base.org",
"42161": "https://arbitrum-one.publicnode.com",
"42220": "https://forno.celo.org",
"43114": "https://avalanche-c-chain.publicnode.com",
"1111": "https://api.wemix.com",
}
# Extra public RPCs (retry when primary fails — connection resets, rate limits).
RPC_FALLBACKS: dict[str, list[str]] = {
"1": [
"https://ethereum.publicnode.com",
"https://1rpc.io/eth",
"https://rpc.ankr.com/eth",
],
"137": ["https://polygon-bor.publicnode.com", "https://1rpc.io/matic"],
"42161": ["https://arbitrum.llamarpc.com"],
"56": ["https://bsc.publicnode.com"],
"8453": ["https://base.llamarpc.com"],
"10": ["https://optimism.publicnode.com"],
}
RPC_KEYS: dict[str, list[str]] = {
"1": ["ETHEREUM_MAINNET_RPC", "ETH_MAINNET_RPC_URL"],
"10": ["OPTIMISM_RPC_URL", "OPTIMISM_MAINNET_RPC"],
"25": ["CRONOS_RPC_URL", "CRONOS_MAINNET_RPC"],
"56": ["BSC_RPC_URL", "BSC_MAINNET_RPC"],
"100": ["GNOSIS_RPC_URL", "GNOSIS_MAINNET_RPC", "GNOSIS_RPC"],
"137": ["POLYGON_MAINNET_RPC", "POLYGON_RPC_URL"],
"138": ["RPC_URL_138", "CHAIN_138_RPC_URL"],
"8453": ["BASE_RPC_URL", "BASE_MAINNET_RPC"],
"42161": ["ARBITRUM_RPC_URL", "ARBITRUM_MAINNET_RPC"],
"42220": ["CELO_RPC_URL", "CELO_MAINNET_RPC", "CELO_RPC"],
"43114": ["AVALANCHE_RPC_URL", "AVALANCHE_MAINNET_RPC"],
"1111": ["WEMIX_RPC_URL", "WEMIX_RPC"],
"651940": ["ALL_MAINNET_RPC", "CHAIN_651940_RPC_URL"],
}
def load_dotenv(path: Path) -> dict[str, str]:
out: dict[str, str] = {}
if not path.is_file():
return out
for raw in path.read_text().splitlines():
line = raw.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
out[k.strip()] = v.strip().strip('"').strip("'")
return out
def resolve(env: dict[str, str], key: str, default: str = "") -> str:
v = env.get(key, "")
if v.startswith("${") and ":-" in v:
inner = v[2:-1]
alt = inner.split(":-", 1)
return env.get(alt[0], alt[1] if len(alt) > 1 else "")
return v or default
def rpc_for(env: dict[str, str], cid: str) -> str:
for k in RPC_KEYS.get(cid, []):
v = resolve(env, k, "")
if v and not v.startswith("$"):
return v
return DEFAULT_RPC.get(cid, "") or (
resolve(env, "RPC_URL_138", "http://127.0.0.1:8545") if cid == "138" else ""
)
def rpc_chain_list(env: dict[str, str], cid: str) -> list[str]:
primary = rpc_for(env, cid)
seen: set[str] = set()
out: list[str] = []
for u in [primary] + RPC_FALLBACKS.get(cid, []):
if u and u not in seen:
seen.add(u)
out.append(u)
return out
def deployer_address(env: dict[str, str], override: str | None) -> str:
if override:
return override
for k in ("DEPLOYER_ADDRESS", "DEPLOYER"):
v = (os.environ.get(k) or "").strip()
if v:
return v
pk = env.get("PRIVATE_KEY", "") or (os.environ.get("PRIVATE_KEY") or "").strip()
if pk:
r = subprocess.run(
["cast", "wallet", "address", pk],
capture_output=True,
text=True,
check=False,
)
if r.returncode == 0 and r.stdout.strip():
return r.stdout.strip()
return (env.get("DEPLOYER_ADDRESS") or "").strip()
def parse_uint(s: str) -> int:
return int(s.strip().split()[0])
def parse_address_line(s: str) -> str | None:
s = s.strip()
if not s:
return None
m = re.search(r"(0x[a-fA-F0-9]{40})", s)
return m.group(1) if m else None
def cast_call(to: str, sig: str, rpc: str) -> tuple[str, str]:
cmd = ["cast", "call", to, sig, "--rpc-url", rpc]
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
return "err", (r.stderr or r.stdout or "").strip()[:400]
return "ok", r.stdout.strip()
def erc20_balance(token: str, holder: str, rpc: str) -> tuple[str, int | str]:
cmd = ["cast", "call", token, "balanceOf(address)(uint256)", holder, "--rpc-url", rpc]
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
return "err", (r.stderr or r.stdout or "").strip()[:400]
try:
return "ok", parse_uint(r.stdout)
except (ValueError, IndexError):
return "err", f"parse:{r.stdout[:120]}"
def erc20_balance_any_rpc(
token: str, holder: str, rpcs: list[str]
) -> tuple[str, int | str, str]:
"""Returns (status, value|err, rpc_used)."""
last_err = ""
for rpc in rpcs:
st, val = erc20_balance(token, holder, rpc)
if st == "ok":
return st, val, rpc
last_err = str(val)
return "err", last_err, rpcs[0] if rpcs else ""
def resolve_pmm_row(
pool: str,
deployer: str,
rpcs: list[str],
do_resolve_dodo: bool,
) -> dict:
"""
Build a result dict with lp resolution fields.
Tries: pool ERC20 balance (any RPC) -> DODO _BASE/_QUOTE -> capital tokens.
"""
rec: dict = {
"contract": pool,
"lpTokenAddress": pool,
"lpResolution": "unknown",
"dodoBaseToken": None,
"dodoQuoteToken": None,
"lpBalances": [],
"balanceRaw": 0,
"status": "pending",
"error": None,
"rpcUsed": rpcs[0] if rpcs else None,
}
st, val, used = erc20_balance_any_rpc(pool, deployer, rpcs)
rec["rpcUsed"] = used
if st == "ok":
rec["status"] = "ok"
rec["lpResolution"] = "dvm_or_erc20_pool"
rec["balanceRaw"] = int(val)
rec["lpBalances"] = [
{
"role": "lp_erc20",
"token": pool,
"raw": int(val),
"note": "balanceOf(pool): DVM LP shares are usually the pool contract itself",
}
]
return rec
rec["error"] = str(val)
if not do_resolve_dodo:
rec["status"] = "erc20_error"
rec["lpResolution"] = "unresolved_pass_resolve_dodo"
return rec
base_tok: str | None = None
quote_tok: str | None = None
for rpc in rpcs:
stb, outb = cast_call(pool, "_BASE_TOKEN_()(address)", rpc)
if stb == "ok":
base_tok = parse_address_line(outb)
rec["rpcUsed"] = rpc
break
if base_tok:
rec["dodoBaseToken"] = base_tok
for rpc in rpcs:
stq, outq = cast_call(pool, "_QUOTE_TOKEN_()(address)", rpc)
if stq == "ok":
quote_tok = parse_address_line(outq)
rec["dodoQuoteToken"] = quote_tok
break
# Retry pool balanceOf after confirming DVM interface (fresh RPC may fix flake)
st2, val2, used2 = erc20_balance_any_rpc(pool, deployer, rpcs)
if st2 == "ok":
rec["status"] = "ok"
rec["lpResolution"] = "dvm_erc20_pool_after_probe"
rec["balanceRaw"] = int(val2)
rec["rpcUsed"] = used2
rec["error"] = None
rec["lpBalances"] = [
{
"role": "lp_erc20",
"token": pool,
"raw": int(val2),
"note": "balanceOf(pool) succeeded after _BASE_TOKEN_ probe + RPC retry",
}
]
return rec
capital_balances: list[dict] = []
for cap_sig, role in (
("_BASE_CAPITAL_TOKEN_()(address)", "base_capital"),
("_QUOTE_CAPITAL_TOKEN_()(address)", "quote_capital"),
):
tok_a: str | None = None
for rpc in rpcs:
stc, outc = cast_call(pool, cap_sig, rpc)
if stc == "ok":
tok_a = parse_address_line(outc)
if tok_a and tok_a != ZERO:
bst, bval, bused = erc20_balance_any_rpc(tok_a, deployer, rpcs)
if bst == "ok":
capital_balances.append(
{
"role": role,
"token": tok_a,
"raw": int(bval),
"note": f"DODO V1-style {cap_sig.split('(')[0]} balanceOf",
}
)
break
if capital_balances:
rec["status"] = "ok"
rec["lpResolution"] = "v1_capital_tokens"
rec["lpTokenAddress"] = pool
rec["lpBalances"] = capital_balances
rec["balanceRaw"] = sum(x["raw"] for x in capital_balances)
rec["error"] = None
return rec
if base_tok:
rec["lpResolution"] = "dvm_interface_no_balance"
rec["status"] = "erc20_error"
rec["error"] = (
f"Pool responds as DVM (_BASE_TOKEN_={base_tok}) but balanceOf(pool) failed: {rec.get('error', '')[:200]}"
)
else:
rec["status"] = "erc20_error"
rec["lpResolution"] = "unresolved"
return rec
def collect_entries(status_path: Path, discovery_path: Path) -> list[tuple]:
status = json.loads(status_path.read_text())
rows: list[tuple] = []
for cid, ch in (status.get("chains") or {}).items():
name = ch.get("name", cid)
for pool in (ch.get("pmmPools") or []) + (ch.get("pmmPoolsVolatile") or []) + (ch.get("gasPmmPools") or []):
addr = pool.get("poolAddress") or ""
if not addr or addr == ZERO:
continue
label = f"{pool.get('base')}/{pool.get('quote')}"
rows.append((cid, name, "PMM", label, addr))
if discovery_path.is_file():
disc = json.loads(discovery_path.read_text())
for ent in disc.get("entries") or []:
cid = str(ent.get("chain_id"))
name = ent.get("network", cid)
for pr in ent.get("pairsChecked") or []:
addr = pr.get("poolAddress") or ""
if not addr or addr == ZERO:
continue
label = f"{pr.get('base')}/{pr.get('quote')}"
rows.append((cid, name, "UniV2", label, addr))
seen: set[tuple[str, str]] = set()
out: list[tuple] = []
for row in rows:
k = (row[0], row[4].lower())
if k in seen:
continue
seen.add(k)
out.append(row)
return out
def main() -> int:
ap = argparse.ArgumentParser(
description="Deployer LP balances with DODO / Uni V2 LP token resolution."
)
ap.add_argument("--status", type=Path, default=DEFAULT_STATUS)
ap.add_argument("--discovery", type=Path, default=DEFAULT_DISCOVERY)
ap.add_argument("--env", type=Path, default=DEFAULT_ENV)
ap.add_argument("--deployer", default=None)
ap.add_argument("--summary-only", action="store_true")
ap.add_argument("--only-nonzero", action="store_true")
ap.add_argument(
"--no-resolve-dodo",
action="store_true",
help="Skip DODO _BASE_TOKEN_ / capital-token probes and extra RPC fallbacks (faster; more erc20_error).",
)
ap.add_argument(
"--chain-id",
type=int,
default=None,
metavar="N",
help="Only check this chain (e.g. 1 for Ethereum). Default: all chains.",
)
ap.add_argument(
"--json-out",
type=Path,
default=None,
help="Full report JSON.",
)
ap.add_argument(
"--errors-json",
type=Path,
default=None,
help="Rows that remain erc20_error or no_rpc.",
)
args = ap.parse_args()
env = load_dotenv(args.env)
dep = deployer_address(env, args.deployer)
if not dep:
print("No deployer: set PRIVATE_KEY or DEPLOYER_ADDRESS in .env or pass --deployer", file=sys.stderr)
return 1
rows = collect_entries(args.status, args.discovery)
if args.chain_id is not None:
want = str(args.chain_id)
rows = [r for r in rows if r[0] == want]
results: list[dict] = []
nonzero: list[dict] = []
errors: list[dict] = []
for cid, name, venue, label, addr in sorted(rows, key=lambda x: (int(x[0]), x[2], x[3])):
rpcs = rpc_chain_list(env, cid)
base_rec: dict = {
"chainId": cid,
"network": name,
"venue": venue,
"pair": label,
"contract": addr,
}
if not rpcs or not rpcs[0]:
base_rec["status"] = "no_rpc"
base_rec["lpResolution"] = "no_rpc"
errors.append(base_rec)
results.append(base_rec)
continue
if venue == "UniV2":
st, val, used = erc20_balance_any_rpc(addr, dep, rpcs)
r = {
**base_rec,
"lpTokenAddress": addr,
"lpResolution": "uniswap_v2_pair",
"rpcUsed": used,
"lpBalances": [
{
"role": "pair_lp",
"token": addr,
"raw": int(val) if st == "ok" else 0,
"note": "Uniswap V2 pair contract is the LP ERC-20",
}
],
"balanceRaw": int(val) if st == "ok" else 0,
"status": "ok" if st == "ok" else "erc20_error",
"error": None if st == "ok" else str(val),
"dodoBaseToken": None,
"dodoQuoteToken": None,
}
if st != "ok":
r["status"] = "erc20_error"
errors.append(r)
elif r["balanceRaw"] > 0:
nonzero.append(r)
results.append(r)
continue
# PMM
r = {**base_rec, **resolve_pmm_row(addr, dep, rpcs, not args.no_resolve_dodo)}
if r.get("status") == "ok" and r.get("balanceRaw", 0) > 0:
nonzero.append(r)
if r.get("status") != "ok":
errors.append(r)
results.append(r)
# Summary stats
by_res: dict[str, int] = {}
for r in results:
lr = r.get("lpResolution") or "unknown"
by_res[lr] = by_res.get(lr, 0) + 1
print(f"Deployer: {dep}")
print(f"Contracts checked: {len(rows)}")
print(f"Non-zero LP exposure (sum of components): {len(nonzero)}")
print(f"Errors / no RPC: {len(errors)}")
print(f"Resolution breakdown: {by_res}")
if args.no_resolve_dodo:
print("(Re-run without --no-resolve-dodo to probe DODO interfaces + RPC fallbacks.)")
if not args.summary_only:
print("\n=== Non-zero LP / capital balances ===")
for r in nonzero:
lp = r.get("lpTokenAddress", r.get("contract"))
print(
f" chain {r['chainId']} {r['venue']} {r['pair']} | lpToken={lp} | "
f"resolution={r.get('lpResolution')} | raw_total={r.get('balanceRaw')}"
)
for leg in r.get("lpBalances") or []:
if leg.get("raw", 0) > 0:
print(f" - {leg.get('role')} {leg.get('token')}: {leg.get('raw')} ({leg.get('note', '')[:60]})")
if not args.only_nonzero and errors:
print("\nSample unresolved / errors:")
for r in errors[:12]:
e = r.get("error", r.get("status", ""))
print(
f" chain {r['chainId']} {r['venue']} {r['pair']} | "
f"{r.get('lpResolution', '')}: {str(e)[:120]}"
)
if args.json_out:
payload = {
"deployer": dep,
"resolveDodo": not args.no_resolve_dodo,
"summary": {
"checked": len(rows),
"nonzero": len(nonzero),
"errors": len(errors),
"byLpResolution": by_res,
},
"nonzero": nonzero,
"all": results,
}
args.json_out.parent.mkdir(parents=True, exist_ok=True)
args.json_out.write_text(json.dumps(payload, indent=2) + "\n")
print(f"Wrote {args.json_out}")
if args.errors_json:
err_only = [r for r in results if r.get("status") in ("no_rpc", "erc20_error")]
args.errors_json.parent.mkdir(parents=True, exist_ok=True)
args.errors_json.write_text(json.dumps(err_only, indent=2) + "\n")
print(f"Wrote {args.errors_json} ({len(err_only)} rows)")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,31 +1,48 @@
#!/usr/bin/env bash
# Check deployer nonce and balance on Mainnet, Cronos, and Arbitrum.
# Check deployer nonce and balance on Mainnet, Cronos, and Arbitrum (public RPCs by default).
# Use to diagnose "nonce too high" / "invalid nonce" and "insufficient funds" before retrying cW* deploy.
# Usage: ./scripts/deployment/check-deployer-nonce-and-balance.sh
# Requires: smom-dbis-138/.env with PRIVATE_KEY, ETHEREUM_MAINNET_RPC, CRONOS_RPC_URL, ARBITRUM_MAINNET_RPC
# Requires: PRIVATE_KEY (repo root .env, smom-dbis-138/.env, or ~/.secure-secrets/private-keys.env via load-project-env).
# Optional: ETHEREUM_MAINNET_RPC, CRONOS_RPC_URL, ARBITRUM_MAINNET_RPC — if unset, uses public endpoints
# (override per chain with ETHEREUM_MAINNET_PUBLIC_RPC / CRONOS_MAINNET_PUBLIC_RPC / ARBITRUM_MAINNET_PUBLIC_RPC).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SMOM="${PROJECT_ROOT}/smom-dbis-138"
[[ -f "$SMOM/.env" ]] || { echo "Missing $SMOM/.env" >&2; exit 1; }
set -a
source "$SMOM/.env"
set +a
# shellcheck disable=SC1091
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh"
# Defaults when .env does not set provider URLs (read-only public RPCs).
PUBLIC_ETHEREUM_RPC="${ETHEREUM_MAINNET_PUBLIC_RPC:-https://ethereum-rpc.publicnode.com}"
PUBLIC_CRONOS_RPC="${CRONOS_MAINNET_PUBLIC_RPC:-https://evm.cronos.org}"
PUBLIC_ARBITRUM_RPC="${ARBITRUM_MAINNET_PUBLIC_RPC:-https://arbitrum-one-rpc.publicnode.com}"
DEPLOYER=""
if [[ -n "${PRIVATE_KEY:-}" ]]; then
DEPLOYER=$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)
fi
[[ -z "$DEPLOYER" ]] && { echo "Could not derive deployer address (set PRIVATE_KEY in $SMOM/.env)" >&2; exit 1; }
[[ -z "$DEPLOYER" ]] && {
echo "Could not derive deployer address. Set PRIVATE_KEY in ${PROJECT_ROOT}/.env, smom-dbis-138/.env, or ~/.secure-secrets/private-keys.env" >&2
exit 1
}
echo "Deployer address: $DEPLOYER"
echo ""
for label in "Mainnet (1)" "Cronos (25)" "Arbitrum (42161)"; do
case "$label" in
"Mainnet (1)") RPC="${ETHEREUM_MAINNET_RPC:-$ETH_MAINNET_RPC_URL}"; CHAIN=1 ;;
"Cronos (25)") RPC="${CRONOS_RPC_URL:-$CRONOS_RPC}"; CHAIN=25 ;;
"Arbitrum (42161)") RPC="${ARBITRUM_MAINNET_RPC:-$ARBITRUM_MAINNET_RPC_URL}"; CHAIN=42161 ;;
"Mainnet (1)")
RPC="${ETHEREUM_MAINNET_RPC:-${ETH_MAINNET_RPC_URL:-$PUBLIC_ETHEREUM_RPC}}"
CHAIN=1
;;
"Cronos (25)")
RPC="${CRONOS_RPC_URL:-${CRONOS_RPC:-$PUBLIC_CRONOS_RPC}}"
CHAIN=25
;;
"Arbitrum (42161)")
RPC="${ARBITRUM_MAINNET_RPC:-${ARBITRUM_MAINNET_RPC_URL:-$PUBLIC_ARBITRUM_RPC}}"
CHAIN=42161
;;
esac
[[ -z "$RPC" ]] && { echo "$label: no RPC set, skip"; continue; }
NONCE=$(cast nonce "$DEPLOYER" --rpc-url "$RPC" 2>/dev/null || echo "?")

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SUBMODULE_ROOT="$PROJECT_ROOT/atomic-swap-dapp"
source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true
PROXMOX_HOST="${PROXMOX_HOST:-${PROXMOX_HOST_R630_02:-192.168.11.12}}"
VMID="${VMID:-5801}"
DEPLOY_ROOT="${DEPLOY_ROOT:-/var/www/atomic-swap}"
TMP_ARCHIVE="/tmp/atomic-swap-dapp-5801.tgz"
DIST_DIR="$SUBMODULE_ROOT/dist"
SKIP_BUILD="${SKIP_BUILD:-0}"
cleanup() {
rm -f "$TMP_ARCHIVE"
}
trap cleanup EXIT
if [ ! -d "$SUBMODULE_ROOT" ]; then
echo "Missing submodule at $SUBMODULE_ROOT" >&2
exit 1
fi
cd "$SUBMODULE_ROOT"
if [ "$SKIP_BUILD" != "1" ]; then
npm run sync:ecosystem >/dev/null
npm run validate:manifest >/dev/null
npm run build >/dev/null
fi
for required_path in \
"$DIST_DIR/index.html" \
"$DIST_DIR/data/ecosystem-manifest.json" \
"$DIST_DIR/data/live-route-registry.json" \
"$DIST_DIR/data/deployed-venue-inventory.json"; do
if [ ! -f "$required_path" ]; then
echo "Missing required build artifact: $required_path" >&2
exit 1
fi
done
jq -e '.supportedNetworks[] | select(.chainId == 138) | .deployedVenuePoolCount >= 19 and .publicRoutingPoolCount >= 19' \
"$DIST_DIR/data/ecosystem-manifest.json" >/dev/null
jq -e '.liveSwapRoutes | length >= 19' "$DIST_DIR/data/live-route-registry.json" >/dev/null
jq -e '.liveBridgeRoutes | length >= 12' "$DIST_DIR/data/live-route-registry.json" >/dev/null
jq -e '.networks[] | select(.chainId == 138) | .venueCounts.deployedVenuePoolCount >= 19 and .summary.totalVenues >= 19' \
"$DIST_DIR/data/deployed-venue-inventory.json" >/dev/null
rm -f "$TMP_ARCHIVE"
tar -C "$SUBMODULE_ROOT" -czf "$TMP_ARCHIVE" dist
scp -q -o StrictHostKeyChecking=accept-new "$TMP_ARCHIVE" "root@$PROXMOX_HOST:/tmp/atomic-swap-dapp-5801.tgz"
ssh -o StrictHostKeyChecking=accept-new "root@$PROXMOX_HOST" "
pct push $VMID /tmp/atomic-swap-dapp-5801.tgz /tmp/atomic-swap-dapp-5801.tgz
pct exec $VMID -- bash -lc '
set -euo pipefail
mkdir -p \"$DEPLOY_ROOT\"
find \"$DEPLOY_ROOT\" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
rm -rf /tmp/dist
tar -xzf /tmp/atomic-swap-dapp-5801.tgz -C /tmp
cp -R /tmp/dist/. \"$DEPLOY_ROOT/\"
mkdir -p /var/cache/nginx/atomic-swap-api
cat > /etc/nginx/conf.d/atomic-swap-api-cache.conf <<\"EOF\"
proxy_cache_path /var/cache/nginx/atomic-swap-api
levels=1:2
keys_zone=atomic_swap_api_cache:10m
max_size=256m
inactive=30m
use_temp_path=off;
EOF
cat > /etc/nginx/sites-available/atomic-swap <<\"EOF\"
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root $DEPLOY_ROOT;
index index.html;
location / {
try_files \$uri \$uri/ /index.html;
}
location = /index.html {
add_header Cache-Control \"no-store, no-cache, must-revalidate\" always;
}
location /data/ {
add_header Cache-Control \"no-store, no-cache, must-revalidate\" always;
}
location /assets/ {
add_header Cache-Control \"public, max-age=31536000, immutable\" always;
}
location /api/v1/ {
proxy_pass https://explorer.d-bis.org/api/v1/;
proxy_ssl_server_name on;
proxy_set_header Host explorer.d-bis.org;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host \$host;
proxy_http_version 1.1;
proxy_buffering on;
proxy_cache atomic_swap_api_cache;
proxy_cache_methods GET HEAD;
proxy_cache_key \"\$scheme\$proxy_host\$request_uri\";
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
proxy_cache_lock_age 10s;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache_valid 200 10s;
proxy_cache_valid 404 1s;
proxy_cache_valid any 0;
proxy_cache_use_stale error timeout invalid_header updating http_429 http_500 http_502 http_503 http_504;
add_header X-Atomic-Swap-Cache \$upstream_cache_status always;
}
}
EOF
ln -sfn /etc/nginx/sites-available/atomic-swap /etc/nginx/sites-enabled/atomic-swap
rm -f /etc/nginx/sites-enabled/default
rm -f /etc/nginx/sites-enabled/dapp
nginx -t
systemctl reload nginx
curl -fsS http://127.0.0.1/index.html >/dev/null
curl -fsS http://127.0.0.1/data/ecosystem-manifest.json >/dev/null
curl -fsS http://127.0.0.1/data/live-route-registry.json >/dev/null
curl -fsS http://127.0.0.1/data/deployed-venue-inventory.json >/dev/null
rm -rf /tmp/dist /tmp/atomic-swap-dapp-5801.tgz
'
rm -f /tmp/atomic-swap-dapp-5801.tgz
"
curl -fsS https://atomic-swap.defi-oracle.io/ >/dev/null
curl -fsS https://atomic-swap.defi-oracle.io/data/ecosystem-manifest.json | jq -e '.supportedNetworks[] | select(.chainId == 138) | .deployedVenuePoolCount >= 19 and .publicRoutingPoolCount >= 19' >/dev/null
curl -fsS https://atomic-swap.defi-oracle.io/data/live-route-registry.json | jq -e '.liveSwapRoutes | length >= 19' >/dev/null
curl -fsS https://atomic-swap.defi-oracle.io/data/live-route-registry.json | jq -e '.liveBridgeRoutes | length >= 12' >/dev/null
curl -fsS https://atomic-swap.defi-oracle.io/data/deployed-venue-inventory.json | jq -e '.networks[] | select(.chainId == 138) | .venueCounts.deployedVenuePoolCount >= 19 and .summary.totalVenues >= 19' >/dev/null
echo "Deployed atomic-swap-dapp to VMID $VMID via $PROXMOX_HOST"

View File

@@ -0,0 +1,191 @@
#!/usr/bin/env bash
# Price cW* wrapped stables on Ethereum Mainnet using:
# (1) Accounting peg assumption ($1 per token for USD-stable wrappers — document in reporting)
# (2) DODO PMM integration: getPoolPrice / getPoolPriceOrOracle (1e18 = $1 when oracle aligned)
# (3) Implied ratio from pool vault reserves (USDC per cWUSDT if base/quote match manifest)
# (4) Optional Chainlink ETH/USD (macro reference only; not cW* direct)
#
# Usage (from repo root):
# source scripts/lib/load-project-env.sh
# ./scripts/deployment/price-cw-token-mainnet.sh
# ./scripts/deployment/price-cw-token-mainnet.sh --json
# POOL_CWUSDT_USDC_MAINNET=0x... ./scripts/deployment/price-cw-token-mainnet.sh
#
# Requires: cast (Foundry), jq (for --json)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# shellcheck disable=SC1091
[[ -f "$PROJECT_ROOT/scripts/lib/load-project-env.sh" ]] && source "$PROJECT_ROOT/scripts/lib/load-project-env.sh"
JSON=false
for a in "$@"; do [[ "$a" == "--json" ]] && JSON=true; done
RPC="${ETHEREUM_MAINNET_RPC:-https://ethereum-rpc.publicnode.com}"
INT="${DODO_PMM_INTEGRATION_MAINNET:-0xa9F284eD010f4F7d7F8F201742b49b9f58e29b84}"
CWUSDT="${CWUSDT_MAINNET:-0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE}"
# Mainnet canonical USDC (do not use Chain 138 OFFICIAL_USDC_ADDRESS for this script)
USDC_MAINNET_CANON="${USDC_MAINNET:-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48}"
USDC_OFFICIAL="${USDC_MAINNET_CANON}"
POOL="${POOL_CWUSDT_USDC_MAINNET:-0x27f3aE7EE71Be3d77bAf17d4435cF8B895DD25D2}"
# Chainlink ETH/USD — verified on mainnet; optional macro reference
CHAINLINK_ETH_USD="${CHAINLINK_ETH_USD_FEED:-0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419}"
first_word() {
awk '{print $1}' | head -1
}
to_dec() {
local x="$1"
[[ -z "$x" ]] && echo "" && return
echo "$x" | sed 's/\[.*//g' | awk '{print $1}'
}
get_decimals() {
local tok="$1"
to_dec "$(cast call "$tok" "decimals()(uint8)" --rpc-url "$RPC" 2>/dev/null || echo "")"
}
price_oracle_or_mid() {
local pool="$1"
local out
out=$(cast call "$INT" "getPoolPriceOrOracle(address)(uint256)" "$pool" --rpc-url "$RPC" 2>/dev/null | first_word) || true
if [[ -n "$out" && "$out" != *Error* ]]; then
printf '%s|%s\n' "getPoolPriceOrOracle" "$out"
return
fi
out=$(cast call "$INT" "getPoolPrice(address)(uint256)" "$pool" --rpc-url "$RPC" 2>/dev/null | first_word) || true
printf '%s|%s\n' "getPoolPrice" "$out"
}
parse_two_uints() {
python3 -c "
import re, sys
s = sys.stdin.read()
# cast may print scientific notation for large numbers
parts = re.findall(r'-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?', s)
if len(parts) >= 2:
print(int(float(parts[0])), int(float(parts[1])))
"
}
reserves() {
local pool="$1"
cast call "$INT" "getPoolReserves(address)(uint256,uint256)" "$pool" --rpc-url "$RPC" 2>/dev/null | parse_two_uints
}
eth_usd_chainlink() {
local feed="$1"
[[ -z "$feed" ]] && { echo ""; return; }
local code
code=$(cast code "$feed" --rpc-url "$RPC" 2>/dev/null || true)
[[ -z "$code" || "$code" == "0x" ]] && { echo ""; return; }
cast call "$feed" "latestRoundData()(uint80,int256,uint256,uint256,uint80)" --rpc-url "$RPC" 2>/dev/null
}
db=$(get_decimals "$CWUSDT")
dq=$(get_decimals "$USDC_OFFICIAL")
read -r br_raw qr_raw < <(reserves "$POOL" | awk '{print $1, $2}')
br_raw=$(to_dec "${br_raw:-0}")
qr_raw=$(to_dec "${qr_raw:-0}")
_pom=$(price_oracle_or_mid "$POOL")
pm_method="${_pom%%|*}"
price_raw=$(to_dec "${_pom#*|}")
# price from integration is 1e18-scaled "USD" in docs
price_human=""
if [[ -n "$price_raw" && "$price_raw" =~ ^[0-9]+$ ]]; then
price_human=$(python3 -c "print(int('$price_raw')/1e18)" 2>/dev/null || echo "")
fi
implied_usdc_per_cwusdt=""
if [[ -n "$br_raw" && -n "$qr_raw" && "$br_raw" != "0" && -n "$db" && -n "$dq" ]]; then
implied_usdc_per_cwusdt=$(python3 -c "br=int('$br_raw'); qr=int('$qr_raw'); db=int('$db'); dq=int('$dq'); print((qr/10**dq)/(br/10**db))" 2>/dev/null || echo "")
fi
cl_eth=$(eth_usd_chainlink "$CHAINLINK_ETH_USD")
cl_eth_price=""
if [[ -n "$cl_eth" ]]; then
# latestRoundData(): cast prints one value per line; answer is the 2nd field (int256, 1e8 USD for ETH/USD)
ans=$(echo "$cl_eth" | awk 'NR == 2 { print $1; exit }')
[[ -n "$ans" ]] && cl_eth_price=$(python3 -c "a=int(float('$ans')); print(a/1e8)" 2>/dev/null || echo "")
fi
if [[ "$JSON" == true ]]; then
jq_n() { [[ -z "$1" ]] && echo null || echo "$1"; }
jq -n \
--arg network "ethereum-mainnet" \
--arg token "cWUSDT" \
--arg token_addr "$CWUSDT" \
--arg pool "$POOL" \
--arg integration "$INT" \
--argjson accounting_usd_assumption 1 \
--arg pool_price_method "${pm_method:-none}" \
--argjson pool_price_raw "$(jq_n "$price_raw")" \
--argjson pool_price_usd_scale "$(jq_n "$price_human")" \
--argjson base_reserve_raw "$(jq_n "$br_raw")" \
--argjson quote_reserve_raw "$(jq_n "$qr_raw")" \
--arg implied_str "${implied_usdc_per_cwusdt:-}" \
--argjson chainlink_eth_usd "$(jq_n "$cl_eth_price")" \
'{
network: $network,
token: $token,
tokenAddress: $token_addr,
poolAddress: $pool,
integrationAddress: $integration,
accountingUsdAssumptionPerToken: $accounting_usd_assumption,
poolMidOrOracle: {
method: $pool_price_method,
priceRaw1e18: $pool_price_raw,
priceAsUsdIf1e18EqualsOneDollar: $pool_price_usd_scale
},
impliedFromReserves: {
baseReserveRaw: $base_reserve_raw,
quoteReserveRaw: $quote_reserve_raw,
impliedUsdcPerCwusdtIfBaseIsCwusdtAndQuoteIsUsdc: (if $implied_str == "" then null else ($implied_str | tonumber) end)
},
chainlinkEthUsd: $chainlink_eth_usd
}'
exit 0
fi
cat << EOF
========================================
cWUSDT (Mainnet) — USD pricing toolkit
========================================
RPC: ${RPC%%\?*}
Integration: $INT
Pool (cWUSDT/USDC): $POOL
cWUSDT token: $CWUSDT
USDC (mainnet): $USDC_OFFICIAL
(1) ACCOUNTING ASSUMPTION (treasury / reporting)
Treat 1 cWUSDT ≈ 1 USD only if your policy says the wrapper tracks USDT 1:1.
Use for: internal books, dashboards where Etherscan shows \$0.
(2) POOL MID / ORACLE (DODOPMMIntegration)
Method: ${pm_method:-n/a}
Raw price (uint256, doc scale 1e18 = \$1): ${price_raw:-n/a}
As decimal (if 1e18 = \$1): ${price_human:-n/a}
(3) IMPLIED FROM VAULT RESERVES (USDC per cWUSDT)
Base reserve (cWUSDT, ${db:-?} dp): ${br_raw:-n/a}
Quote reserve (USDC, ${dq:-?} dp): ${qr_raw:-n/a}
Implied USDC per 1 cWUSDT: ${implied_usdc_per_cwusdt:-n/a}
(Thin or imbalanced pools skew this; use for sanity check, not sole mark.)
(4) CHAINLINK ETH/USD (macro only — not cWUSDT)
Feed: ${CHAINLINK_ETH_USD}
ETH/USD: ${cl_eth_price:-n/a}
Optional env overrides:
ETHEREUM_MAINNET_RPC, DODO_PMM_INTEGRATION_MAINNET,
CWUSDT_MAINNET, POOL_CWUSDT_USDC_MAINNET, USDC_MAINNET,
CHAINLINK_ETH_USD_FEED
See: docs/03-deployment/CW_TOKEN_USD_PRICING_RUNBOOK.md
========================================
EOF

View File

@@ -1,13 +1,14 @@
#!/usr/bin/env bash
# Run remaining cW* steps: deploy (or dry-run), update token-mapping from .env, optional verify.
# See docs/07-ccip/CW_DEPLOY_AND_WIRE_RUNBOOK.md and docs/00-meta/CW_BRIDGE_TASK_LIST.md.
# Mainnet Etherscan (CompliantWrappedToken): smom-dbis-138/scripts/deployment/verify-mainnet-cw-etherscan.sh
#
# Usage:
# ./scripts/deployment/run-cw-remaining-steps.sh [--dry-run] [--deploy] [--update-mapping] [--verify]
# --dry-run Run deploy-cw in dry-run mode (print commands only).
# --deploy Run deploy-cw on all chains (requires RPC/PRIVATE_KEY in smom-dbis-138/.env).
# --update-mapping Update config/token-mapping-multichain.json from CWUSDT_*/CWUSDC_* in .env.
# --verify For each chain with CWUSDT_* set, check MINTER_ROLE/BURNER_ROLE on cW* for CW_BRIDGE_*.
# --update-mapping Update config/token-mapping-multichain.json from CW*_CHAIN keys in .env (12 cW symbols × mapped chains).
# --verify For each chain with CW_BRIDGE_* set, check MINTER_ROLE/BURNER_ROLE on every deployed cW* (CWUSDT…CWXAUT) vs CW_BRIDGE_*.
# With no options, runs --dry-run then --update-mapping (if any CWUSDT_* in .env).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -103,18 +104,18 @@ if $DO_UPDATE_MAPPING; then
fi
if $DO_VERIFY; then
echo "=== Verify MINTER/BURNER roles on cW* for each chain ==="
echo "=== Verify MINTER/BURNER roles on cW* for each chain (12 symbols) ==="
MINTER_ROLE=$(cast keccak "MINTER_ROLE" 2>/dev/null || echo "0x")
BURNER_ROLE=$(cast keccak "BURNER_ROLE" 2>/dev/null || echo "0x")
# Env suffix -> env var for CompliantWrappedToken (must match update_mapping keyToEnv)
CW_KEYS=(CWUSDT CWUSDC CWEURC CWEURT CWGBPC CWGBPT CWAUDC CWJPYC CWCHFC CWCADC CWXAUC CWXAUT)
for name in MAINNET CRONOS BSC POLYGON GNOSIS AVALANCHE BASE ARBITRUM OPTIMISM; do
cwusdt_var="CWUSDT_${name}"
bridge_var="CW_BRIDGE_${name}"
cwusdt="${!cwusdt_var:-}"
bridge="${!bridge_var:-}"
rpc_var="${name}_RPC_URL"
[[ -z "$rpc_var" ]] && rpc_var="${name}_RPC"
rpc="${!rpc_var:-}"
if [[ -z "$cwusdt" || -z "$bridge" ]]; then continue; fi
if [[ -z "$bridge" ]]; then continue; fi
if [[ -z "$rpc" ]]; then
case "$name" in
MAINNET) rpc="${ETH_MAINNET_RPC_URL:-${ETHEREUM_MAINNET_RPC:-}}";;
@@ -129,9 +130,17 @@ if $DO_VERIFY; then
esac
fi
if [[ -z "$rpc" ]]; then echo " Skip $name: no RPC"; continue; fi
m=$(cast call "$cwusdt" "hasRole(bytes32,address)(bool)" "$MINTER_ROLE" "$bridge" --rpc-url "$rpc" 2>/dev/null || echo "false")
b=$(cast call "$cwusdt" "hasRole(bytes32,address)(bool)" "$BURNER_ROLE" "$bridge" --rpc-url "$rpc" 2>/dev/null || echo "false")
echo " $name: MINTER=$m BURNER=$b (cWUSDT=$cwusdt bridge=$bridge)"
any=false
for key in "${CW_KEYS[@]}"; do
var="${key}_${name}"
addr="${!var:-}"
[[ -z "$addr" ]] && continue
any=true
m=$(cast call "$addr" "hasRole(bytes32,address)(bool)" "$MINTER_ROLE" "$bridge" --rpc-url "$rpc" 2>/dev/null || echo "false")
b=$(cast call "$addr" "hasRole(bytes32,address)(bool)" "$BURNER_ROLE" "$bridge" --rpc-url "$rpc" 2>/dev/null || echo "false")
echo " $name $key: MINTER=$m BURNER=$b ($addr)"
done
if ! $any; then echo " $name: no CW*_* addresses in .env; skipped"; fi
done
fi

View File

@@ -1,24 +1,42 @@
#!/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).
# 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).
# --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)"
@@ -28,11 +46,19 @@ 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
@@ -40,6 +66,13 @@ 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"
@@ -47,8 +80,6 @@ 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).
@@ -63,13 +94,21 @@ command -v jq &>/dev/null || { echo "jq required" >&2; exit 1; }
FROM_ADDR=$(cast wallet address --private-key "$PRIVATE_KEY")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "EI matrix ETH distribution (random 59 ETH per wallet)"
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 "RPC: $RPC"
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 ""
@@ -87,10 +126,41 @@ pending_nonce() {
cast to-dec "$hex"
}
random_wei() {
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")"
@@ -102,55 +172,120 @@ 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 '.wallets[] | .address' "$GRID" | tail -n +$((OFFSET + 1)) | head -n "$LIMIT"
jq -r --argjson o "$OFFSET" --argjson l "$LIMIT" '.wallets[$o:$o+$l][] | .address' "$GRID"
else
jq -r '.wallets[] | .address' "$GRID" | tail -n +$((OFFSET + 1))
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
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)
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