chore(sim): refresh deployment status, pool matrix, schemas, and scenario scripts

- deployment-status and pool-matrix snapshots aligned with validate-deployment-status.cjs.
- Micro-trade / scorecard docs and run-scenario wiring.

Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-07 22:56:16 -07:00
parent f7f3e3b020
commit 168dba25d9
13 changed files with 2051 additions and 84 deletions

View File

@@ -14,11 +14,12 @@ Implementation-grade blueprint for the **home-minted M1 suite on ChainID 138**,
2. See [docs/02-pool-topology.md](docs/02-pool-topology.md) for edge pool design.
3. See [docs/06-deployment-recipe.md](docs/06-deployment-recipe.md) for step-by-step deployment.
4. Config: [config/token-map.json](config/token-map.json), [config/pool-matrix.json](config/pool-matrix.json), [config/peg-bands.json](config/peg-bands.json), [config/chains.json](config/chains.json).
5. **Simulation:** Two-layer model (design vs deployed graph), routing supergraph, PMM edge function, and scenario-based sim: [docs/08-simulation-model.md](docs/08-simulation-model.md). First-pass params: [config/simulation-params.json](config/simulation-params.json), [docs/09-simulation-params-sheet.md](docs/09-simulation-params-sheet.md). Deployment-realistic sim: fill [config/deployment-status.json](config/deployment-status.json).
5. **Simulation:** Two-layer model (design vs deployed graph), routing supergraph, PMM edge function, and scenario-based sim: [docs/08-simulation-model.md](docs/08-simulation-model.md). First-pass params: [config/simulation-params.json](config/simulation-params.json), [docs/09-simulation-params-sheet.md](docs/09-simulation-params-sheet.md). Deployment-realistic sim: fill [config/deployment-status.json](config/deployment-status.json). **Hub stable pools** (`cW*/USDC`, `cW*/USDT`) live in `pmmPools[]`. **Volatile cWUSD\* vs TRUU** (mainnet) use `anchorAddresses.TRUU` plus `pmmPoolsVolatile[]` (runbook `docs/03-deployment/MAINNET_PMM_TRUU_CWUSD_PEG_AND_BOT_RUNBOOK.md` §11; parent scripts `deploy-mainnet-pmm-cw-truu-pool.sh`, `add-mainnet-truu-pmm-topup.sh`).
6. **Behavioral stability:** What to simulate first, three metrics in economic terms, where it can break: [docs/10-behavioral-stability-analysis.md](docs/10-behavioral-stability-analysis.md). **Safe inventory sizing** (closed-form I_T^* per chain): [docs/11-safe-inventory-sizing.md](docs/11-safe-inventory-sizing.md).
7. **Tools:** [scripts/size-inventory.cjs](scripts/size-inventory.cjs) — regenerate I_T^*, D_0 from params (single command, deterministic; see [scripts/README.md](scripts/README.md)). [scripts/validate-deployment-status.cjs](scripts/validate-deployment-status.cjs) — CI validation for deployment-status (phase 1 tokens when bridge=true; pool role/fee/k/tokens).
8. **Sim scorecard:** Simulator output contract and pass/fail gates: [docs/12-sim-scorecard.md](docs/12-sim-scorecard.md), [config/scorecard-schema.json](config/scorecard-schema.json). Bridge edge with optional **latency risk** ρ(Δt): [docs/08-simulation-model.md](docs/08-simulation-model.md) §2.
9. **Scenario contract:** Formal scenario input schema and three Phase 0 scenarios: [config/scenario-schema.json](config/scenario-schema.json), [config/scenarios/](config/scenarios/). **Routing exposure controls:** [config/routing-controls.json](config/routing-controls.json) (maxTradeSize, cooldown, minImprovementBps, publicRoutingEnabled). **Real sim (PR#1):** `node scripts/run-scenario.cjs hub_only_11` — graph from configs, PMM state, path enumeration + waterfilling, real scorecard (drain half-life, path concentration, capture, churn); see [spec/13-minimal-router-sim.md](spec/13-minimal-router-sim.md).
10. **USD-wrapper support lane:** Gas-budgeted micro-trade policy for `cWUSDC` / `cWUSDT` and a runnable scenario: [docs/15-gas-budgeted-micro-trade-support.md](docs/15-gas-budgeted-micro-trade-support.md), [config/scenarios/micro_support_usd_wrappers_1_56_137.json](config/scenarios/micro_support_usd_wrappers_1_56_137.json).
## Parent repo

View File

@@ -1,8 +1,17 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Public chain IDs and hub stable per chain for cW* edge pools.",
"version": "1.0.0",
"updated": "2026-02-26",
"description": "Public chain IDs and hub stable per chain for cW* edge pools (EVM). Non-EVM networks used for interoperability are listed under nonEvm (no EIP-155 id).",
"version": "1.1.0",
"updated": "2026-04-03",
"nonEvm": [
{
"identifier": "Solana",
"vmKind": "SVM",
"caip2": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"hubStableNote": "SPL USDC/USDT hubs differ from EVM; cW* edge PMM topology TBD when a wrapped representation is defined",
"adapter": "SolanaAdapter"
}
],
"chains": [
{ "chainId": 1, "name": "Ethereum Mainnet", "hubStable": "USDC" },
{ "chainId": 10, "name": "Optimism", "hubStable": "USDC" },

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,317 @@
{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Per-chain pool matrix: which cW* / stable pools to deploy. Single-sided on cW* side.","version":"1.0.0","updated":"2026-02-27","cwTokens":["cWUSDT","cWUSDC","cWAUSDT","cWEURC","cWEURT","cWUSDW"],"strategy":"hub_first","chains":{"1":{"name":"Ethereum Mainnet","hubStable":"USDC","poolsFirst":["cWUSDT/USDC","cWUSDC/USDC","cWEURC/USDC","cWEURT/USDC","cWUSDW/USDC","cWAUSDT/USDC"],"poolsOptional":["cWUSDT/USDT","cWUSDT/DAI","cWUSDC/USDT","cWUSDC/DAI"]},"56":{"name":"BSC","hubStable":"USDT","poolsFirst":["cWUSDT/USDT","cWUSDC/USDT","cWAUSDT/USDT","cWEURC/USDT","cWEURT/USDT","cWUSDW/USDT"],"poolsOptional":["cWUSDT/USDC","cWUSDT/BUSD","cWUSDC/USDC","cWUSDC/BUSD"]},"137":{"name":"Polygon","hubStable":"USDC","poolsFirst":["cWUSDT/USDC","cWUSDC/USDC","cWAUSDT/USDC","cWEURC/USDC","cWEURT/USDC","cWUSDW/USDC"],"poolsOptional":["cWUSDT/USDT","cWUSDC/USDT","cWUSDT/DAI","cWUSDC/DAI"]},"10":{"name":"Optimism","hubStable":"USDC","poolsFirst":["cWUSDT/USDC","cWUSDC/USDC","cWAUSDT/USDC","cWEURC/USDC","cWEURT/USDC","cWUSDW/USDC"],"poolsOptional":["cWUSDT/USDT","cWUSDC/USDT","cWUSDT/DAI","cWUSDC/DAI"]},"100":{"name":"Gnosis","hubStable":"USDC","poolsFirst":["cWUSDT/USDC","cWUSDC/USDC","cWAUSDT/USDC","cWEURC/USDC","cWEURT/USDC","cWUSDW/USDC"],"poolsOptional":["cWUSDT/USDT","cWUSDC/USDT","cWUSDT/DAI","cWUSDT/mUSD","cWUSDC/mUSD"]},"25":{"name":"Cronos","hubStable":"USDT","poolsFirst":["cWUSDT/USDT","cWUSDC/USDT","cWAUSDT/USDT","cWEURC/USDT","cWEURT/USDT","cWUSDW/USDT"],"poolsOptional":["cWUSDT/USDC","cWUSDT/BUSD","cWUSDC/USDC","cWUSDC/BUSD"]},"42220":{"name":"Celo","hubStable":"USDC","poolsFirst":["cWUSDT/USDC","cWUSDC/USDC","cWAUSDT/USDC","cWEURC/USDC","cWEURT/USDC","cWUSDW/USDC"],"poolsOptional":["cWUSDT/USDT","cWUSDC/USDT","cWUSDT/DAI","cWUSDC/DAI"]},"43114":{"name":"Avalanche C-Chain","hubStable":"USDC","poolsFirst":["cWUSDT/USDC","cWUSDC/USDC","cWAUSDT/USDC","cWEURC/USDC","cWEURT/USDC","cWUSDW/USDC"],"poolsOptional":["cWUSDT/USDT","cWUSDC/USDT","cWUSDT/DAI","cWUSDC/DAI"]},"42161":{"name":"Arbitrum One","hubStable":"USDC","poolsFirst":["cWUSDT/USDC","cWUSDC/USDC","cWAUSDT/USDC","cWEURC/USDC","cWEURT/USDC","cWUSDW/USDC"],"poolsOptional":["cWUSDT/USDT","cWUSDC/USDT","cWUSDT/DAI","cWUSDC/DAI"]},"8453":{"name":"Base","hubStable":"USDC","poolsFirst":["cWUSDT/USDC","cWUSDC/USDC","cWAUSDT/USDC","cWEURC/USDC","cWEURT/USDC","cWUSDW/USDC"],"poolsOptional":["cWUSDT/USDT","cWUSDC/USDT","cWUSDT/DAI","cWUSDC/DAI"]},"1111":{"name":"Wemix","hubStable":"USDT","poolsFirst":["cWUSDT/USDT","cWUSDC/USDT","cWAUSDT/USDT","cWEURC/USDT","cWEURT/USDT","cWUSDW/USDT"],"poolsOptional":["cWUSDT/USDC","cWUSDT/BUSD","cWUSDC/USDC","cWUSDC/BUSD"]}},"liquiditySizingTargets":{}}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Per-chain pool matrix: which cW* / stable pools to deploy. Single-sided on cW* side.",
"version": "1.1.0",
"updated": "2026-04-03",
"cwTokens": [
"cWUSDT",
"cWUSDC",
"cWAUSDT",
"cWEURC",
"cWEURT",
"cWUSDW",
"cWGBPC",
"cWGBPT",
"cWAUDC",
"cWJPYC",
"cWCHFC",
"cWCADC",
"cWXAUC",
"cWXAUT"
],
"strategy": "hub_first",
"notes": [
"The first-tier public cW matrix now covers the full GRU Wave 1 wrapped set in addition to the older USD/AUSDT/USDW lanes.",
"This file is a deployment plan, not proof of live liquidity. Live pool addresses still belong in cross-chain-pmm-lps/config/deployment-status.json."
],
"chains": {
"1": {
"name": "Ethereum Mainnet",
"hubStable": "USDC",
"poolsFirst": [
"cWUSDT/USDC",
"cWUSDC/USDC",
"cWEURC/USDC",
"cWEURT/USDC",
"cWGBPC/USDC",
"cWGBPT/USDC",
"cWAUDC/USDC",
"cWJPYC/USDC",
"cWCHFC/USDC",
"cWCADC/USDC",
"cWXAUC/USDC",
"cWXAUT/USDC",
"cWUSDW/USDC",
"cWAUSDT/USDC"
],
"poolsOptional": [
"cWUSDT/USDT",
"cWUSDT/DAI",
"cWUSDC/USDT",
"cWUSDC/DAI"
]
},
"56": {
"name": "BSC",
"hubStable": "USDT",
"poolsFirst": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWEURC/USDT",
"cWEURT/USDT",
"cWGBPC/USDT",
"cWGBPT/USDT",
"cWAUDC/USDT",
"cWJPYC/USDT",
"cWCHFC/USDT",
"cWCADC/USDT",
"cWXAUC/USDT",
"cWXAUT/USDT",
"cWUSDW/USDT",
"cWAUSDT/USDT"
],
"poolsOptional": [
"cWUSDT/USDC",
"cWUSDT/BUSD",
"cWUSDC/USDC",
"cWUSDC/BUSD"
]
},
"137": {
"name": "Polygon",
"hubStable": "USDC",
"poolsFirst": [
"cWUSDT/USDC",
"cWUSDC/USDC",
"cWEURC/USDC",
"cWEURT/USDC",
"cWGBPC/USDC",
"cWGBPT/USDC",
"cWAUDC/USDC",
"cWJPYC/USDC",
"cWCHFC/USDC",
"cWCADC/USDC",
"cWXAUC/USDC",
"cWXAUT/USDC",
"cWUSDW/USDC",
"cWAUSDT/USDC"
],
"poolsOptional": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWUSDT/DAI",
"cWUSDC/DAI"
]
},
"10": {
"name": "Optimism",
"hubStable": "USDC",
"poolsFirst": [
"cWUSDT/USDC",
"cWUSDC/USDC",
"cWEURC/USDC",
"cWEURT/USDC",
"cWGBPC/USDC",
"cWGBPT/USDC",
"cWAUDC/USDC",
"cWJPYC/USDC",
"cWCHFC/USDC",
"cWCADC/USDC",
"cWXAUC/USDC",
"cWXAUT/USDC",
"cWUSDW/USDC",
"cWAUSDT/USDC"
],
"poolsOptional": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWUSDT/DAI",
"cWUSDC/DAI"
]
},
"100": {
"name": "Gnosis",
"hubStable": "USDC",
"poolsFirst": [
"cWUSDT/USDC",
"cWUSDC/USDC",
"cWEURC/USDC",
"cWEURT/USDC",
"cWGBPC/USDC",
"cWGBPT/USDC",
"cWAUDC/USDC",
"cWJPYC/USDC",
"cWCHFC/USDC",
"cWCADC/USDC",
"cWXAUC/USDC",
"cWXAUT/USDC",
"cWUSDW/USDC",
"cWAUSDT/USDC"
],
"poolsOptional": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWUSDT/DAI",
"cWUSDT/mUSD",
"cWUSDC/mUSD"
]
},
"25": {
"name": "Cronos",
"hubStable": "USDT",
"poolsFirst": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWEURC/USDT",
"cWEURT/USDT",
"cWGBPC/USDT",
"cWGBPT/USDT",
"cWAUDC/USDT",
"cWJPYC/USDT",
"cWCHFC/USDT",
"cWCADC/USDT",
"cWXAUC/USDT",
"cWXAUT/USDT",
"cWUSDW/USDT",
"cWAUSDT/USDT"
],
"poolsOptional": [
"cWUSDT/USDC",
"cWUSDT/BUSD",
"cWUSDC/USDC",
"cWUSDC/BUSD"
]
},
"42220": {
"name": "Celo",
"hubStable": "USDC",
"poolsFirst": [
"cWUSDT/USDC",
"cWUSDC/USDC",
"cWEURC/USDC",
"cWEURT/USDC",
"cWGBPC/USDC",
"cWGBPT/USDC",
"cWAUDC/USDC",
"cWJPYC/USDC",
"cWCHFC/USDC",
"cWCADC/USDC",
"cWXAUC/USDC",
"cWXAUT/USDC",
"cWUSDW/USDC",
"cWAUSDT/USDC"
],
"poolsOptional": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWUSDT/DAI",
"cWUSDC/DAI"
]
},
"43114": {
"name": "Avalanche C-Chain",
"hubStable": "USDC",
"poolsFirst": [
"cWUSDT/USDC",
"cWUSDC/USDC",
"cWEURC/USDC",
"cWEURT/USDC",
"cWGBPC/USDC",
"cWGBPT/USDC",
"cWAUDC/USDC",
"cWJPYC/USDC",
"cWCHFC/USDC",
"cWCADC/USDC",
"cWXAUC/USDC",
"cWXAUT/USDC",
"cWUSDW/USDC",
"cWAUSDT/USDC"
],
"poolsOptional": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWUSDT/DAI",
"cWUSDC/DAI"
]
},
"42161": {
"name": "Arbitrum One",
"hubStable": "USDC",
"poolsFirst": [
"cWUSDT/USDC",
"cWUSDC/USDC",
"cWEURC/USDC",
"cWEURT/USDC",
"cWGBPC/USDC",
"cWGBPT/USDC",
"cWAUDC/USDC",
"cWJPYC/USDC",
"cWCHFC/USDC",
"cWCADC/USDC",
"cWXAUC/USDC",
"cWXAUT/USDC",
"cWUSDW/USDC",
"cWAUSDT/USDC"
],
"poolsOptional": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWUSDT/DAI",
"cWUSDC/DAI"
]
},
"8453": {
"name": "Base",
"hubStable": "USDC",
"poolsFirst": [
"cWUSDT/USDC",
"cWUSDC/USDC",
"cWEURC/USDC",
"cWEURT/USDC",
"cWGBPC/USDC",
"cWGBPT/USDC",
"cWAUDC/USDC",
"cWJPYC/USDC",
"cWCHFC/USDC",
"cWCADC/USDC",
"cWXAUC/USDC",
"cWXAUT/USDC",
"cWUSDW/USDC",
"cWAUSDT/USDC"
],
"poolsOptional": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWUSDT/DAI",
"cWUSDC/DAI"
]
},
"1111": {
"name": "Wemix",
"hubStable": "USDT",
"poolsFirst": [
"cWUSDT/USDT",
"cWUSDC/USDT",
"cWEURC/USDT",
"cWEURT/USDT",
"cWGBPC/USDT",
"cWGBPT/USDT",
"cWAUDC/USDT",
"cWJPYC/USDT",
"cWCHFC/USDT",
"cWCADC/USDT",
"cWXAUC/USDT",
"cWXAUT/USDT",
"cWUSDW/USDT",
"cWAUSDT/USDT"
],
"poolsOptional": [
"cWUSDT/USDC",
"cWUSDT/BUSD",
"cWUSDC/USDC",
"cWUSDC/BUSD"
]
}
},
"liquiditySizingTargets": {}
}

View File

@@ -135,9 +135,84 @@
}
}
},
"microTradePolicy": {
"type": "object",
"description": "Optional gas-budgeted support trades for selected cW wrappers. Use to model tiny corrective or turnover-seeding trades without turning the PMM into a venue.",
"properties": {
"enabled": {
"type": "boolean",
"default": false
},
"tokens": {
"type": "array",
"items": {
"type": "string"
},
"description": "Base cW tokens to support, e.g. [\"cWUSDT\", \"cWUSDC\"]"
},
"quoteTokens": {
"type": "array",
"items": {
"type": "string"
},
"description": "Allowed quote stables for the support lane, e.g. [\"USDT\", \"USDC\"]"
},
"preferMatchedQuote": {
"type": "boolean",
"default": true,
"description": "Prefer cWUSDT/USDT and cWUSDC/USDC when those rails exist; otherwise fall back to another allowed quote."
},
"mode": {
"type": "string",
"enum": [
"inventory",
"alternate",
"inventory_or_alternate",
"buy_base",
"sell_base"
],
"default": "inventory_or_alternate",
"description": "inventory = only trade when pool inventory leaves the band; alternate = buy/sell flip-flop; inventory_or_alternate = inventory-corrective first, alternate only inside band."
},
"tradeSizeUnits": {
"type": "number",
"minimum": 0,
"description": "Nominal base-token size per support trade."
},
"tradesPerEpoch": {
"type": "integer",
"minimum": 0,
"description": "Requested support trades per epoch before gas-budget clipping."
},
"gasCostPerTradeUnits": {
"type": "number",
"minimum": 0,
"description": "Abstract gas cost per trade for scenario comparison; not chain-native gas units."
},
"gasBudgetPerEpochUnits": {
"type": "number",
"minimum": 0,
"description": "Per-epoch gas budget for support trades. Maximum trades = floor(budget / gasCostPerTradeUnits) when gasCostPerTradeUnits > 0."
},
"inventoryBandFraction": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.05,
"description": "Keep support trades inventory-aware within +/- this fraction of I_T^* before alternating."
},
"maxFractionOfTarget": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.01,
"description": "Clamp each support trade to this fraction of I_T^*."
}
}
},
"seed": {
"type": "integer",
"description": "Optional RNG seed for deterministic runs; if omitted, derived from scenario name"
}
}
}
}

View File

@@ -114,6 +114,29 @@
}
}
},
"micro_trade_count": {
"type": "integer",
"minimum": 0,
"description": "Count of support-lane micro-trades executed during the run"
},
"micro_trade_buy_count": {
"type": "integer",
"minimum": 0
},
"micro_trade_sell_count": {
"type": "integer",
"minimum": 0
},
"micro_trade_volume_total": {
"type": "number",
"minimum": 0,
"description": "Base-token units traded through the support lane"
},
"micro_trade_gas_cost_total": {
"type": "number",
"minimum": 0,
"description": "Abstract gas budget consumed by support trades"
},
"worst_pool_diagnostic": {
"type": "object",
"description": "Last-epoch worst pool at pre_arb, post_arb, post_bot",
@@ -148,4 +171,4 @@
}
}
}
}
}

View File

@@ -33,6 +33,23 @@ When multiple quotes exist (USDT/USDC/DAI), the bot routes to the best edge:
- If the bridge is congested or risk flags trip, bot widens bands / reduces exposure
### 5. Gas-budgeted micro-support lane
For selected wrapped USD rails, especially **cWUSDC** and **cWUSDT**, you can reserve a small gas budget for **micro-trades** against **USDC** / **USDT**:
- Prefer matched-quote rails when they exist: `cWUSDC/USDC`, `cWUSDT/USDT`
- Keep each trade tiny relative to `I_T^*`
- Use inventory-aware direction first: sell cW into the pool when inventory is too low; buy cW from the pool when inventory is too high
- If the pool is already near its inventory target, alternate tiny buys/sells only within a separate gas budget
Purpose:
- Give wrappers **observable turnover**
- Tighten route discovery and quote confidence
- Keep the support lane small enough that the PMM remains a **corridor defense tool**, not the primary venue
Important: this improves **tradability and price discovery**, but it does **not** create backing or intrinsic value by itself.
## Peg bands
See [../config/peg-bands.json](../config/peg-bands.json) and [05-oracles.md](05-oracles.md). Summary:

View File

@@ -29,6 +29,11 @@ Every run (hub-only, full-quote, bridge shock) should produce a scorecard with a
| `intervention_cost_inject_total` | number | Bot inject (bridge-in) cost only |
| `intervention_cost_withdraw_total` | number | Bot withdraw cost only |
| `intervention_cost_by_chain` | object | Per chain: `{ inject, withdraw }` — which chains are liquidity sinks |
| `micro_trade_count` | number | Count of gas-budgeted support trades executed |
| `micro_trade_buy_count` | number | Support-lane buys of cW from the pool |
| `micro_trade_sell_count` | number | Support-lane sells of cW into the pool |
| `micro_trade_volume_total` | number | Base-token units rotated through the support lane |
| `micro_trade_gas_cost_total` | number | Abstract gas budget consumed by support trades |
| `scenario` | string | e.g. `hub_only_11`, `full_quote_1_56_137`, `bridge_shock_137_56` |
| `runId` | string | Optional run identifier |
@@ -92,6 +97,14 @@ Compare deltas:
If churn jumps >50% with full-quote → clear “dont deploy full-quote” rule.
For **gas-budgeted support scenarios**, compare:
- **micro_trade_gas_cost_total** vs baseline intervention cost
- **micro_trade_volume_total / micro_trade_count** to verify trades stay genuinely micro
- **peak_deviation_bps_post_bot** vs the same scenario without micro-support
If support gas rises faster than the deviation improvement you get back, the support lane is too aggressive.
---
## 4. Phase 0: Runnable scenarios and knob guidance

View File

@@ -30,7 +30,7 @@ Validates `config/deployment-status.json` for minimum viable deployed graph. Use
**Rules:**
- If `bridgeAvailable === true` on a chain, `cwTokens` must include at least **cWUSDT** and **cWUSDC** (phase 1).
- For each `pmmPool`: `role` ∈ {defense, public_routing}; `feeBps` and `k` present; `base`/`quote` (or `tokenIn`/`tokenOut`) exist in `cwTokens` or `anchorAddresses`.
- For each `pmmPool` and each `pmmPoolsVolatile[]` entry: `role` ∈ {defense, public_routing, truu_routing}; `feeBps` and `k` present; `base`/`quote` (or `tokenIn`/`tokenOut`) exist in `cwTokens` or `anchorAddresses` (e.g. mainnet **TRUU** under `anchorAddresses.TRUU`). Non-zero `poolAddress` must not be the zero address.
**Run:**
@@ -40,12 +40,16 @@ node scripts/validate-deployment-status.cjs
**Exit code:** 0 if valid, 1 if invalid (errors to stderr).
**Parent proxmox repo:** live Mainnet cW/TRUU pool deploy and ratio-matched top-up scripts live under `scripts/deployment/` (`deploy-mainnet-pmm-cw-truu-pool.sh`, `add-mainnet-truu-pmm-topup.sh`); see `docs/03-deployment/MAINNET_PMM_TRUU_CWUSD_PEG_AND_BOT_RUNBOOK.md` §11.
---
## run-scenario.cjs
Builds the **real** routing graph from configs, runs epochs with PMM state updates, path enumeration + waterfilling, **arb step** (implied-price deviation, capped corrective trades, profit gate), **bot step** (inject/withdraw at 0.5×/1.5× I_T^* with intervention cost β/γ/ρ), and optional **bridge shock** trades. Emits a **real scorecard** (PR#2). Runs are **deterministic** when `scenario.seed` is set or derived from scenario name.
Scenarios may also include an optional **microTradePolicy** for gas-budgeted support trades in selected USD wrapper rails. This is meant to model tiny, controlled `cWUSDC` / `cWUSDT` turnover-seeding or inventory-corrective activity, not to turn the PMM into a venue.
**Configs used:** `simulation-params.json`, `token-map.json`, `routing-controls.json`; `deployment-status.json` only when `graphMode = deployed`. Pool topology from `pool-matrix.json` and scenario `topology` / `fullQuoteChains`.
**Tuning constants (in script):**
@@ -64,8 +68,9 @@ Builds the **real** routing graph from configs, runs epochs with PMM state updat
node scripts/run-scenario.cjs hub_only_11
node scripts/run-scenario.cjs --scenario full_quote_1_56_137
node scripts/run-scenario.cjs bridge_shock_137_56
node scripts/run-scenario.cjs micro_support_usd_wrappers_1_56_137
```
**Output:** JSON scorecard including: `capture_mean`, `churn_mean`, `drain_half_life_epochs`, `path_concentration_index`; `intervention_cost_total` / `intervention_cost_inject_total` / `intervention_cost_withdraw_total` / `intervention_cost_by_chain` / `intervention_cost_per_1M_volume`; `peak_deviation_bps` (post-arb), `peak_deviation_bps_pre_arb`, `peak_deviation_bps_post_arb`, `peak_deviation_bps_post_bot`; `arb_volume_total`, `arb_profit_total` (execution-based, not mid). See [docs/12-sim-scorecard.md](../docs/12-sim-scorecard.md) and [config/scorecard-schema.json](../config/scorecard-schema.json).
**Output:** JSON scorecard including: `capture_mean`, `churn_mean`, `drain_half_life_epochs`, `path_concentration_index`; `intervention_cost_total` / `intervention_cost_inject_total` / `intervention_cost_withdraw_total` / `intervention_cost_by_chain` / `intervention_cost_per_1M_volume`; `micro_trade_count` / `micro_trade_volume_total` / `micro_trade_gas_cost_total`; `peak_deviation_bps` (post-arb), `peak_deviation_bps_pre_arb`, `peak_deviation_bps_post_arb`, `peak_deviation_bps_post_bot`; `arb_volume_total`, `arb_profit_total` (execution-based, not mid). See [docs/12-sim-scorecard.md](../docs/12-sim-scorecard.md) and [config/scorecard-schema.json](../config/scorecard-schema.json).
**Orderflow:** Trade sizes use `distribution: "uniform"` by default. Scenario schema supports `lognormal` / `pareto` for skewed (many small + occasional whale) flows; implement in `sampleTrade()` when needed.

View File

@@ -355,6 +355,173 @@ function getBridgeRho(scenario, fromChain, toChain) {
return (blocks ?? 10) * rhoPerBlock / 10000;
}
function getMatchedQuoteForBase(base) {
if (base === 'cWUSDT') return 'USDT';
if (base === 'cWUSDC') return 'USDC';
return null;
}
function getMicroTradeCandidates(graph, policy) {
const tokenFilter = new Set(policy.tokens || []);
const quoteFilter = new Set(policy.quoteTokens || []);
let candidates = graph.pmmEdges.filter((e) => {
if (tokenFilter.size > 0 && !tokenFilter.has(e.base)) return false;
if (quoteFilter.size > 0 && !quoteFilter.has(e.quote)) return false;
return true;
});
if (policy.preferMatchedQuote) {
const perChainBase = new Map();
for (const edge of candidates) {
const key = `${edge.chainId}:${edge.base}`;
const preferredQuote = getMatchedQuoteForBase(edge.base);
const existing = perChainBase.get(key);
if (!existing) {
perChainBase.set(key, edge);
continue;
}
const score = edge.quote === preferredQuote ? 0 : 1;
const existingScore = existing.quote === preferredQuote ? 0 : 1;
if (
score < existingScore
|| (score === existingScore && edge.quote.localeCompare(existing.quote) < 0)
) {
perChainBase.set(key, edge);
}
}
candidates = Array.from(perChainBase.values());
}
candidates.sort((a, b) => (
a.chainId.localeCompare(b.chainId)
|| a.base.localeCompare(b.base)
|| a.quote.localeCompare(b.quote)
));
return candidates;
}
function resolveMicroTradeDirection(policy, poolState, epochIndex, tradeIndex) {
const mode = policy.mode || 'inventory_or_alternate';
const bandFrac = Number(policy.inventoryBandFraction ?? 0.05);
const lower = (1 - bandFrac) * poolState.I_T_star;
const upper = (1 + bandFrac) * poolState.I_T_star;
if (mode === 'inventory' || mode === 'inventory_or_alternate') {
if (poolState.I_T < lower) return 'sell_base';
if (poolState.I_T > upper) return 'buy_base';
if (mode === 'inventory') return null;
}
if (mode === 'alternate' || mode === 'inventory_or_alternate') {
return ((epochIndex + tradeIndex) % 2 === 0) ? 'buy_base' : 'sell_base';
}
if (mode === 'buy_base') return 'buy_base';
if (mode === 'sell_base') return 'sell_base';
return null;
}
function runMicroTradeSupportStep(graph, state, scenario, epochIndex) {
const policy = scenario.microTradePolicy || {};
if (policy.enabled === false || Object.keys(policy).length === 0) {
return {
state,
microTradeCount: 0,
microTradeBuyCount: 0,
microTradeSellCount: 0,
microTradeVolumeTotal: 0,
microTradeGasCostTotal: 0,
};
}
const requestedTrades = Math.max(0, parseInt(policy.tradesPerEpoch ?? 0, 10));
if (requestedTrades === 0) {
return {
state,
microTradeCount: 0,
microTradeBuyCount: 0,
microTradeSellCount: 0,
microTradeVolumeTotal: 0,
microTradeGasCostTotal: 0,
};
}
const candidates = getMicroTradeCandidates(graph, policy);
if (candidates.length === 0) {
return {
state,
microTradeCount: 0,
microTradeBuyCount: 0,
microTradeSellCount: 0,
microTradeVolumeTotal: 0,
microTradeGasCostTotal: 0,
};
}
const gasCostPerTrade = Number(policy.gasCostPerTradeUnits ?? 0);
const gasBudgetPerEpoch = Number(policy.gasBudgetPerEpochUnits ?? 0);
let tradesAllowed = requestedTrades;
if (gasCostPerTrade > 0 && gasBudgetPerEpoch > 0) {
tradesAllowed = Math.min(tradesAllowed, Math.floor(gasBudgetPerEpoch / gasCostPerTrade));
}
if (tradesAllowed <= 0) {
return {
state,
microTradeCount: 0,
microTradeBuyCount: 0,
microTradeSellCount: 0,
microTradeVolumeTotal: 0,
microTradeGasCostTotal: 0,
};
}
let curState = state;
let microTradeCount = 0;
let microTradeBuyCount = 0;
let microTradeSellCount = 0;
let microTradeVolumeTotal = 0;
let microTradeGasCostTotal = 0;
for (let i = 0; i < tradesAllowed; i++) {
const edge = candidates[(epochIndex * tradesAllowed + i) % candidates.length];
const poolState = curState[edge.key];
if (!poolState || poolState.I_T_star == null) continue;
const rawTradeSize = Number(policy.tradeSizeUnits ?? 0);
const maxFractionOfTarget = Number(policy.maxFractionOfTarget ?? 0.01);
const maxTradeSize = maxFractionOfTarget > 0 ? poolState.I_T_star * maxFractionOfTarget : rawTradeSize;
const tradeSize = Math.max(0, Math.min(rawTradeSize, maxTradeSize || rawTradeSize));
if (tradeSize <= 0) continue;
const direction = resolveMicroTradeDirection(policy, poolState, epochIndex, i);
if (!direction) continue;
if (direction === 'sell_base') {
const { newState } = pmmSellT(edge.key, tradeSize, curState);
curState = newState;
microTradeSellCount += 1;
microTradeVolumeTotal += tradeSize;
} else {
const quoteSpend = tradeSize * (poolState.P || 1);
const { outputT, newState } = pmmBuyT(edge.key, quoteSpend, curState);
curState = newState;
microTradeBuyCount += 1;
microTradeVolumeTotal += outputT;
}
microTradeCount += 1;
microTradeGasCostTotal += gasCostPerTrade;
}
return {
state: curState,
microTradeCount,
microTradeBuyCount,
microTradeSellCount,
microTradeVolumeTotal,
microTradeGasCostTotal,
};
}
function runBotStep(graph, state, scenario, configs) {
const chains = graph.chains;
let curState = state;
@@ -625,6 +792,11 @@ function runEpoch(scenario, graph, state, configs, epochIndex) {
let pmmVolume = 0;
let churnSum = 0;
const I_T_start = {};
let microTradeCount = 0;
let microTradeBuyCount = 0;
let microTradeSellCount = 0;
let microTradeVolumeTotal = 0;
let microTradeGasCostTotal = 0;
for (const k of Object.keys(state)) {
if (state[k].I_T_star != null) I_T_start[k] = state[k].I_T;
@@ -682,6 +854,15 @@ function runEpoch(scenario, graph, state, configs, epochIndex) {
}
}
const micro = runMicroTradeSupportStep(graph, curState, scenario, epochIndex);
curState = micro.state;
microTradeCount += micro.microTradeCount || 0;
microTradeBuyCount += micro.microTradeBuyCount || 0;
microTradeSellCount += micro.microTradeSellCount || 0;
microTradeVolumeTotal += micro.microTradeVolumeTotal || 0;
microTradeGasCostTotal += micro.microTradeGasCostTotal || 0;
totalVolume += micro.microTradeVolumeTotal || 0;
const peakDeviationBpsPreArb = maxDeviationBpsOverPools(graph, curState);
const worstPreArb = getWorstPoolDiagnostic(graph, curState);
const { state: afterArb, arbVolumeTotal, arbProfitTotal, peakDeviationBps: peakDeviationBpsPostArb } = runArbStep(graph, curState, configs);
@@ -717,6 +898,11 @@ function runEpoch(scenario, graph, state, configs, epochIndex) {
interventionCostInject: interventionCostInject || 0,
interventionCostWithdraw: interventionCostWithdraw || 0,
interventionCostByChain: interventionCostByChain || {},
microTradeCount,
microTradeBuyCount,
microTradeSellCount,
microTradeVolumeTotal,
microTradeGasCostTotal,
peakDeviationBpsPreArb: peakDeviationBpsPreArb || 0,
peakDeviationBpsPostArb: peakDeviationBpsPostArb || 0,
peakDeviationBpsPostBot: peakDeviationBpsPostBot || 0,
@@ -753,6 +939,11 @@ function computeScorecard(scenario, scenarioName, graph, initialState, epochResu
let peakDeviationBpsPreArb = 0;
let peakDeviationBpsPostArb = 0;
let peakDeviationBpsPostBot = 0;
let microTradeCountTotal = 0;
let microTradeBuyCountTotal = 0;
let microTradeSellCountTotal = 0;
let microTradeVolumeTotal = 0;
let microTradeGasCostTotal = 0;
for (const r of epochResults) {
totalVolume += r.totalVolume;
@@ -763,6 +954,11 @@ function computeScorecard(scenario, scenarioName, graph, initialState, epochResu
arbProfitTotal += r.arbProfitTotal || 0;
interventionCostInjectTotal += r.interventionCostInject || 0;
interventionCostWithdrawTotal += r.interventionCostWithdraw || 0;
microTradeCountTotal += r.microTradeCount || 0;
microTradeBuyCountTotal += r.microTradeBuyCount || 0;
microTradeSellCountTotal += r.microTradeSellCount || 0;
microTradeVolumeTotal += r.microTradeVolumeTotal || 0;
microTradeGasCostTotal += r.microTradeGasCostTotal || 0;
for (const [chainId, v] of Object.entries(r.interventionCostByChain || {})) {
if (!interventionCostByChain[chainId]) interventionCostByChain[chainId] = { inject: 0, withdraw: 0 };
interventionCostByChain[chainId].inject += v.inject || 0;
@@ -838,6 +1034,11 @@ function computeScorecard(scenario, scenarioName, graph, initialState, epochResu
intervention_cost_withdraw_total: Math.round(interventionCostWithdrawTotal),
intervention_cost_by_chain: interventionCostByChain,
intervention_cost_per_1M_volume: Math.round(interventionPer1M * 100) / 100,
micro_trade_count: Math.round(microTradeCountTotal),
micro_trade_buy_count: Math.round(microTradeBuyCountTotal),
micro_trade_sell_count: Math.round(microTradeSellCountTotal),
micro_trade_volume_total: Math.round(microTradeVolumeTotal),
micro_trade_gas_cost_total: Math.round(microTradeGasCostTotal * 100) / 100,
peak_deviation_bps: Math.round(Number.isFinite(peakDeviationBpsPostArb) ? peakDeviationBpsPostArb : 0),
peak_deviation_bps_pre_arb: Math.round(peakDeviationBpsPreArb),
peak_deviation_bps_post_arb: Math.round(peakDeviationBpsPostArb),

View File

@@ -5,8 +5,9 @@
*
* Rules:
* - If bridgeAvailable === true on a chain, cwTokens must include at least cWUSDT and cWUSDC (phase 1).
* - For each pmmPool: role in {defense, public_routing}, feeBps and k present,
* base/quote (or tokenIn/tokenOut) exist in cwTokens or anchorAddresses.
* - For each pmmPool / pmmPoolsVolatile[]: role in {defense, public_routing, truu_routing},
* feeBps and k present, base/quote (or tokenIn/tokenOut) exist in cwTokens or anchorAddresses.
* TRUU must be listed under anchorAddresses when used as quote (e.g. mainnet chain 1).
*
* Exit code: 0 if valid, 1 if invalid (and prints errors to stderr).
*/
@@ -18,12 +19,41 @@ const CONFIG_DIR = path.join(__dirname, '..', 'config');
const DEPLOYMENT_STATUS_PATH = path.join(CONFIG_DIR, 'deployment-status.json');
const PHASE1_CW = ['cWUSDT', 'cWUSDC'];
const VALID_ROLES = ['defense', 'public_routing'];
const VALID_ROLES = ['defense', 'public_routing', 'truu_routing'];
const VALID_REFERENCE_PROTOCOLS = ['uniswap_v3', 'balancer', 'curve', '1inch'];
function loadJson(p) {
return JSON.parse(fs.readFileSync(p, 'utf8'));
}
function validatePoolEntries(chainId, pools, listLabel, knownTokens, errors) {
for (let i = 0; i < pools.length; i++) {
const pool = pools[i];
const base = pool.base ?? pool.tokenIn;
const quote = pool.quote ?? pool.tokenOut;
if (!VALID_ROLES.includes(pool.role)) {
errors.push(`Chain ${chainId} ${listLabel}[${i}]: role must be one of ${VALID_ROLES.join(', ')}`);
}
if (pool.feeBps == null || pool.k == null) {
errors.push(`Chain ${chainId} ${listLabel}[${i}]: feeBps and k required`);
}
if (base && !knownTokens.has(base)) {
errors.push(`Chain ${chainId} ${listLabel}[${i}]: base/tokenIn "${base}" not in cwTokens or anchorAddresses`);
}
if (quote && !knownTokens.has(quote)) {
errors.push(`Chain ${chainId} ${listLabel}[${i}]: quote/tokenOut "${quote}" not in cwTokens or anchorAddresses`);
}
const addr = pool.poolAddress;
if (addr != null && addr !== '') {
const z = String(addr).toLowerCase();
if (z === '0x0000000000000000000000000000000000000000') {
errors.push(`Chain ${chainId} ${listLabel}[${i}]: poolAddress must not be zero when set`);
}
}
}
}
function main() {
const status = loadJson(DEPLOYMENT_STATUS_PATH);
const chains = status.chains || {};
@@ -31,8 +61,13 @@ function main() {
for (const [chainId, chain] of Object.entries(chains)) {
const cwTokens = chain.cwTokens || {};
const gasMirrors = chain.gasMirrors || {};
const anchorAddresses = chain.anchorAddresses || {};
const gasQuoteAddresses = chain.gasQuoteAddresses || {};
const pmmPools = chain.pmmPools || [];
const pmmPoolsVolatile = chain.pmmPoolsVolatile || [];
const gasPmmPools = chain.gasPmmPools || [];
const gasReferenceVenues = chain.gasReferenceVenues || [];
const bridgeAvailable = chain.bridgeAvailable;
if (bridgeAvailable === true) {
@@ -43,24 +78,63 @@ function main() {
}
}
const knownTokens = new Set([...Object.keys(cwTokens), ...Object.keys(anchorAddresses)]);
const knownTokens = new Set([
...Object.keys(cwTokens),
...Object.keys(gasMirrors),
...Object.keys(anchorAddresses),
...Object.keys(gasQuoteAddresses),
]);
for (let i = 0; i < pmmPools.length; i++) {
const pool = pmmPools[i];
const base = pool.base ?? pool.tokenIn;
const quote = pool.quote ?? pool.tokenOut;
validatePoolEntries(chainId, pmmPools, 'pmmPools', knownTokens, errors);
validatePoolEntries(chainId, pmmPoolsVolatile, 'pmmPoolsVolatile', knownTokens, errors);
validatePoolEntries(chainId, gasPmmPools, 'gasPmmPools', knownTokens, errors);
if (!VALID_ROLES.includes(pool.role)) {
errors.push(`Chain ${chainId} pmmPools[${i}]: role must be one of ${VALID_ROLES.join(', ')}`);
const gasPoolsByFamily = new Map();
for (const pool of gasPmmPools) {
if (!pool.familyKey || typeof pool.familyKey !== 'string') {
errors.push(`Chain ${chainId} gasPmmPools entry is missing familyKey`);
continue;
}
if (pool.feeBps == null || pool.k == null) {
errors.push(`Chain ${chainId} pmmPools[${i}]: feeBps and k required`);
if (!gasPoolsByFamily.has(pool.familyKey)) gasPoolsByFamily.set(pool.familyKey, []);
gasPoolsByFamily.get(pool.familyKey).push(pool);
}
for (const [familyKey, pools] of gasPoolsByFamily.entries()) {
const poolTypes = new Set(pools.map((pool) => pool.poolType));
if (!poolTypes.has('wrapped_native')) {
errors.push(`Chain ${chainId} gas family ${familyKey}: missing wrapped_native DODO pool`);
}
if (base && !knownTokens.has(base)) {
errors.push(`Chain ${chainId} pmmPools[${i}]: base/tokenIn "${base}" not in cwTokens or anchorAddresses`);
if (!poolTypes.has('stable_quote')) {
errors.push(`Chain ${chainId} gas family ${familyKey}: missing stable_quote DODO pool`);
}
if (quote && !knownTokens.has(quote)) {
errors.push(`Chain ${chainId} pmmPools[${i}]: quote/tokenOut "${quote}" not in cwTokens or anchorAddresses`);
}
const referenceVenuesByFamily = new Map();
for (let i = 0; i < gasReferenceVenues.length; i++) {
const venue = gasReferenceVenues[i];
if (!VALID_REFERENCE_PROTOCOLS.includes(venue.protocol)) {
errors.push(`Chain ${chainId} gasReferenceVenues[${i}]: protocol must be one of ${VALID_REFERENCE_PROTOCOLS.join(', ')}`);
}
if (!venue.familyKey || typeof venue.familyKey !== 'string') {
errors.push(`Chain ${chainId} gasReferenceVenues[${i}]: familyKey required`);
continue;
}
if (!referenceVenuesByFamily.has(venue.familyKey)) referenceVenuesByFamily.set(venue.familyKey, []);
referenceVenuesByFamily.get(venue.familyKey).push(venue);
}
for (const [familyKey, venues] of referenceVenuesByFamily.entries()) {
const protocols = new Set(venues.map((venue) => venue.protocol));
if (!protocols.has('uniswap_v3')) {
errors.push(`Chain ${chainId} gas family ${familyKey}: missing uniswap_v3 reference venue`);
}
const oneInch = venues.find((venue) => venue.protocol === '1inch');
if (oneInch?.routingVisible === true || oneInch?.live === true) {
const hasUniswap = venues.some((venue) => venue.protocol === 'uniswap_v3' && venue.live === true);
const hasDodo = (gasPoolsByFamily.get(familyKey) || []).some((pool) => pool.publicRoutingEnabled === true);
if (!hasUniswap || !hasDodo) {
errors.push(`Chain ${chainId} gas family ${familyKey}: 1inch cannot be live/routingVisible before DODO and Uniswap venues are live`);
}
}
}
}

View File

@@ -22,9 +22,10 @@ For each epoch:
2. **Compute candidate paths** (e.g. k-shortest paths or enumerate swap+bridge combos).
3. **Allocate flow** by marginal-equalization heuristic (waterfilling): split volume across paths so marginal output equalizes.
4. **Update PMM inventories** and implied prices (use inventory-sensitive depth D = D_0·min(1, I_T/I_T^*)).
5. **Arb step (optional):** agents that trade toward oracle when profitable; update inventories again.
6. **Bot intervention step:** apply policy from config (or keep exogenous); record intervention cost.
7. **Emit scorecard** (or accumulate for end-of-run scorecard).
5. **Micro-support step (optional):** run gas-budgeted support trades for selected rails (e.g. `cWUSDC`, `cWUSDT`) to model tiny turnover-seeding or inventory-corrective activity.
6. **Arb step (optional):** agents that trade toward oracle when profitable; update inventories again.
7. **Bot intervention step:** apply policy from config (or keep exogenous); record intervention cost.
8. **Emit scorecard** (or accumulate for end-of-run scorecard).
---
@@ -46,9 +47,10 @@ AMM edges can be mocked (e.g. fixed 5 bps spread); relative behavior of PMM vs a
- **PMM state:** Per-pool `I_T`, `D = D_0·min(1, I_T/I_T^*)`, sell/buy with documented formula; routing controls applied.
- **Routing:** Candidate paths (same-chain, length ≤3), top-K by cost; waterfilling by chunk (5%), marginal-equalization.
- **Arb step (PR#2):** Implied price (sell/buy probe) vs oracle P; deviation δ; if |δ| > DELTA_ARB_BPS, trade in corrective direction with size min(x_max, α·|δ|·I_T^*); profit gate (skip if profit ≤ 0). Tuning: `DELTA_ARB_BPS`, `ARB_ALPHA`, `ARB_MAX_FRACTION_OF_TARGET`.
- **Micro-support step:** When `scenario.microTradePolicy` is enabled, run tiny support trades on selected cW rails before arb. Policy can prefer matched quotes (`cWUSDC/USDC`, `cWUSDT/USDT`), stay inside an inventory band, alternate when inside band, and clip the trade count by a per-epoch gas budget.
- **Bot step (PR#2):** If I_T < 0.5·I_T^* inject; if I_T > 1.5·I_T^* withdraw; action clipped to `BOT_MAX_FRACTION_OF_TARGET`·I_T^*; intervention cost = |u|·(β+ρ)+γ (bridge params + latency ρ from scenario `latencyModel`).
- **Bridge shock:** When `scenario.bridgeShock` is set, extra trades (sell cW on fromChain, buy cW on toChain) over `durationEpochs` at `magnitudeFraction` of baseline.
- **Determinism:** RNG seeded from `scenario.seed` or hash of scenario name.
- **Scorecard:** All PR#1 metrics plus `peak_deviation_bps`, `intervention_cost_*`, `arb_volume_total`, `arb_profit_total`.
- **Scorecard:** All PR#1 metrics plus `peak_deviation_bps`, `intervention_cost_*`, `arb_volume_total`, `arb_profit_total`, and support-lane `micro_trade_*` metrics.
Tuning: see constants in script and [scripts/README.md](../scripts/README.md).

View File

@@ -33,5 +33,6 @@ for each pool (cW* / quote):
- **Bridge throttle**: If bridge backlog or risk flag, widen bands or skip trade.
- **Global budget**: Before any trade, check per-token trade budget for current window; skip if exhausted.
- **Min improvement**: Only trade if expected |δ| improvement net of fees ≥ minImprovementBps.
- **Gas-budgeted micro-support**: For selected USD wrappers (especially `cWUSDC`, `cWUSDT`), allow tiny matched-quote trades (`cWUSDC/USDC`, `cWUSDT/USDT` when available) under a separate gas budget so wrappers stay economically live without converting the PMM into the destination venue.
Thresholds and bands from [../config/peg-bands.json](../config/peg-bands.json). Mesh reflexivity from [../docs/07-mesh-reflexivity.md](../docs/07-mesh-reflexivity.md).